From e0c40abb40f05efe32e263e8a59e923281b559dc Mon Sep 17 00:00:00 2001 From: Marcel Hollerbach Date: Sun, 12 Jan 2020 17:38:05 +0100 Subject: [PATCH] ecore_evas: introduce initial selection & dnd support for x. Seats are not implemented, if there is a type mismatch promises are going to be rejected. Most of this code is copied over from selection_manager. Reviewed-by: Mike Blumenkrantz Differential Revision: https://phab.enlightenment.org/D11195 --- .../ecore_evas/engines/x/ecore_evas_x.c | 756 +++++++++++++++++- 1 file changed, 753 insertions(+), 3 deletions(-) diff --git a/src/modules/ecore_evas/engines/x/ecore_evas_x.c b/src/modules/ecore_evas/engines/x/ecore_evas_x.c index fb6eaa63f1..d689a5622c 100644 --- a/src/modules/ecore_evas/engines/x/ecore_evas_x.c +++ b/src/modules/ecore_evas/engines/x/ecore_evas_x.c @@ -73,6 +73,16 @@ static Eina_Bool wm_exists; typedef struct _Ecore_Evas_Engine_Data_X11 Ecore_Evas_Engine_Data_X11; +typedef struct { + Ecore_Evas_Selection_Callbacks callbacks; + Ecore_Evas_Selection_Buffer buffer; + Ecore_Evas *ee; + Eina_Promise *delivery; + Eina_Array *acceptable_type; + Eina_Stringshare *requested_type; + Eina_Stringshare *later_conversion; +} Ecore_Evas_X11_Selection_Data; + struct _Ecore_Evas_Engine_Data_X11 { Ecore_X_Window win_root; Eina_List *win_extra; @@ -128,6 +138,11 @@ struct _Ecore_Evas_Engine_Data_X11 { void *visual; // store visual used to create pixmap unsigned long colormap; // store colormap used to create pixmap } pixmap; + Ecore_Evas_X11_Selection_Data selection_data[ECORE_EVAS_SELECTION_BUFFER_LAST]; + Eina_Array *xserver_atom_name_during_dnd; + Ecore_Event_Handler *mouse_up_handler; + Ecore_Job *init_job; + int skip_clean_event; Eina_Bool destroyed : 1; // X window has been deleted and cannot be used Eina_Bool fully_obscured : 1; // X window is fully obscured Eina_Bool configured : 1; // X window has been configured @@ -151,6 +166,8 @@ static void _alpha_do(Ecore_Evas *, int); static void _transparent_do(Ecore_Evas *, int); static void _avoid_damage_do(Ecore_Evas *, int); static void _rotation_do(Ecore_Evas *, int, int); +static void _ecore_evas_x_selection_init(void); +static void _ecore_evas_x_selection_window_init(Ecore_Evas *ee); #define SWAP_INT(a, b) do { a ^= b; b ^= a; a ^= b; } while (0) @@ -1970,6 +1987,7 @@ _ecore_evas_x_init(void) ecore_event_handler_add(ECORE_X_EVENT_WINDOW_CREATE, _ecore_evas_x_event_window_create, NULL); ecore_event_evas_init(); + _ecore_evas_x_selection_init(); return _ecore_evas_init_count; } @@ -1997,6 +2015,7 @@ _ecore_evas_x_free(Ecore_Evas *ee) { Ecore_Evas_Engine_Data_X11 *edata = ee->engine.data; + ecore_job_del(edata->init_job); if (edata->pixmap.back) ecore_x_pixmap_free(edata->pixmap.back); if (edata->pixmap.front) @@ -2696,6 +2715,7 @@ _alpha_do(Ecore_Evas *ee, int alpha) _ecore_evas_x_aux_hints_supported_update(ee); _ecore_evas_x_aux_hints_update(ee); _ecore_evas_x_size_pos_hints_update(ee); + _ecore_evas_x_selection_window_init(ee); #endif /* BUILD_ECORE_EVAS_SOFTWARE_X11 */ if ((id = getenv("DESKTOP_STARTUP_ID"))) { @@ -2850,6 +2870,7 @@ _ecore_evas_x_alpha_set(Ecore_Evas *ee, int alpha) _ecore_evas_x_aux_hints_supported_update(ee); _ecore_evas_x_aux_hints_update(ee); _ecore_evas_x_size_pos_hints_update(ee); + _ecore_evas_x_selection_window_init(ee); #endif /* BUILD_ECORE_EVAS_OPENGL_X11 */ if ((id = getenv("DESKTOP_STARTUP_ID"))) { @@ -3667,6 +3688,731 @@ _ecore_evas_x_aux_hints_set(Ecore_Evas *ee, const char *hints) (ee->prop.window, ECORE_X_ATOM_E_WINDOW_AUX_HINT); } +static Ecore_X_Atom ecore_evas_selection_to_atom[] = {0, 0, 0, 0}; +static Ecore_Event_Handler *ecore_evas_selection_handlers[8]; + +static inline Ecore_Evas_Selection_Buffer +_atom_to_selection(Ecore_X_Atom atom) +{ + for (int i = 0; i < ECORE_EVAS_SELECTION_BUFFER_LAST; ++i) + { + if (ecore_evas_selection_to_atom[i] == atom) + return i; + } + return ECORE_EVAS_SELECTION_BUFFER_LAST; +} + +static Eina_Stringshare* +_decrypt_type(const char *target) +{ + // reference https://tronche.com/gui/x/icccm/sec-2.html + if (eina_streq(target, "TEXT")) return eina_stringshare_add("text/plain"); + //FIXME no support in eina_content for that so far + if (eina_streq(target, "COMPOUND_TEXT")) return eina_stringshare_add("text/plain"); + // reference https://tronche.com/gui/x/icccm/sec-2.html + if (eina_streq(target, "STRING")) return eina_stringshare_add("text/plain;charset=iso-8859-1"); + if (eina_streq(target, "UTF8_STRING")) return eina_stringshare_add("text/plain;charset=utf-8"); + + return eina_stringshare_add(target); +} + +static Eina_Stringshare* +_mime_to_xserver_type(const char *target) +{ + // FIXME // reference https://tronche.com/gui/x/icccm/sec-2.html says it is in the owners choice of encoding, not sure what this means directly here + if (eina_streq(target, "text/plain")) return eina_stringshare_add("TEXT"); + // reference https://tronche.com/gui/x/icccm/sec-2.html + if (eina_streq(target, "text/plain;charset=iso-8859-1")) return eina_stringshare_add("STRING"); + if (eina_streq(target, "text/plain;charset=utf-8")) return eina_stringshare_add("UTF8_STRING"); + + return eina_stringshare_add(target); +} + +static inline void +_clear_selection(Ecore_Evas *ee, Ecore_Evas_Selection_Buffer selection) +{ + Ecore_Evas_Engine_Data_X11 *edata = ee->engine.data; + Ecore_Evas_Selection_Callbacks *cbs = &edata->selection_data[selection].callbacks; + + EINA_SAFETY_ON_FALSE_RETURN(cbs->cancel); + + cbs->cancel(ee, 1, selection); + eina_array_free(cbs->available_types); + + cbs->delivery = NULL; + cbs->cancel = NULL; + cbs->available_types = NULL; +} + +static void +_clear_selection_delivery(Ecore_Evas *ee, Ecore_Evas_Selection_Buffer selection) +{ + Ecore_Evas_Engine_Data_X11 *edata = ee->engine.data; + eina_stringshare_replace(&edata->selection_data[selection].requested_type, NULL); + eina_stringshare_replace(&edata->selection_data[selection].later_conversion, NULL); + edata->selection_data[selection].delivery = NULL; + eina_array_free(edata->selection_data[selection].acceptable_type); + edata->selection_data[selection].acceptable_type = NULL; +} + +static void +_ecore_x_selection_request(Ecore_X_Window win, Ecore_Evas_Selection_Buffer selection, const char *type) +{ + if (selection == ECORE_EVAS_SELECTION_BUFFER_SELECTION_BUFFER) + ecore_x_selection_primary_request(win, type); + else if (selection == ECORE_EVAS_SELECTION_BUFFER_COPY_AND_PASTE_BUFFER) + ecore_x_selection_clipboard_request(win, type); + else + ecore_x_selection_xdnd_request(win, type); +} + +static void +_search_fitting_type(Ecore_Evas *ee, Ecore_Evas_Engine_Data_X11 *edata, Ecore_Evas_Selection_Buffer selection, Eina_Array *arr) +{ + Eina_Stringshare *mime_type; + Eina_Bool found_conversion = EINA_FALSE; + +#define HANDLE_TYPE() \ + { \ + edata->selection_data[selection].requested_type = eina_stringshare_add(x11_name); \ + edata->selection_data[selection].later_conversion = eina_stringshare_add(acceptable_type);\ + found_conversion = EINA_TRUE; \ + break; \ + } + + EINA_SAFETY_ON_NULL_RETURN(edata->selection_data[selection].acceptable_type); + + for (unsigned int i = 0; i < eina_array_count(arr) && !found_conversion; ++i) + { + const char *x11_name = eina_array_data_get(arr, i); + mime_type = _decrypt_type(x11_name); + + for (unsigned int j = 0; j < eina_array_count(edata->selection_data[selection].acceptable_type) && !found_conversion; ++j) + { + const char *acceptable_type = (const char*) eina_array_data_get(edata->selection_data[selection].acceptable_type, j); + + if (mime_type == acceptable_type) + HANDLE_TYPE() + + //if there is no available type yet, check if we can convert to the desiared type via this type + if (!found_conversion) + { + const char *convertion_type = NULL; + Eina_Iterator *iter = eina_content_converter_possible_conversions(mime_type); + EINA_ITERATOR_FOREACH(iter, convertion_type) + { + if (convertion_type == acceptable_type) + HANDLE_TYPE() + } + eina_iterator_free(iter); + } + } + eina_stringshare_del(mime_type); + } + if (found_conversion) + { + _ecore_x_selection_request(ee->prop.window, selection, edata->selection_data[selection].requested_type); + } + else + { + eina_promise_resolve(edata->selection_data[selection].delivery, eina_value_error_init(ecore_evas_no_matching_type)); + _clear_selection_delivery(ee, selection); + } + +#undef HANDLE_TYPE +} + +static void +_search_fitting_type_from_event(Ecore_Evas *ee, Ecore_Evas_Engine_Data_X11 *edata, Ecore_Evas_Selection_Buffer selection, Ecore_X_Event_Selection_Notify *ev) +{ + Ecore_X_Atom *available_atoms; + Ecore_X_Selection_Data_Targets *targets; + Eina_Array *tmp = eina_array_new(10); + + targets = ev->data; + available_atoms = (Ecore_X_Atom *)targets->data.data; + for (int i = 0; i < targets->data.length; ++i) + { + Ecore_X_Atom atom = available_atoms[i]; + eina_array_push(tmp, ecore_x_atom_name_get(atom)); + } + _search_fitting_type(ee, edata, selection, tmp); + eina_array_free(tmp); +} + +static void +_deliver_content(Ecore_Evas *ee, Ecore_Evas_Engine_Data_X11 *edata, Ecore_Evas_Selection_Buffer selection, Ecore_X_Event_Selection_Notify *ev) +{ + Ecore_X_Selection_Data *x11_data = ev->data; + Eina_Rw_Slice data; + Eina_Content *result; + Eina_Stringshare *mime_type = _decrypt_type(edata->selection_data[selection].requested_type); + + if (!strncmp(mime_type, "text", strlen("text"))) + { + //ensure that we always have a \0 at the end, there is no assertion that \0 is included here. + data.len = x11_data->length + 1; + data.mem = eina_memdup(x11_data->data, x11_data->length, EINA_TRUE); + } + else + { + data.len = x11_data->length; + data.mem = x11_data->data; + } + + result = eina_content_new(eina_rw_slice_slice_get(data), mime_type); + + //ensure that we deliver the correct type, we might have choosen a convertion before + if (edata->selection_data[selection].later_conversion != mime_type) + { + Eina_Content *tmp = eina_content_convert(result, edata->selection_data[selection].later_conversion); + eina_content_free(result); + result = tmp; + } + + eina_promise_resolve(edata->selection_data[selection].delivery, eina_value_content_init(result)); + eina_content_free(result); + _clear_selection_delivery(ee, selection); + + if (selection == ECORE_EVAS_SELECTION_BUFFER_DRAG_AND_DROP_BUFFER) + ecore_x_dnd_send_finished(); +} + +static Eina_Bool +_ecore_evas_x_selection_notify(void *udata EINA_UNUSED, int type EINA_UNUSED, void *event) +{ + Ecore_X_Event_Selection_Notify *ev = event; + Ecore_Evas_Selection_Buffer selection; + Ecore_Evas_Engine_Data_X11 *edata; + Ecore_Evas *ee; + + ee = ecore_event_window_match(ev->win); + selection = _atom_to_selection(ev->atom); + EINA_SAFETY_ON_FALSE_GOTO(!!ee, end); + EINA_SAFETY_ON_FALSE_GOTO(selection != ECORE_EVAS_SELECTION_BUFFER_LAST, end); + edata = ee->engine.data; + + //if dnd drops above us, and even if we did not request anything, we are getting notified, refuse to do anything + if (selection == ECORE_EVAS_SELECTION_BUFFER_DRAG_AND_DROP_BUFFER && + !edata->selection_data[selection].later_conversion) + { + ecore_x_dnd_send_finished(); + } + else + { + if (eina_streq(ev->target, "TARGETS") || eina_streq(ev->target, "ATOMS")) + { + //This will decide for a type, and will sent that via _ecore_x_selection_request + EINA_SAFETY_ON_FALSE_RETURN_VAL(!edata->selection_data[selection].later_conversion, EINA_FALSE); + EINA_SAFETY_ON_FALSE_RETURN_VAL(!edata->selection_data[selection].requested_type, EINA_FALSE); + _search_fitting_type_from_event(ee, edata, selection, ev); + } + else + { + //This will read the data, fill it into a Eina_Content apply all conversions required. + EINA_SAFETY_ON_FALSE_RETURN_VAL(edata->selection_data[selection].later_conversion, EINA_FALSE); + EINA_SAFETY_ON_FALSE_RETURN_VAL(edata->selection_data[selection].requested_type, EINA_FALSE); + _deliver_content(ee, edata, selection, ev); + } + } +end: + return ECORE_CALLBACK_PASS_ON; +} + +static Eina_Bool +_ecore_evas_x_selection_clear(void *udata EINA_UNUSED, int type EINA_UNUSED, void *event) +{ + Ecore_X_Event_Selection_Clear *ev = event; + Ecore_Evas_Selection_Callbacks *cbs; + Ecore_Evas_Engine_Data_X11 *edata; + Ecore_Evas_Selection_Buffer selection; + Ecore_Evas *ee; + + ee = ecore_event_window_match(ev->win); + selection = _atom_to_selection(ev->atom); + EINA_SAFETY_ON_FALSE_GOTO(ee, end); + EINA_SAFETY_ON_FALSE_GOTO(selection != ECORE_EVAS_SELECTION_BUFFER_LAST, end); + edata = ee->engine.data; + cbs = &edata->selection_data[selection].callbacks; + + //skip clean event + if (edata->skip_clean_event) + { + edata->skip_clean_event --; + goto end; + } + + if (cbs->cancel) + _clear_selection(ee, selection); +end: + return ECORE_CALLBACK_PASS_ON; +} + +static void +_force_stop_self_dnd(Ecore_Evas *ee) +{ + Ecore_Evas_Engine_Data_X11 *edata; + + EINA_SAFETY_ON_NULL_RETURN(ee); + edata = ee->engine.data; + EINA_SAFETY_ON_NULL_RETURN(edata); + + //Never clear the buffer for selection here. + //Selection buffer is freed as a response to the FINISHED event. + ecore_x_pointer_ungrab(); + ecore_x_dnd_self_drop(); + ecore_x_dnd_aware_set(ee->prop.window, EINA_FALSE); + ecore_event_handler_del(edata->mouse_up_handler); + edata->mouse_up_handler = NULL; + + if (ee->drag.free) + ee->drag.free(ee, 1, ee->drag.data, ee->drag.accepted); + ee->drag.free = NULL; +} + +static Eina_Bool +_ecore_evas_x_selection_fixes_notify(void *udata EINA_UNUSED, int type EINA_UNUSED, void *event) +{ + Ecore_X_Event_Fixes_Selection_Notify *ev = event; + Ecore_Evas *ee; + Ecore_Evas_Selection_Buffer selection; + + ee = ecore_event_window_match(ev->win); + selection = _atom_to_selection(ev->atom); + EINA_SAFETY_ON_FALSE_GOTO(!!ee, end); + EINA_SAFETY_ON_FALSE_GOTO(selection != ECORE_EVAS_SELECTION_BUFFER_LAST, end); + + //notify that the selection has changed on this ecore evas + if (ee->func.fn_selection_changed) + ee->func.fn_selection_changed(ee, 0, selection); +end: + return ECORE_CALLBACK_PASS_ON; +} + +static Eina_Bool +_eina_content_converter(char *target, void *data, int size EINA_UNUSED, void **data_ret, int *size_ret, Ecore_X_Atom *ttype, int *typesize) +{ + Ecore_Evas_X11_Selection_Data *sdata = data; + Eina_Bool ret = EINA_FALSE;; + if (eina_streq(target, "TARGETS") || eina_streq(target, "ATOM")) + { + //list all available types that we have currently + Ecore_X_Atom *result = calloc(eina_array_count(sdata->callbacks.available_types), sizeof(Ecore_X_Atom)); + for (unsigned int i = 0; i < eina_array_count(sdata->callbacks.available_types); ++i) + { + result[i] = ecore_x_atom_get(eina_array_data_get(sdata->callbacks.available_types, i)); + } + *size_ret = eina_array_count(sdata->callbacks.available_types); + *data_ret = result; + *ttype = ECORE_X_ATOM_ATOM; + *typesize = 32; /* urk */ + ret = EINA_TRUE; + } + else + { + const char *mime_type = _decrypt_type(target); + for (unsigned int i = 0; i < eina_array_count(sdata->callbacks.available_types); ++i) + { + if (mime_type == eina_array_data_get(sdata->callbacks.available_types, i)) + { + Eina_Rw_Slice slice; + sdata->callbacks.delivery(sdata->ee, 1, sdata->buffer, mime_type, &slice); + *size_ret = slice.len; + *data_ret = slice.mem; + *ttype = ecore_x_atom_get(target); //use here target in order to get the correct atom + //FIXME in selection manager we never set here the typesize, isn't that weird ? + ret = EINA_TRUE; + break; + } + } + eina_stringshare_del(mime_type); + } + + return ret; +} + +static Eina_Bool +_ecore_evas_x_dnd_enter(void *udata EINA_UNUSED, int type EINA_UNUSED, void *event) +{ + Ecore_X_Event_Xdnd_Enter *enter = event; + Eina_Array *mime_tmp; + Ecore_Evas_Engine_Data_X11 *edata; + Ecore_Evas *ee; + + mime_tmp = eina_array_new(10); + ee = ecore_event_window_match(enter->win); + EINA_SAFETY_ON_NULL_GOTO(ee, end); + edata = ee->engine.data; + edata->xserver_atom_name_during_dnd = eina_array_new(10); + for (int i = 0; i < enter->num_types; ++i) + { + const char *mime_type = _decrypt_type(enter->types[i]); + eina_array_push(mime_tmp, mime_type); + eina_array_push(edata->xserver_atom_name_during_dnd, eina_stringshare_add(enter->types[i])); + } + ecore_evas_dnd_enter(ee, 1, eina_array_iterator_new(mime_tmp), EINA_POSITION2D(0,0)); //FIXME + eina_array_free(mime_tmp); + +end: + return ECORE_CALLBACK_PASS_ON; +} + +static Eina_Bool +_ecore_evas_x_dnd_leave(void *udata EINA_UNUSED, int type EINA_UNUSED, void *event) +{ + Ecore_X_Event_Xdnd_Leave *leave = event; + Ecore_Evas_Engine_Data_X11 *edata; + Ecore_Evas *ee; + + ee = ecore_event_window_match(leave->win); + EINA_SAFETY_ON_NULL_GOTO(ee, end); + edata = ee->engine.data; + ecore_evas_dnd_leave(ee, 1, EINA_POSITION2D(0,0)); + for (unsigned int i = 0; i < eina_array_count(edata->xserver_atom_name_during_dnd); ++i) + { + eina_stringshare_del(eina_array_data_get(edata->xserver_atom_name_during_dnd, i)); + } + eina_array_free(edata->xserver_atom_name_during_dnd); + edata->xserver_atom_name_during_dnd = NULL; +end: + return ECORE_CALLBACK_PASS_ON; +} + +static Ecore_X_Atom +_x11_dnd_action_rev_map(const char* action) +{ + if (eina_streq(action, "copy")) return ECORE_X_ATOM_XDND_ACTION_COPY; + if (eina_streq(action, "move")) return ECORE_X_ATOM_XDND_ACTION_MOVE; + else if (eina_streq(action, "privat")) return ECORE_X_ATOM_XDND_ACTION_PRIVATE; + else if (eina_streq(action, "ask")) return ECORE_X_ATOM_XDND_ACTION_ASK; + else if (eina_streq(action, "list")) return ECORE_X_ATOM_XDND_ACTION_LIST; + else if (eina_streq(action, "link")) return ECORE_X_ATOM_XDND_ACTION_LINK; + else if (eina_streq(action, "description")) return ECORE_X_ATOM_XDND_ACTION_DESCRIPTION; + return 0; +} + +static const char* +_x11_dnd_action_map(Ecore_X_Atom action) +{ + if (action == ECORE_X_DND_ACTION_COPY) return "copy"; + if (action == ECORE_X_ATOM_XDND_ACTION_MOVE) return "move"; + if (action == ECORE_X_ATOM_XDND_ACTION_PRIVATE) return "privat"; + if (action == ECORE_X_ATOM_XDND_ACTION_ASK) return "ask"; + if (action == ECORE_X_ATOM_XDND_ACTION_LIST) return "list"; + if (action == ECORE_X_ATOM_XDND_ACTION_LINK) return "link"; + if (action == ECORE_X_ATOM_XDND_ACTION_DESCRIPTION) return "description"; + + return "unknown"; +} + +static Eina_Bool +_ecore_evas_x_dnd_position(void *udata EINA_UNUSED, int type EINA_UNUSED, void *event) +{ + Ecore_X_Event_Xdnd_Position *pos = event; + int x, y, w, h; + Ecore_Evas *ee; + + ee = ecore_event_window_match(pos->win); + EINA_SAFETY_ON_NULL_GOTO(ee, end); + ecore_evas_geometry_get(ee, &x, &y, &w, &h); + ecore_evas_dnd_position_set(ee, 1, EINA_POSITION2D(pos->position.x - x, pos->position.y - y)); + ecore_x_dnd_send_status(EINA_TRUE, EINA_FALSE, (Ecore_X_Rectangle){x,y,w,h}, pos->action); +end: + return ECORE_CALLBACK_PASS_ON; +} + +static Eina_Bool +_ecore_evas_x_dnd_drop(void *udata EINA_UNUSED, int type EINA_UNUSED, void *event) +{ + Ecore_X_Event_Xdnd_Drop *drop = event; + Ecore_Evas_Engine_Data_X11 *edata; + Ecore_Evas *ee; + + ee = ecore_event_window_match(drop->win); + EINA_SAFETY_ON_NULL_GOTO(ee, end); + edata = ee->engine.data; + if (ee->func.fn_dnd_drop) + ee->func.fn_dnd_drop(ee, 1, ecore_evas_dnd_pos_get(ee, 1), _x11_dnd_action_map(drop->action)); + if (!edata->selection_data[ECORE_EVAS_SELECTION_BUFFER_DRAG_AND_DROP_BUFFER].requested_type) + { + ecore_x_dnd_send_finished(); + } + ecore_evas_dnd_leave(ee, 1, EINA_POSITION2D(drop->position.x ,drop->position.y)); + eina_array_free(edata->xserver_atom_name_during_dnd); + edata->xserver_atom_name_during_dnd = NULL; +end: + return ECORE_CALLBACK_PASS_ON; +} + +static Eina_Bool +_ecore_evas_x_finished(void *udata EINA_UNUSED, int type EINA_UNUSED, void *event) +{ + Ecore_X_Event_Xdnd_Finished *finished = event; + Ecore_Evas *ee; + + ee = ecore_event_window_match(finished->win); + EINA_SAFETY_ON_NULL_GOTO(ee, end); + + _clear_selection(ee, ECORE_EVAS_SELECTION_BUFFER_DRAG_AND_DROP_BUFFER); +end: + return ECORE_CALLBACK_PASS_ON; +} + +static void +_ecore_evas_x_selection_init(void) +{ + Ecore_X_Atom _ecore_evas_selection_to_atom[] = {ECORE_X_ATOM_SELECTION_PRIMARY, ECORE_X_ATOM_SELECTION_CLIPBOARD, ECORE_X_ATOM_SELECTION_XDND}; + + for (int i = 0; i < ECORE_EVAS_SELECTION_BUFFER_LAST; ++i) + { + ecore_evas_selection_to_atom[i] = _ecore_evas_selection_to_atom[i]; + } + + ecore_evas_selection_handlers[0] = + ecore_event_handler_add(ECORE_X_EVENT_SELECTION_NOTIFY, + _ecore_evas_x_selection_notify, NULL); + ecore_evas_selection_handlers[1] = + ecore_event_handler_add(ECORE_X_EVENT_SELECTION_CLEAR, + _ecore_evas_x_selection_clear, NULL); + if (ECORE_X_EVENT_FIXES_SELECTION_NOTIFY) + ecore_evas_selection_handlers[2] = + ecore_event_handler_add(ECORE_X_EVENT_FIXES_SELECTION_NOTIFY, + _ecore_evas_x_selection_fixes_notify, NULL); + + ecore_evas_selection_handlers[3] = ecore_event_handler_add(ECORE_X_EVENT_XDND_ENTER, + _ecore_evas_x_dnd_enter, NULL); + ecore_evas_selection_handlers[4] = ecore_event_handler_add(ECORE_X_EVENT_XDND_LEAVE, + _ecore_evas_x_dnd_leave, NULL); + ecore_evas_selection_handlers[5] = ecore_event_handler_add(ECORE_X_EVENT_XDND_POSITION, + _ecore_evas_x_dnd_position, NULL); + ecore_evas_selection_handlers[6] = ecore_event_handler_add(ECORE_X_EVENT_XDND_DROP, + _ecore_evas_x_dnd_drop, NULL); + ecore_evas_selection_handlers[7] = ecore_event_handler_add(ECORE_X_EVENT_XDND_FINISHED, + _ecore_evas_x_finished, NULL); + /* for us known type */ + char *supported_types[] = { + "text/plain", + "text/plain;charset=utf-8", + "image/png", + "image/jpeg", + "image/x-ms-bmp", + "image/gif", + "image/tiff", + "image/svg+xml", + "image/x-xpixmap", + "image/x-tga", + "image/x-portable-pixmap", + "TEXT", + "COMPOUND_TEXT", + "STRING", + "UTF8_STRING", + "text/x-vcard", + "text/uri-list", + "application/x-elementary-markup", + "ATOM", + "TARGETS", + NULL + }; + for (int i = 0; supported_types[i]; ++i) + { + ecore_x_selection_converter_add(supported_types[i], _eina_content_converter); + } +} + +static Eina_Bool +_ecore_evas_x_selection_has_owner(Ecore_Evas *ee EINA_UNUSED, unsigned int seat EINA_UNUSED, Ecore_Evas_Selection_Buffer selection) +{ + return !!ecore_x_selection_owner_get(ecore_evas_selection_to_atom[selection]); +} + +static void +_deliver_selection_changed(void *data) +{ + Ecore_Evas *ee = data; + Ecore_Evas_Engine_Data_X11 *edata = ee->engine.data; + + if (!ee->func.fn_selection_changed) + goto end; + + if (_ecore_evas_x_selection_has_owner(ee, 1, ECORE_EVAS_SELECTION_BUFFER_SELECTION_BUFFER)) + ee->func.fn_selection_changed(ee, 1, ECORE_EVAS_SELECTION_BUFFER_SELECTION_BUFFER); + if (_ecore_evas_x_selection_has_owner(ee, 1, ECORE_EVAS_SELECTION_BUFFER_COPY_AND_PASTE_BUFFER)) + ee->func.fn_selection_changed(ee, 1, ECORE_EVAS_SELECTION_BUFFER_COPY_AND_PASTE_BUFFER); + if (_ecore_evas_x_selection_has_owner(ee, 1, ECORE_EVAS_SELECTION_BUFFER_DRAG_AND_DROP_BUFFER)) + ee->func.fn_selection_changed(ee, 1, ECORE_EVAS_SELECTION_BUFFER_DRAG_AND_DROP_BUFFER); +end: + edata->init_job = NULL; +} + +static void +_ecore_evas_x_selection_window_init(Ecore_Evas *ee) +{ + Ecore_Evas_Engine_Data_X11 *edata = ee->engine.data; + for (int i = 0; i < ECORE_EVAS_SELECTION_BUFFER_LAST; ++i) + { + ecore_x_fixes_window_selection_notification_request(ee->prop.window, ecore_evas_selection_to_atom[i]); + edata->selection_data[i].ee = ee; + edata->selection_data[i].buffer = i; + } + ecore_x_dnd_aware_set(ee->prop.window, EINA_TRUE); + edata->init_job = ecore_job_add(_deliver_selection_changed, ee); +} + +static void +_store_selection_cbs(Ecore_Evas *ee, Ecore_Evas_Selection_Buffer selection, Eina_Array *available_types, Eina_Bool (*delivery)(Ecore_Evas *ee, unsigned int seat, Ecore_Evas_Selection_Buffer buffer, const char *type, Eina_Rw_Slice *slice), void (*cancel)(Ecore_Evas *ee, unsigned int seat, Ecore_Evas_Selection_Buffer buffer)) +{ + Ecore_Evas_X11_Selection_Data *sdata; + Ecore_Evas_Engine_Data_X11 *edata; + Ecore_Evas_Selection_Callbacks *cbs; + + edata = ee->engine.data; + sdata = &edata->selection_data[selection]; + cbs = &sdata->callbacks; + + if (cbs->cancel) + { + _clear_selection(ee, selection); + edata->skip_clean_event ++; //we are going to overwrite our own selection, this will emit a clean event, but we already freed it. + } + + cbs->delivery = delivery; + cbs->cancel = cancel; + cbs->available_types = available_types; +} + +static Eina_Bool +_ecore_evas_x_selection_claim(Ecore_Evas *ee, unsigned int seat EINA_UNUSED, Ecore_Evas_Selection_Buffer selection, Eina_Array *available_types, Ecore_Evas_Internal_Delivery delivery, Ecore_Evas_Internal_Cancel cancel) +{ + Ecore_Evas_X11_Selection_Data *sdata; + Ecore_Evas_Engine_Data_X11 *edata; + + edata = ee->engine.data; + sdata = &edata->selection_data[selection]; + + _store_selection_cbs(ee, selection, available_types, delivery, cancel); + + if (eina_array_count(available_types) > 0) + { + //the commands below will *copy* the content of sdata, this you have to ensure that clear is called when sdata is changed. + if (selection == ECORE_EVAS_SELECTION_BUFFER_SELECTION_BUFFER) + ecore_x_selection_primary_set(ee->prop.window, sdata, sizeof(Ecore_Evas_X11_Selection_Data)); + else if (selection == ECORE_EVAS_SELECTION_BUFFER_COPY_AND_PASTE_BUFFER) + ecore_x_selection_clipboard_set(ee->prop.window, sdata, sizeof(Ecore_Evas_X11_Selection_Data)); + } + else + { + if (selection == ECORE_EVAS_SELECTION_BUFFER_SELECTION_BUFFER) + ecore_x_selection_primary_clear(); + else if (selection == ECORE_EVAS_SELECTION_BUFFER_COPY_AND_PASTE_BUFFER) + ecore_x_selection_clipboard_clear(); + } + + //for drag and drop, we are not calling anything in here + + return EINA_TRUE; +} + +Eina_Future* +_ecore_evas_x_selection_request(Ecore_Evas *ee EINA_UNUSED, unsigned int seat EINA_UNUSED, Ecore_Evas_Selection_Buffer selection, Eina_Array *acceptable_type) +{ + Ecore_Evas_X11_Selection_Data *sdata; + Ecore_Evas_Engine_Data_X11 *edata; + Eina_Future *future; + + edata = ee->engine.data; + sdata = &edata->selection_data[selection]; + + if (sdata->delivery) + { + eina_promise_reject(sdata->delivery, ecore_evas_request_replaced); + _clear_selection_delivery(ee, selection); + } + sdata->delivery = efl_loop_promise_new(efl_main_loop_get()); + sdata->acceptable_type = acceptable_type; + future = eina_future_new(sdata->delivery); + + if (selection == ECORE_EVAS_SELECTION_BUFFER_DRAG_AND_DROP_BUFFER) + { + //when in dnd - we are requesting out of the set that we know from the enter event + EINA_SAFETY_ON_FALSE_RETURN_VAL(!edata->selection_data[selection].later_conversion, NULL); + EINA_SAFETY_ON_FALSE_RETURN_VAL(!edata->selection_data[selection].requested_type, NULL); + _search_fitting_type(ee, edata, selection, edata->xserver_atom_name_during_dnd); + } + else + { + //when not dnd - we are first wanting to know what is available + _ecore_x_selection_request(ee->prop.window, selection, ECORE_X_SELECTION_TARGET_TARGETS); + } + + return future; +} + +static void +_x11_drag_move(void *data, Ecore_X_Xdnd_Position *pos) +{ + Ecore_Evas *ee = data; + Eina_Rect rect; + + ecore_evas_geometry_get(ee->drag.rep, &rect.x, &rect.y, &rect.w, &rect.h); + + ecore_evas_move(ee->drag.rep, pos->position.x - rect.w / 2, pos->position.y - rect.h/2); +} + +static Eina_Bool +_x11_drag_mouse_up(void *data, int etype EINA_UNUSED, void *event EINA_UNUSED) +{ + Ecore_Evas *ee = data; + + _force_stop_self_dnd(ee); + + return EINA_TRUE; +} + +static Eina_Bool +_ecore_evas_x_dnd_start(Ecore_Evas *ee, unsigned int seat EINA_UNUSED, Eina_Array *available_types, Ecore_Evas *drag_rep, Eina_Bool (*delivery)(Ecore_Evas *ee, unsigned int seat, Ecore_Evas_Selection_Buffer buffer, const char *type, Eina_Rw_Slice *slice), void (*cancel)(Ecore_Evas *ee, unsigned int seat, Ecore_Evas_Selection_Buffer buffer), const char* action) +{ + Ecore_Evas_X11_Selection_Data *sdata; + Ecore_Evas_Engine_Data_X11 *edata; + Ecore_X_Atom actx; + + edata = ee->engine.data; + sdata = &edata->selection_data[ECORE_EVAS_SELECTION_BUFFER_DRAG_AND_DROP_BUFFER]; + _store_selection_cbs(ee, ECORE_EVAS_SELECTION_BUFFER_DRAG_AND_DROP_BUFFER, available_types, delivery, cancel); + + //first set all types we have + ecore_x_dnd_types_set(ee->prop.window, NULL, 0); + for (unsigned int i = 0; i < eina_array_count(available_types); ++i) + { + const char *xserver_mime_type = _mime_to_xserver_type(eina_array_data_get(available_types, i)); + ecore_x_dnd_type_set(ee->prop.window, xserver_mime_type, EINA_TRUE); + eina_stringshare_del(xserver_mime_type); + } + ecore_x_dnd_aware_set(ee->prop.window, EINA_TRUE); + ecore_x_dnd_callback_pos_update_set(_x11_drag_move, ee); + ecore_x_dnd_self_begin(ee->prop.window, (unsigned char*)sdata, sizeof(Ecore_Evas_X11_Selection_Data)); + actx = _x11_dnd_action_rev_map(action); + ecore_x_dnd_source_action_set(actx); + ecore_x_pointer_grab(ee->prop.window); + + ecore_x_window_ignore_set(drag_rep->prop.window, EINA_TRUE); + + if (edata->mouse_up_handler) + ecore_event_handler_del(edata->mouse_up_handler); + edata->mouse_up_handler = ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_UP, + _x11_drag_mouse_up, ee); + + return EINA_TRUE; +} + +static Eina_Bool +_ecore_evas_x_dnd_stop(Ecore_Evas *ee, unsigned int seat EINA_UNUSED) +{ + _force_stop_self_dnd(ee); + _clear_selection(ee, ECORE_EVAS_SELECTION_BUFFER_DRAG_AND_DROP_BUFFER); + ecore_x_selection_xdnd_clear(); //This is needed otherwise a outdated sdata struct will be accessed + return EINA_TRUE; +} + static Ecore_Evas_Engine_Func _ecore_x_engine_func = { _ecore_evas_x_free, @@ -3754,9 +4500,11 @@ static Ecore_Evas_Engine_Func _ecore_x_engine_func = NULL, //fn_pointer_device_xy_get NULL, //fn_prepare NULL, //fn_last_tick_get - NULL, //fn_selection_claim - NULL, //fn_selection_has_owner - NULL, //fn_selection_request + _ecore_evas_x_selection_claim, //fn_selection_claim + _ecore_evas_x_selection_has_owner, //fn_selection_has_owner + _ecore_evas_x_selection_request, //fn_selection_request + _ecore_evas_x_dnd_start, //fn_dnd_start + _ecore_evas_x_dnd_stop, //fn_dnd_stop }; /* @@ -4120,6 +4868,7 @@ ecore_evas_software_x11_new_internal(const char *disp_name, Ecore_X_Window paren _ecore_evas_x_wm_rotation_protocol_set(ee); _ecore_evas_x_aux_hints_supported_update(ee); _ecore_evas_x_aux_hints_update(ee); + _ecore_evas_x_selection_window_init(ee); ee->engine.func->fn_render = _ecore_evas_x_render; ee->draw_block = EINA_TRUE; @@ -4556,6 +5305,7 @@ ecore_evas_gl_x11_options_new_internal(const char *disp_name, Ecore_X_Window par _ecore_evas_x_wm_rotation_protocol_set(ee); _ecore_evas_x_aux_hints_supported_update(ee); _ecore_evas_x_aux_hints_update(ee); + _ecore_evas_x_selection_window_init(ee); ee->draw_block = 1; if (!wm_exists) edata->configured = 1;