#if (ECORE_VERSION_MAJOR > 1) || (ECORE_VERSION_MINOR >= 8) #include "e.h" #include "e_mod_main.h" #define HISTORY_MAX 8 #define DEBUG_INFO 1 static E_Config_DD *conf_edd = NULL; Config *access_config = NULL; typedef struct { E_Zone *zone; Ecore_X_Window win; Ecore_Timer *timer; Ecore_Timer *double_down_timer; Ecore_Timer *tap_timer; Evas_Object *info; Evas_Object *text; int x, y, dx, dy, mx, my; int mouse_history[HISTORY_MAX]; unsigned int dt; Eina_Inlist *history; Eina_Bool longpressed : 1; Eina_Bool two_finger_down : 1; Eina_Bool double_down : 1; } Cover; #if DEBUG_INFO static Ecore_Timer *dbg_timer = NULL; static Eina_Bool _reset_text(void *data) { Cover *cov = data; if(!cov) return EINA_FALSE; ecore_timer_del(dbg_timer); dbg_timer = NULL; evas_object_text_text_set(cov->text, "Screen Reader Mode"); return EINA_FALSE; } #define INFO(cov, txt) \ evas_object_text_text_set(cov->text, txt); \ EINA_LOG_INFO("%s", txt); \ if (dbg_timer) \ { \ ecore_timer_del(dbg_timer); \ dbg_timer = NULL; \ } \ dbg_timer = ecore_timer_add(1.0, _reset_text, cov); #else #define INFO(cov, txt) EINA_LOG_INFO("%s", txt) #endif typedef struct { EINA_INLIST; int device; } Multi; static E_Border *_prev_bd; static Ecore_X_Atom _atom_access = 0; static Ecore_X_Window target_win = 0; static Eina_List *covers = NULL; static Eina_List *handlers = NULL; static Ecore_Event_Handler *client_message_handler = NULL; static Ecore_Event_Handler *property_handler = NULL; static int multi_device[3]; static void _mouse_in_win_get(Cover *cov, int x, int y) { Eina_List *l; Ecore_X_Window *skip; Cover *cov2; int i; skip = alloca(sizeof(Ecore_X_Window) * eina_list_count(covers)); i = 0; EINA_LIST_FOREACH(covers, l, cov2) { skip[i] = cov2->win; i++; } /* TODO: if target window is changed without highlight object, then previous target window which has the highlight object should get the message. how? */ target_win = ecore_x_window_shadow_tree_at_xy_with_skip_get (cov->zone->container->manager->root, x, y, skip, i); } static unsigned int _win_angle_get(Ecore_X_Window win) { Ecore_X_Window root; if (!win) return 0; int ret; int count; int angle = 0; unsigned char *prop_data = NULL; root = ecore_x_window_root_get(win); ret = ecore_x_window_prop_property_get(root, ECORE_X_ATOM_E_ILLUME_ROTATE_ROOT_ANGLE, ECORE_X_ATOM_CARDINAL, 32, &prop_data, &count); if (ret && prop_data) memcpy (&angle, prop_data, sizeof (int)); if (prop_data) free (prop_data); return angle; } static void _coordinate_calibrate(Ecore_X_Window win, int *x, int *y) { int tx, ty, w, h; unsigned int angle; if (!x) return; if (!y) return; angle = _win_angle_get(win); ecore_x_window_geometry_get(win, NULL, NULL, &w, &h); tx = *x; ty = *y; switch (angle) { case 90: *x = h - ty; *y = tx; break; case 180: *x = w - tx; *y = h - ty; break; case 270: *x = ty; *y = w - tx; break; default: break; } } static void _mouse_win_fake_tap(Cover *cov, Ecore_Event_Mouse_Button *ev) { int x, y; /* find target window to send message */ _mouse_in_win_get(cov, ev->root.x, ev->root.y); ecore_x_pointer_xy_get(target_win, &x, &y); ecore_x_mouse_in_send(target_win, x, y); ecore_x_mouse_move_send(target_win, x, y); ecore_x_mouse_down_send(target_win, x, y, 1); ecore_x_mouse_up_send(target_win, x, y, 1); ecore_x_mouse_out_send(target_win, x, y); } static void _messsage_read_send(Cover *cov) { int x, y; /* find target window to send message */ _mouse_in_win_get(cov, cov->x, cov->y); ecore_x_pointer_xy_get(target_win, &x, &y); _coordinate_calibrate(target_win, &x, &y); ecore_x_client_message32_send(target_win, ECORE_X_ATOM_E_ILLUME_ACCESS_CONTROL, ECORE_X_EVENT_MASK_WINDOW_CONFIGURE, target_win, ECORE_X_ATOM_E_ILLUME_ACCESS_ACTION_READ, x, y, 0); #if DEBUG_INFO Eina_List *l; Cover *ecov; Eina_Strbuf *buf; buf = eina_strbuf_new(); eina_strbuf_append_printf(buf, "read x:%d, y:%d", x, y); EINA_LIST_FOREACH(covers, l, ecov) { INFO(ecov, eina_strbuf_string_get(buf)); } eina_strbuf_free(buf); #endif } static Eina_Bool _mouse_longpress(void *data) { Cover *cov = data; int distance = 40; int dx, dy; cov->timer = NULL; dx = cov->x - cov->dx; dy = cov->y - cov->dy; if (((dx * dx) + (dy * dy)) < (distance * distance)) { cov->longpressed = EINA_TRUE; INFO(cov, "longpress"); if (!cov->double_down) _messsage_read_send(cov); else { INFO(cov, "double down and longpress"); //TODO: send message to notify start longpress } } return EINA_FALSE; } static Eina_Bool _mouse_double_down(void *data) { Cover *cov = data; ecore_timer_del(cov->double_down_timer); cov->double_down_timer = NULL; return EINA_FALSE; } static void _double_down_timeout(Cover *cov) { double long_time = 0.5; double short_time = 0.3; int distance = 40; int dx, dy; dx = cov->x - cov->dx; dy = cov->y - cov->dy; if ((cov->double_down_timer) && (((dx * dx) + (dy * dy)) < (distance * distance))) { // start double tap and move from here cov->double_down = EINA_TRUE; if (cov->timer) { ecore_timer_del(cov->timer); cov->timer = NULL; } /* check longpress after double down */ cov->timer = ecore_timer_add(long_time, _mouse_longpress, cov); } if (cov->double_down_timer) { ecore_timer_del(cov->double_down_timer); cov->double_down_timer = NULL; return; } cov->double_down_timer = ecore_timer_add(short_time, _mouse_double_down, cov); } static Eina_Bool _mouse_tap(void *data) { Cover *cov = data; cov->tap_timer = NULL; _messsage_read_send(cov); return EINA_FALSE; } static void _mouse_down(Cover *cov, Ecore_Event_Mouse_Button *ev) { double long_time = 0.5; cov->dx = ev->x; cov->dy = ev->y; cov->mx = ev->x; cov->my = ev->y; cov->x = ev->x; cov->y = ev->y; cov->dt = ev->timestamp; cov->longpressed = EINA_FALSE; cov->timer = ecore_timer_add(long_time, _mouse_longpress, cov); if (cov->tap_timer) { ecore_timer_del(cov->tap_timer); cov->tap_timer = NULL; } /* check mouse double down - not two fingers, refer to double click */ _double_down_timeout(cov); } static void _mouse_up(Cover *cov, Ecore_Event_Mouse_Button *ev) { double timeout = 0.15; double double_tap_timeout = 0.25; int distance = 40; int dx, dy; int x, y; int angle = 0; // for two finger panning if (cov->two_finger_down) { ecore_x_pointer_xy_get(target_win, &x, &y); ecore_x_mouse_up_send(target_win, x, y, 1); cov->two_finger_down = EINA_FALSE; ecore_x_mouse_out_send(target_win, x, y); } // reset double down and moving cov->double_down = EINA_FALSE; if (cov->timer) { ecore_timer_del(cov->timer); cov->timer = NULL; } if (cov->longpressed) { cov->longpressed = EINA_FALSE; return; } dx = ev->x - cov->dx; dy = ev->y - cov->dy; if (((dx * dx) + (dy * dy)) < (distance * distance)) { if (ev->double_click) { /* activate message would change focused window FIXME: but it is possibe to create unfocused window in this case, the message should go to unfocused window? */ _prev_bd = e_border_focused_get(); INFO(cov, "double_click"); ecore_x_e_illume_access_action_activate_send(target_win); } else if ((ev->timestamp - cov->dt) <= (timeout * 1000)) { cov->tap_timer = ecore_timer_add(double_tap_timeout, _mouse_tap, cov); } else if ((ev->timestamp - cov->dt) > (timeout * 1000) && (ev->timestamp - cov->dt) < (2 * timeout * 1000)) { INFO(cov, "tap"); _mouse_win_fake_tap(cov, ev); } } else if (((dx * dx) + (dy * dy)) > (4 * distance * distance) && ((ev->timestamp - cov->dt) < (timeout * 1000))) { /* get root window rotation */ angle = _win_angle_get(target_win); if (abs(dx) > abs(dy)) // left or right { if (dx > 0) // right { INFO(cov, "single flick right"); switch (angle) { case 180: case 270: ecore_x_e_illume_access_action_read_prev_send (target_win); break; case 90: default: ecore_x_e_illume_access_action_read_next_send (target_win); break; } } else // left { INFO(cov, "single flick left"); switch (angle) { case 180: case 270: ecore_x_e_illume_access_action_read_next_send (target_win); break; case 90: default: ecore_x_e_illume_access_action_read_prev_send (target_win); break; } } } else // up or down { if (dy > 0) // down { INFO(cov, "single flick down"); switch (angle) { case 90: case 180: ecore_x_e_illume_access_action_read_prev_send (target_win); break; case 270: default: ecore_x_e_illume_access_action_read_next_send (target_win); break; } } else // up { INFO(cov, "single flick up"); switch (angle) { case 90: case 180: ecore_x_e_illume_access_action_read_next_send (target_win); break; case 270: default: ecore_x_e_illume_access_action_read_prev_send (target_win); break; } } } } cov->longpressed = EINA_FALSE; } static void _mouse_move(Cover *cov, Ecore_Event_Mouse_Move *ev) { int x, y; //FIXME: why here.. after long press you cannot go below.. //if (!cov->down) return; //FIXME: one finger cannot come here //_record_mouse_history(cov, ev); ecore_x_pointer_xy_get(target_win, &x, &y); ecore_x_mouse_move_send(target_win, x, y); } static void _mouse_wheel(Cover *cov __UNUSED__, Ecore_Event_Mouse_Wheel *ev __UNUSED__) { if (ev->z == -1) // up { #if ECORE_VERSION_MAJOR >= 1 # if ECORE_VERSION_MINOR >= 8 ecore_x_e_illume_access_action_up_send(target_win); # endif #endif } else if (ev->z == 1) // down { #if ECORE_VERSION_MAJOR >= 1 # if ECORE_VERSION_MINOR >= 8 ecore_x_e_illume_access_action_down_send(target_win); # endif #endif } } static Eina_Bool _cb_mouse_down(void *data __UNUSED__, int type __UNUSED__, void *event) { Ecore_Event_Mouse_Button *ev = event; Eina_List *l; Cover *cov; int i = 0; int x, y; E_Border *bd; for (i = 0; i < 3; i++) { if (multi_device[i] == -1) { multi_device[i] = ev->multi.device; break; } else if (multi_device[i] == ev->multi.device) break; } /* activate message would change focused window FIXME: but it is possibe to create unfocused window in this case, the message should go to focused window? */ bd = e_border_focused_get(); if (bd && (bd != _prev_bd)) target_win = bd->client.win; EINA_LIST_FOREACH(covers, l, cov) { if (ev->window == cov->win) { // XXX change specific number if (ev->multi.device == multi_device[0]) _mouse_down(cov, ev); if (ev->multi.device == multi_device[1] && !(cov->two_finger_down)) { // prevent longpress client message by two finger if (cov->timer) { ecore_timer_del(cov->timer); cov->timer = NULL; } ecore_x_pointer_xy_get(target_win, &x, &y); ecore_x_mouse_in_send(target_win, x, y); ecore_x_mouse_move_send(target_win, x, y); ecore_x_mouse_down_send(target_win, x, y, 1); cov->two_finger_down = EINA_TRUE; } return ECORE_CALLBACK_PASS_ON; } } return ECORE_CALLBACK_PASS_ON; } static Eina_Bool _cb_mouse_up(void *data __UNUSED__, int type __UNUSED__, void *event) { Ecore_Event_Mouse_Button *ev = event; Eina_List *l; Cover *cov; EINA_LIST_FOREACH(covers, l, cov) { if (ev->window == cov->win) { if (ev->buttons == 1) _mouse_up(cov, ev); return ECORE_CALLBACK_PASS_ON; } } return ECORE_CALLBACK_PASS_ON; } static Eina_Bool _cb_mouse_move(void *data __UNUSED__, int type __UNUSED__, void *event) { Ecore_Event_Mouse_Move *ev = event; Eina_List *l; Cover *cov; int angle = 0; EINA_LIST_FOREACH(covers, l, cov) { cov->x = ev->x; cov->y = ev->y; if (ev->window == cov->win) { //if (ev->multi.device == multi_device[0] || ev->multi.device == multi_device[1]) if (cov->two_finger_down && ev->multi.device == multi_device[1]) _mouse_move(cov, ev); else if (cov->longpressed && // client message for moving is available only after long press is detected !(cov->double_down) && /* mouse move after double down should not send read message */ !(cov->two_finger_down) && ev->multi.device == multi_device[0]) { INFO(cov, "read"); _messsage_read_send(cov); } else if (cov->double_down && // client message for moving is available only after long press is detected !(cov->two_finger_down) && ev->multi.device == multi_device[0]) { int distance = 5; int dx, dy; if (cov->longpressed) { //TODO: send message to notify move afte longpress } else { dx = ev->x - cov->mx; dy = ev->y - cov->my; /* get root window rotation */ angle = _win_angle_get(target_win); if (((dx * dx) + (dy * dy)) > (distance * distance)) { if (abs(dx) > abs(dy)) // left or right { if (dx > 0) // right { INFO(cov, "mouse double down and move - right"); switch (angle) { case 180: case 270: ecore_x_e_illume_access_action_down_send(target_win); break; case 90: default: ecore_x_e_illume_access_action_up_send(target_win); break; } } else // left { INFO(cov, "mouse double down and move - left"); switch (angle) { case 180: case 270: ecore_x_e_illume_access_action_up_send(target_win); break; case 90: default: ecore_x_e_illume_access_action_down_send(target_win); break; } } } else // up or down { if (dy > 0) // down { INFO(cov, "mouse double down and move - down"); switch (angle) { case 90: case 180: ecore_x_e_illume_access_action_up_send(target_win); break; case 270: default: ecore_x_e_illume_access_action_down_send(target_win); break; } } else // up { INFO(cov, "mouse double down and move - up"); switch (angle) { case 90: case 180: ecore_x_e_illume_access_action_down_send(target_win); break; case 270: default: ecore_x_e_illume_access_action_up_send(target_win); break; } } } cov->mx = ev->x; cov->my = ev->y; } } } return ECORE_CALLBACK_PASS_ON; } } return ECORE_CALLBACK_PASS_ON; } static Eina_Bool _cb_mouse_wheel(void *data __UNUSED__, int type __UNUSED__, void *event) { Ecore_Event_Mouse_Wheel *ev = event; Eina_List *l; Cover *cov; EINA_LIST_FOREACH(covers, l, cov) { if (ev->window == cov->win) { _mouse_wheel(cov, ev); return ECORE_CALLBACK_PASS_ON; } } return ECORE_CALLBACK_PASS_ON; } static Cover * _cover_new(E_Zone *zone) { Cover *cov; cov = E_NEW(Cover, 1); if (!cov) return NULL; cov->zone = zone; #if DEBUG_INFO Ecore_Evas *ee; ee = ecore_evas_new(NULL, zone->container->x + zone->x, zone->container->y + zone->y, zone->w, zone->h, NULL); ecore_evas_alpha_set(ee, EINA_TRUE); cov->win = (Ecore_X_Window)ecore_evas_window_get(ee); /* create infomation */ Evas *e; e = ecore_evas_get(ee); cov->info = evas_object_rectangle_add(e); evas_object_color_set(cov->info, 255, 255, 255, 100); evas_object_move(cov->info, zone->container->x + zone->x, zone->container->y + zone->y); evas_object_resize(cov->info, zone->w, 30); evas_object_show(cov->info); cov->text = evas_object_text_add(e); evas_object_text_style_set(cov->text, EVAS_TEXT_STYLE_PLAIN); evas_object_text_font_set(cov->text, "DejaVu", 14); INFO(cov, "Screen Reader Mode"); evas_object_color_set(cov->text, 0, 0, 0, 255); evas_object_resize(cov->text, (zone->w / 8), 20); evas_object_move(cov->text, zone->container->x + zone->x + 5, zone->container->y + zone->y + 5); evas_object_show(cov->text); #else cov->win = ecore_x_window_input_new(zone->container->manager->root, zone->container->x + zone->x, zone->container->y + zone->y, zone->w, zone->h); #endif ecore_x_input_multi_select(cov->win); ecore_x_icccm_title_set(cov->win, "access-screen-reader"); ecore_x_netwm_name_set(cov->win, "access-screen-reader"); ecore_x_window_ignore_set(cov->win, 1); ecore_x_window_configure(cov->win, ECORE_X_WINDOW_CONFIGURE_MASK_SIBLING | ECORE_X_WINDOW_CONFIGURE_MASK_STACK_MODE, 0, 0, 0, 0, 0, zone->container->layers[8].win, ECORE_X_WINDOW_STACK_ABOVE); ecore_x_window_show(cov->win); ecore_x_window_raise(cov->win); return cov; } static void _covers_init(void) { Eina_List *l, *l2, *l3; E_Manager *man; int i = 0; EINA_LIST_FOREACH(e_manager_list(), l, man) { E_Container *con; EINA_LIST_FOREACH(man->containers, l2, con) { E_Zone *zone; EINA_LIST_FOREACH(con->zones, l3, zone) { Cover *cov = _cover_new(zone); if (cov) { covers = eina_list_append(covers, cov); for (i = 0; i < HISTORY_MAX; i++) cov->mouse_history[i] = -1; } } } } } static void _covers_shutdown(void) { Cover *cov; EINA_LIST_FREE(covers, cov) { ecore_x_window_ignore_set(cov->win, 0); ecore_x_window_free(cov->win); evas_object_del(cov->info); evas_object_del(cov->text); if (cov->timer) { ecore_timer_del(cov->timer); cov->timer = NULL; } if (cov->double_down_timer) { ecore_timer_del(cov->double_down_timer); cov->double_down_timer = NULL; } if (cov->tap_timer) { ecore_timer_del(cov->tap_timer); cov->tap_timer = NULL; } #if DEBUG_INFO if (dbg_timer) { ecore_timer_del(dbg_timer); dbg_timer = NULL; } #endif free(cov); } } static Eina_Bool _cb_zone_add(void *data __UNUSED__, int type __UNUSED__, void *event __UNUSED__) { _covers_shutdown(); _covers_init(); return ECORE_CALLBACK_PASS_ON; } static Eina_Bool _cb_zone_del(void *data __UNUSED__, int type __UNUSED__, void *event __UNUSED__) { _covers_shutdown(); _covers_init(); return ECORE_CALLBACK_PASS_ON; } static Eina_Bool _cb_zone_move_resize(void *data __UNUSED__, int type __UNUSED__, void *event __UNUSED__) { _covers_shutdown(); _covers_init(); return ECORE_CALLBACK_PASS_ON; } static void _events_init(void) { int i = 0; handlers = eina_list_append (handlers, ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_DOWN, _cb_mouse_down, NULL)); handlers = eina_list_append (handlers, ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_UP, _cb_mouse_up, NULL)); handlers = eina_list_append (handlers, ecore_event_handler_add(ECORE_EVENT_MOUSE_MOVE, _cb_mouse_move, NULL)); handlers = eina_list_append (handlers, ecore_event_handler_add(ECORE_EVENT_MOUSE_WHEEL, _cb_mouse_wheel, NULL)); handlers = eina_list_append (handlers, ecore_event_handler_add(E_EVENT_ZONE_ADD, _cb_zone_add, NULL)); handlers = eina_list_append (handlers, ecore_event_handler_add(E_EVENT_ZONE_DEL, _cb_zone_del, NULL)); handlers = eina_list_append (handlers, ecore_event_handler_add(E_EVENT_ZONE_MOVE_RESIZE, _cb_zone_move_resize, NULL)); for (i = 0; i < 3; i++) multi_device[i] = -1; } static void _events_shutdown(void) { E_FREE_LIST(handlers, ecore_event_handler_del); } static Eina_Bool _cb_property_change(void *data __UNUSED__, int type __UNUSED__, void *ev) { E_Border *bd; Ecore_X_Event_Window_Property *event = ev; if (event->atom == ECORE_X_ATOM_NET_ACTIVE_WINDOW) { bd = e_border_focused_get(); if (bd) target_win = bd->client.win; } return ECORE_CALLBACK_PASS_ON; } static Eina_Bool _cb_client_message(void *data __UNUSED__, int type __UNUSED__, void *ev) { int block; Ecore_X_Event_Client_Message *event = ev; if (event->message_type != _atom_access) return ECORE_CALLBACK_PASS_ON; block = e_config_save_block_get(); if (block) e_config_save_block_set(!block); if ((Eina_Bool)event->data.l[0]) { EINA_LOG_INFO("[access module] module enable"); _covers_init(); _events_init(); access_config->window = EINA_TRUE; } else { EINA_LOG_INFO("[access module] module disable"); _covers_shutdown(); _events_shutdown(); access_config->window = EINA_FALSE; } /* save config value */ e_config_domain_save("module.access", conf_edd, access_config); e_config_save_block_set(block); return ECORE_CALLBACK_PASS_ON; } /***************************************************************************/ /* module setup */ EAPI E_Module_Api e_modapi = { E_MODULE_API_VERSION, "Access" }; EAPI void * e_modapi_init(E_Module *m) { if (!_atom_access) _atom_access = ecore_x_atom_get("_E_MOD_ACC_SCR_READER_"); ecore_x_event_mask_set(ecore_x_window_root_first_get(), ECORE_X_EVENT_MASK_WINDOW_CONFIGURE); client_message_handler = ecore_event_handler_add (ECORE_X_EVENT_CLIENT_MESSAGE, _cb_client_message, NULL); ecore_x_event_mask_set(ecore_x_window_root_first_get(), ECORE_X_EVENT_MASK_WINDOW_PROPERTY); property_handler = ecore_event_handler_add (ECORE_X_EVENT_WINDOW_PROPERTY, _cb_property_change, NULL); /* load config value */ conf_edd = E_CONFIG_DD_NEW("Access_Config", Config); E_CONFIG_VAL(conf_edd, Config, window, UCHAR); access_config = e_config_domain_load("module.access", conf_edd); if (!access_config) { access_config = E_NEW(Config, 1); access_config->window = EINA_FALSE; return m; } if (access_config->window) { _covers_shutdown(); _covers_init(); _events_init(); } else { _covers_shutdown(); _events_shutdown(); } return m; } EAPI int e_modapi_shutdown(E_Module *m __UNUSED__) { EINA_LOG_INFO("[access module] module shutdown"); if (client_message_handler) ecore_event_handler_del(client_message_handler); if (property_handler) ecore_event_handler_del(property_handler); _covers_shutdown(); _events_shutdown(); return 1; } EAPI int e_modapi_save(E_Module *m __UNUSED__) { e_config_domain_save("module.access", conf_edd, access_config); return 1; } #endif