diff --git a/meson.build b/meson.build index 372ced629..02dbdede7 100644 --- a/meson.build +++ b/meson.build @@ -271,6 +271,7 @@ if get_option('wayland') == true dir_wayland_protocols = wayland_protocols.get_pkgconfig_variable('pkgdatadir') wayland_version = '>= 1.11.0' dep_wayland = [ dependency('ecore-wl2'), + dependency('efl-wl'), dependency('wayland-server' , version: wayland_version), dependency('wayland-client' , version: wayland_version), wayland_protocols, @@ -286,6 +287,7 @@ if get_option('wayland') == true endif requires_wayland = ' '.join([ 'wayland-protocols >= 1.9', 'ecore-wl2', + 'efl-wl', requires_drm, ' '.join(['wayland-server' , wayland_version]), ' '.join(['wayland-client' , wayland_version]), diff --git a/src/Makefile.mk b/src/Makefile.mk index e4cf2bb8c..a3ebd6224 100644 --- a/src/Makefile.mk +++ b/src/Makefile.mk @@ -7,4 +7,5 @@ src/protocol/session-recovery.xml \ src/protocol/efl-aux-hints.xml \ src/protocol/www.xml \ src/protocol/action_route.xml \ +src/protocol/e-gadget.xml \ src/protocol/xdg-foreign-unstable-v1.xml diff --git a/src/bin/Makefile.mk b/src/bin/Makefile.mk index 4235b821c..cc558ed2d 100644 --- a/src/bin/Makefile.mk +++ b/src/bin/Makefile.mk @@ -1,6 +1,9 @@ DISTCLEANFILES += src/bin/e_fm_shared_types.h -EXTRA_DIST += src/bin/e_drm2.x +EXTRA_DIST += \ +src/bin/e_drm2.x \ +src/bin/e_gadget_runner.c \ +src/bin/e_gadget_loader.c efx_files = \ src/bin/efx/efx_bumpmapping.c \ diff --git a/src/bin/e_gadget.c b/src/bin/e_gadget.c index 1de22e091..bddc1387e 100644 --- a/src/bin/e_gadget.c +++ b/src/bin/e_gadget.c @@ -1,5 +1,11 @@ #include "e.h" + +#ifdef HAVE_WAYLAND +EINTERN void e_gadget_runner_init(void); +EINTERN void e_gadget_runner_shutdown(void); +#endif + #define SNAP_DISTANCE 5 #define E_GADGET_TYPE 0xE31337 @@ -2492,6 +2498,9 @@ e_gadget_init(void) evas_object_event_callback_add(e_comp->canvas->resize_object, EVAS_CALLBACK_RESIZE, _comp_site_resize, comp_site); evas_object_layer_set(comp_site, E_LAYER_DESKTOP); evas_object_resize(comp_site, e_comp->w, e_comp->h); +#ifdef HAVE_WAYLAND + e_gadget_runner_init(); +#endif } EINTERN void @@ -2499,6 +2508,9 @@ e_gadget_shutdown(void) { E_Gadget_Site *zgs; +#ifdef HAVE_WAYLAND + e_gadget_runner_shutdown(); +#endif E_FREE_LIST(handlers, ecore_event_handler_del); E_CONFIG_DD_FREE(edd_gadget); E_CONFIG_DD_FREE(edd_site); diff --git a/src/bin/e_gadget_loader.c b/src/bin/e_gadget_loader.c new file mode 100644 index 000000000..1b32304f5 --- /dev/null +++ b/src/bin/e_gadget_loader.c @@ -0,0 +1,310 @@ +#include "config.h" +#define EFL_BETA_API_SUPPORT +#include +#include +#include +#include "e-gadget-client-protocol.h" +#include "action_route-client-protocol.h" +#include + +static Ecore_Event_Handler *handler; + +static Eina_Hash *wins; +static Eina_Hash *gadget_globals; +static Eina_Hash *ar_globals; +static Eina_Hash *display_actions; + +typedef struct Gadget_Action +{ + Ecore_Wl2_Display *d; + Eina_Stringshare *action; + char handle[37]; + Eina_List *requestors; + struct action_route_bind *ar_bind; +} Gadget_Action; + +static inline Ecore_Wl2_Display * +win_display_get(Evas_Object *win) +{ + Ecore_Wl2_Window *ww; + ww = elm_win_wl_window_get(win); + return ecore_wl2_window_display_get(ww); +} + +static void +wins_emit(Ecore_Wl2_Display *d, const char *sig, uint32_t val) +{ + Eina_List *l, *ll; + Evas_Object *win; + + l = eina_hash_find(wins, &d); + EINA_LIST_FOREACH(l, ll, win) + evas_object_smart_callback_call(win, sig, (uintptr_t*)(uintptr_t)val); +} + +static void +_gadget_anchor(void *data, struct e_gadget *e_gadget EINA_UNUSED, uint32_t anchor) +{ + wins_emit(data, "gadget_site_anchor", anchor); +} + +static void +_gadget_orient(void *data, struct e_gadget *e_gadget EINA_UNUSED, uint32_t orient) +{ + wins_emit(data, "gadget_site_orient", orient); +} + +static void +_gadget_gravity(void *data, struct e_gadget *e_gadget EINA_UNUSED, uint32_t gravity) +{ + wins_emit(data, "gadget_site_gravity", gravity); +} + +static const struct e_gadget_listener _gadget_listener = +{ + _gadget_anchor, + _gadget_orient, + _gadget_gravity, +}; + +static void +_gadget_global_bind(Ecore_Wl2_Display *d, uint32_t id) +{ + struct e_gadget *gadget_global = wl_registry_bind(ecore_wl2_display_registry_get(d), id, &e_gadget_interface, 1); + e_gadget_add_listener(gadget_global, &_gadget_listener, d); + eina_hash_add(gadget_globals, &d, gadget_global); +} + +static void +_ar_global_bind(Ecore_Wl2_Display *d, uint32_t id) +{ + struct action_route *ar_global = wl_registry_bind(ecore_wl2_display_registry_get(d), id, &action_route_interface, 1); + eina_hash_add(ar_globals, &d, ar_global); +} + +static Eina_Bool +_global_added(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Wl2_Event_Global *ev) +{ + if (eina_streq(ev->interface, "e_gadget")) + _gadget_global_bind(ev->display, ev->id); + else if (eina_streq(ev->interface, "action_route")) + _ar_global_bind(ev->display, ev->id); + return ECORE_CALLBACK_RENEW; +} + +static void +_gadget_init(Ecore_Wl2_Display *d) +{ + Eina_Iterator *it; + Ecore_Wl2_Global *g; + + if (wins) + { + if (eina_hash_find(gadget_globals, &d)) return; + } + else + { + gadget_globals = eina_hash_pointer_new(NULL); + ar_globals = eina_hash_pointer_new(NULL); + } + it = ecore_wl2_display_globals_get(d); + EINA_ITERATOR_FOREACH(it, g) + { + if (eina_streq(g->interface, "e_gadget")) + _gadget_global_bind(d, g->id); + else if (eina_streq(g->interface, "action_route")) + _ar_global_bind(d, g->id); + } + eina_iterator_free(it); + if (!handler) + handler = ecore_event_handler_add(ECORE_WL2_EVENT_GLOBAL_ADDED, (Ecore_Event_Handler_Cb)_global_added, NULL); +} + +static void +_ar_bind_activate(void *data, Evas_Object *obj EINA_UNUSED, void *event_info) +{ + const char *params = event_info; + Gadget_Action *ga = data; + + if (params && (!params[0])) params = NULL; + action_route_bind_activate(ga->ar_bind, params); +} + +static void +_ar_bind_del(Gadget_Action *ga) +{ + Evas_Object *r; + eina_stringshare_del(ga->action); + EINA_LIST_FREE(ga->requestors, r) + evas_object_smart_callback_del_full(r, ga->handle, _ar_bind_activate, ga); + free(ga); +} + +static void +_ar_bind_end(void *data, struct action_route_bind *action_route_bind EINA_UNUSED) +{ + Gadget_Action *ga = data; + Eina_List *l; + Evas_Object *r; + + EINA_LIST_FOREACH(ga->requestors, l, r) + evas_object_smart_callback_call(r, "gadget_action_end", ga->handle); +} + +static void +_ar_bind_status(void *data, struct action_route_bind *action_route_bind, uint32_t state) +{ + uuid_t u; + Gadget_Action *ga = data; + Evas_Object *r; + + if (state == ACTION_ROUTE_BIND_STATE_REJECTED) + { + Eina_Hash *h; + Eina_List *l; + h = eina_hash_find(display_actions, &ga->d); + EINA_LIST_FOREACH(ga->requestors, l, r) + { + if (ga->handle[0]) + evas_object_smart_callback_call(r, "gadget_action_deleted", ga->handle); + else + evas_object_smart_callback_call(r, "gadget_action", NULL); + } + eina_hash_del_by_key(h, ga->action); + return; + } + uuid_generate(u); + uuid_unparse_lower(u, ga->handle); + ga->ar_bind = action_route_bind; + r = eina_list_data_get(ga->requestors); + evas_object_smart_callback_add(r, ga->handle, _ar_bind_activate, ga); + evas_object_smart_callback_call(r, "gadget_action", ga->handle); +} + +static const struct action_route_bind_listener _ar_bind_interface = +{ + _ar_bind_status, + _ar_bind_end +}; + +static void +uriopen_request(void *data, Evas_Object *obj EINA_UNUSED, void *event_info) +{ + Ecore_Wl2_Display *d = data; + const char *uri = event_info; + struct e_gadget *gadget_global = eina_hash_find(gadget_globals, &d); + + e_gadget_open_uri(gadget_global, uri); +} + +static void +action_request(void *data, Evas_Object *obj, void *event_info) +{ + Gadget_Action *ga; + const char *action = event_info; + Ecore_Wl2_Display *d = data; + void *ar_global; + struct action_route_bind *ar_bind; + Eina_Hash *h; + + if ((!action) || (!action[0])) + { + evas_object_smart_callback_call(obj, "gadget_action", NULL); + return; + } + if (display_actions) + { + h = eina_hash_find(display_actions, &d); + if (h) + { + ga = eina_hash_find(h, action); + if (ga && (!eina_list_data_find(ga->requestors, obj))) + { + ga->requestors = eina_list_append(ga->requestors, obj); + evas_object_smart_callback_add(obj, ga->handle, _ar_bind_activate, ga); + } + evas_object_smart_callback_call(obj, "gadget_action", ga ? ga->handle : NULL); + return; + } + } + ar_global = eina_hash_find(ar_globals, &d); + if (!ar_global) + { + evas_object_smart_callback_call(obj, "gadget_action", NULL); + return; + } + ga = calloc(1, sizeof(Gadget_Action)); + ga->d = d; + ga->requestors = eina_list_append(ga->requestors, obj); + ga->action = eina_stringshare_add(action); + if (!display_actions) + display_actions = eina_hash_string_superfast_new(NULL); + h = eina_hash_find(display_actions, &d); + if (!h) + { + h = eina_hash_pointer_new((Eina_Free_Cb)_ar_bind_del); + eina_hash_add(display_actions, &d, h); + } + + ar_bind = action_route_bind_action(ar_global, action); + action_route_bind_add_listener(ar_bind, &_ar_bind_interface, ga); + wl_display_roundtrip(ecore_wl2_display_get(d)); +} + +static void +win_del(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + Ecore_Wl2_Display *d = win_display_get(obj); + eina_hash_list_remove(wins, &d, obj); +} + +static Evas_Object * +win_add(Evas_Object *win) +{ + Ecore_Wl2_Display *d; + if (!win) return NULL; + d = win_display_get(win); + _gadget_init(d); + if (!wins) + wins = eina_hash_pointer_new(NULL); + eina_hash_list_append(wins, &d, win); + evas_object_smart_callback_add(win, "gadget_action_request", action_request, d); + evas_object_smart_callback_add(win, "gadget_open_uri", uriopen_request, d); + evas_object_event_callback_add(win, EVAS_CALLBACK_DEL, win_del, NULL); + elm_win_borderless_set(win, 1); + return win; +} + +int +eina_init(void) +{ + int (*_eina_init)(void) = dlsym(RTLD_NEXT, __func__); + + if (wins) return _eina_init(); + if (getenv("RUNNER_DEBUG")) raise(SIGSTOP); + return _eina_init(); +} + +Evas_Object * +elm_win_util_dialog_add(Evas_Object *parent, const char *name, const char *title) +{ + Evas_Object *(*_elm_win_util_dialog_add)(Evas_Object *, const char *, const char *) = dlsym(RTLD_NEXT, __func__); + + return win_add(_elm_win_util_dialog_add(parent, name, title)); +} + +Evas_Object * +elm_win_util_standard_add(const char *name, const char *title) +{ + Evas_Object *(*_elm_win_util_standard_add)(const char *, const char *) = dlsym(RTLD_NEXT, __func__); + + return win_add(_elm_win_util_standard_add(name, title)); +} + +Evas_Object * +elm_win_add(Evas_Object *parent, const char *name, Elm_Win_Type type) +{ + Evas_Object *(*_elm_win_add)(Evas_Object *,const char*, Elm_Win_Type) = dlsym(RTLD_NEXT, __func__); + + return win_add(_elm_win_add(parent, name, type)); +} diff --git a/src/bin/e_gadget_runner.c b/src/bin/e_gadget_runner.c new file mode 100644 index 000000000..965bc3d35 --- /dev/null +++ b/src/bin/e_gadget_runner.c @@ -0,0 +1,942 @@ +#include "e.h" +#include +#include "e-gadget-server-protocol.h" +#include "action_route-server-protocol.h" +#include + +#ifdef __GNUC__ +# pragma GCC diagnostic ignored "-Wformat-truncation" +#endif + +typedef enum +{ + EXIT_MODE_RESTART, + EXIT_MODE_DELETE, +} Exit_Mode; + +typedef struct Config_Item +{ + int id; + int exit_mode; + Eina_Stringshare *cmd; + void *inst; + Eina_Bool cmd_changed : 1; +} Config_Item; + +typedef struct Instance +{ + Evas_Object *box; + Evas_Object *obj; + Ecore_Exe *exe; + Config_Item *ci; + Eina_Hash *allowed_pids; + void *gadget_resource; + Evas_Object *popup; + Evas_Object *ctxpopup; + Eina_List *extracted; +} Instance; + +typedef struct RConfig +{ + Eina_List *items; + Evas_Object *config_dialog; +} RConfig; + +static E_Config_DD *conf_edd = NULL; +static E_Config_DD *conf_item_edd = NULL; + +static int ns_fd = -1; + +static RConfig *rconfig; +static Eina_List *instances; +static Eina_List *wizards; + +static Eina_Hash *sandbox_gadgets; + +static Eina_List *handlers; +static Eio_Monitor *gadget_monitor; +static Eio_File *gadget_lister; + +typedef struct Wizard_Item +{ + Evas_Object *site; + Evas_Object *popup; + E_Gadget_Wizard_End_Cb cb; + void *data; + int id; + Eina_Bool sandbox : 1; +} Wizard_Item; + +static void +runner_run(Instance *inst) +{ + char *preload = eina_strdup(getenv("LD_PRELOAD")); + char buf[PATH_MAX]; + int pid; + + snprintf(buf, sizeof(buf), "%s/enlightenment/gadgets/%s/loader.so", e_prefix_lib_get(), MODULE_ARCH); + e_util_env_set("LD_PRELOAD", buf); + + snprintf(buf, sizeof(buf), "%d", inst->ci->id); + e_util_env_set("E_GADGET_ID", buf); + + unshare(CLONE_NEWPID); + + inst->exe = efl_wl_run(inst->obj, inst->ci->cmd); + + setns(ns_fd, CLONE_NEWPID); + + e_util_env_set("E_GADGET_ID", NULL); + e_util_env_set("LD_PRELOAD", preload); + free(preload); + eina_hash_free_buckets(inst->allowed_pids); + pid = ecore_exe_pid_get(inst->exe); + eina_hash_add(inst->allowed_pids, &pid, (void*)1); +} + +static void +_config_close(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + Config_Item *ci = data; + Instance *inst = ci->inst; + + e_comp_ungrab_input(1, 1); + rconfig->config_dialog = NULL; + if (ci->cmd_changed) + { + char *cmd; + + cmd = elm_entry_markup_to_utf8(elm_entry_entry_get(evas_object_data_get(obj, "entry"))); + eina_stringshare_replace(&ci->cmd, cmd); + free(cmd); + e_config_save_queue(); + } + if (!inst) ci->cmd_changed = 0; + if (!ci->cmd_changed) return; + ci->cmd_changed = 0; + if (inst->exe) ecore_exe_quit(inst->exe); + runner_run(inst); +} + +static void +_config_label_add(Evas_Object *tb, const char *txt, int row) +{ + Evas_Object *o; + + o = elm_label_add(tb); + E_ALIGN(o, 0, 0.5); + elm_object_text_set(o, txt); + evas_object_show(o); + elm_table_pack(tb, o, 0, row, 1, 1); +} + +static void +_config_cmd_changed(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Config_Item *ci = data; + + ci->cmd_changed = 1; +} + +static void +_config_cmd_activate(void *data, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + Config_Item *ci = data; + Instance *inst = ci->inst; + char *cmd; + + ci->cmd_changed = 0; + cmd = elm_entry_markup_to_utf8(elm_entry_entry_get(obj)); + eina_stringshare_replace(&ci->cmd, cmd); + free(cmd); + e_config_save_queue(); + if (!inst) return; + if (inst->exe) ecore_exe_quit(inst->exe); + runner_run(inst); +} + +EINTERN Evas_Object * +config_runner(Config_Item *ci, E_Zone *zone) +{ + Evas_Object *popup, *tb, *o, *ent, *rg; + int row = 0; + + if (!zone) zone = e_zone_current_get(); + popup = elm_popup_add(e_comp->elm); + E_EXPAND(popup); + evas_object_layer_set(popup, E_LAYER_POPUP); + elm_popup_allow_events_set(popup, 1); + elm_popup_scrollable_set(popup, 1); + + tb = elm_table_add(popup); + elm_table_align_set(tb, 0, 0.5); + E_EXPAND(tb); + evas_object_show(tb); + elm_object_content_set(popup, tb); + + o = evas_object_rectangle_add(e_comp->evas); + evas_object_size_hint_min_set(o, ELM_SCALE_SIZE(200), 1); + elm_table_pack(tb, o, 0, row++, 2, 1); + + _config_label_add(tb, _("Command:"), row); + ent = o = elm_entry_add(tb); + E_FILL(o); + evas_object_show(o); + elm_entry_single_line_set(o, 1); + elm_entry_entry_set(o, ci->cmd); + evas_object_smart_callback_add(o, "changed,user", _config_cmd_changed, ci); + evas_object_smart_callback_add(o, "activated", _config_cmd_activate, ci); + elm_table_pack(tb, o, 1, row++, 1, 1); + + _config_label_add(tb, _("On Exit:"), row); + o = rg = elm_radio_add(tb); + E_FILL(o); + evas_object_show(o); + elm_object_text_set(o, _("Restart")); + elm_radio_state_value_set(o, EXIT_MODE_RESTART); + elm_radio_value_pointer_set(o, &ci->exit_mode); + elm_table_pack(tb, o, 1, row++, 1, 1); + + o = elm_radio_add(tb); + E_FILL(o); + elm_radio_group_add(o, rg); + evas_object_show(o); + elm_object_text_set(o, _("Delete")); + elm_radio_state_value_set(o, EXIT_MODE_DELETE); + elm_table_pack(tb, o, 1, row++, 1, 1); + + + popup = e_comp_object_util_add(popup, E_COMP_OBJECT_TYPE_NONE); + evas_object_layer_set(popup, E_LAYER_POPUP); + evas_object_move(popup, zone->x, zone->y); + evas_object_resize(popup, zone->w / 4, zone->h / 3); + e_comp_object_util_center(popup); + evas_object_show(popup); + e_comp_object_util_autoclose(popup, NULL, e_comp_object_util_autoclose_on_escape, NULL); + evas_object_event_callback_priority_add(popup, EVAS_CALLBACK_DEL, EVAS_CALLBACK_PRIORITY_BEFORE, _config_close, ci); + evas_object_data_set(popup, "entry", ent); + e_comp_grab_input(1, 1); + + elm_object_focus_set(ent, 1); + + return rconfig->config_dialog = popup; +} + +static Config_Item * +_conf_item_get(int *id) +{ + Config_Item *ci; + Eina_List *l; + + if (*id > 0) + { + EINA_LIST_FOREACH(rconfig->items, l, ci) + if (*id == ci->id) return ci; + } + + ci = E_NEW(Config_Item, 1); + if (!*id) + *id = ci->id = rconfig->items ? eina_list_count(rconfig->items) + 1 : 1; + else + ci->id = *id; + + if (ci->id < 1) return ci; + rconfig->items = eina_list_append(rconfig->items, ci); + e_config_save_queue(); + + return ci; +} + +static void +wizard_site_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Wizard_Item *wi = data; + wi->site = NULL; + evas_object_hide(wi->popup); + evas_object_del(wi->popup); +} + +static void +_wizard_config_end(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Wizard_Item *wi = data; + Eina_List *l; + Config_Item *ci; + + EINA_LIST_FOREACH(rconfig->items, l, ci) + { + if (ci->id == wi->id) + { + if (ci->cmd) break; + wi->id = 0; + free(ci); + rconfig->items = eina_list_remove_list(rconfig->items, l); + break; + } + } + + if (wi->site) + wi->cb(wi->data, wi->id); + wizards = eina_list_remove(wizards, wi); + if (wi->site) + evas_object_event_callback_del_full(wi->site, EVAS_CALLBACK_DEL, wizard_site_del, wi); + free(wi); +} + +static Evas_Object * +runner_wizard(E_Gadget_Wizard_End_Cb cb, void *data, Evas_Object *site) +{ + int id = 0; + Config_Item *ci; + Wizard_Item *wi; + + wi = E_NEW(Wizard_Item, 1); + wi->cb = cb; + wi->data = data; + wi->site = site; + evas_object_event_callback_add(wi->site, EVAS_CALLBACK_DEL, wizard_site_del, wi); + wizards = eina_list_append(wizards, wi); + + ci = _conf_item_get(&id); + wi->id = ci->id; + wi->popup = config_runner(ci, NULL); + evas_object_event_callback_add(wi->popup, EVAS_CALLBACK_DEL, _wizard_config_end, wi); + return wi->popup; +} + +///////////////////////////////////////// + +static void +mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Instance *inst = data; + evas_object_focus_set(inst->obj, 1); +} + +static void +runner_removed(void *data, Evas_Object *obj EINA_UNUSED, void *event_info) +{ + Instance *inst = data; + if (inst->box != event_info) return; + rconfig->items = eina_list_remove(rconfig->items, inst->ci); + eina_stringshare_del(inst->ci->cmd); + E_FREE(inst->ci); +} + +static void +runner_site_gravity(void *data, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + Instance *inst = data; + if (inst->gadget_resource) + e_gadget_send_gadget_gravity(inst->gadget_resource, e_gadget_site_gravity_get(obj)); +} + +static void +runner_site_anchor(void *data, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + Instance *inst = data; + if (inst->gadget_resource) + e_gadget_send_gadget_anchor(inst->gadget_resource, e_gadget_site_anchor_get(obj)); +} + +static void +runner_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + Instance *inst = data; + Evas_Object *site = e_gadget_site_get(obj); + + evas_object_smart_callback_del_full(site, "gadget_removed", runner_removed, inst); + evas_object_smart_callback_del_full(site, "gadget_site_anchor", runner_site_anchor, inst); + evas_object_smart_callback_del_full(site, "gadget_site_gravity", runner_site_gravity, inst); + if (inst->ci) + { + inst->ci->inst = NULL; + E_FREE_FUNC(inst->exe, ecore_exe_quit); + } + else + E_FREE_FUNC(inst->exe, ecore_exe_terminate); + instances = eina_list_remove(instances, inst); + eina_hash_free(inst->allowed_pids); + free(inst); +} + +static Evas_Object * +runner_gadget_configure(Evas_Object *g) +{ + Instance *inst = evas_object_data_get(g, "runner"); + return config_runner(inst->ci, e_comp_object_util_zone_get(g)); +} + +static void +runner_created(void *data, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + Instance *inst = data; + if (inst->box != event_info) return; + e_gadget_configure_cb_set(inst->box, runner_gadget_configure); + evas_object_smart_callback_del_full(obj, "gadget_created", runner_created, data); +} + + +static void +gadget_unbind(struct wl_resource *resource) +{ + Instance *inst = wl_resource_get_user_data(resource); + inst->gadget_resource = NULL; +} + +static void +gadget_open_uri(struct wl_client *client EINA_UNUSED, struct wl_resource *resource EINA_UNUSED, const char *uri) +{ + //Instance *inst = wl_resource_get_user_data(resource); + + /* FIXME: rate limit? */ + e_util_open(uri, NULL); +} + +static const struct e_gadget_interface _gadget_interface = +{ + .open_uri = gadget_open_uri, +}; + +static void +gadget_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) +{ + struct wl_resource *res; + Instance *inst = data; + pid_t pid; + Evas_Object *site; + + wl_client_get_credentials(client, &pid, NULL, NULL); + if (pid != ecore_exe_pid_get(inst->exe)) + { + wl_client_post_no_memory(client); + return; + } + + res = wl_resource_create(client, &e_gadget_interface, version, id); + wl_resource_set_implementation(res, &_gadget_interface, data, gadget_unbind); + inst->gadget_resource = res; + site = e_gadget_site_get(inst->box); + e_gadget_send_gadget_orient(res, e_gadget_site_orient_get(site)); + e_gadget_send_gadget_gravity(res, e_gadget_site_gravity_get(site)); + e_gadget_send_gadget_anchor(res, e_gadget_site_anchor_get(site)); +} + +static void +ar_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) +{ + struct wl_resource *res; + Instance *inst = data; + int v; + const void *ar_interface; + pid_t pid; + + wl_client_get_credentials(client, &pid, NULL, NULL); + if (pid != ecore_exe_pid_get(inst->exe)) + { + wl_client_post_no_memory(client); + return; + } + ar_interface = e_comp_wl_extension_action_route_interface_get(&v); + + if (!(res = wl_resource_create(client, &action_route_interface, MIN(v, version), id))) + { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(res, ar_interface, inst->allowed_pids, NULL); +} + +static void +child_close(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + Instance *inst = data; + Evas_Object *ext; + + inst->popup = NULL; + ext = evas_object_data_get(obj, "extracted"); + elm_box_unpack_all(obj); + inst->extracted = eina_list_remove(inst->extracted, ext); + evas_object_hide(ext); +} + +static void +child_added(void *data, Evas_Object *obj, void *event_info) +{ + Evas_Object *popup, *bx; + E_Zone *zone = e_comp_object_util_zone_get(obj); + Instance *inst = data; + + if (!efl_wl_surface_extract(event_info)) return; + inst->extracted = eina_list_append(inst->extracted, event_info); + + popup = elm_popup_add(e_comp->elm); + e_comp_object_util_del_list_append(event_info, popup); + E_EXPAND(popup); + evas_object_layer_set(popup, E_LAYER_POPUP); + elm_popup_allow_events_set(popup, 1); + elm_popup_scrollable_set(popup, 1); + + bx = elm_box_add(popup); + E_EXPAND(event_info); + E_FILL(event_info); + elm_box_homogeneous_set(bx, 1); + evas_object_show(bx); + elm_box_pack_end(bx, event_info); + elm_object_content_set(popup, bx); + + inst->popup = popup = e_comp_object_util_add(popup, E_COMP_OBJECT_TYPE_NONE); + evas_object_layer_set(popup, E_LAYER_POPUP); + evas_object_move(popup, zone->x, zone->y); + evas_object_resize(popup, zone->w / 4, zone->h / 3); + e_comp_object_util_center(popup); + evas_object_show(popup); + e_comp_canvas_feed_mouse_up(0); + e_comp_object_util_autoclose(popup, NULL, e_comp_object_util_autoclose_on_escape, NULL); + evas_object_event_callback_add(bx, EVAS_CALLBACK_DEL, child_close, inst); + evas_object_data_set(bx, "extracted", event_info); + evas_object_focus_set(event_info, 1); +} + +static void +popup_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + Instance *inst = data; + Evas_Object *ext; + + inst->ctxpopup = NULL; + ext = evas_object_data_get(obj, "extracted"); + elm_box_unpack_all(obj); + inst->extracted = eina_list_remove(inst->extracted, ext); + evas_object_hide(ext); +} + +static void +popup_dismissed(void *data EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + evas_object_del(obj); +} + +static void +popup_hide(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Instance *inst = data; + elm_ctxpopup_dismiss(inst->ctxpopup); + evas_object_del(elm_object_content_get(inst->ctxpopup)); +} + +static void +popup_hints_update(Evas_Object *obj) +{ + double w, h; + E_Zone *zone = e_comp_object_util_zone_get(obj); + + evas_object_size_hint_weight_get(obj, &w, &h); + w = E_CLAMP(w, 0, 0.5); + h = E_CLAMP(h, 0, 0.5); + + if ((w > 0) && (h > 0)) + { + evas_object_size_hint_min_set(obj, w * zone->w, h * zone->h); + evas_object_size_hint_max_set(obj, w * zone->w, h * zone->h); + } + if ((!EINA_DBL_NONZERO(w)) && (!EINA_DBL_NONZERO(h))) + { + int ww, hh; + evas_object_geometry_get(obj, NULL, NULL, &ww, &hh); + evas_object_size_hint_min_set(obj, ww, hh); + } + E_WEIGHT(obj, 0, 0); +} + +static void +popup_hints(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + evas_object_event_callback_del_full(obj, EVAS_CALLBACK_CHANGED_SIZE_HINTS, popup_hints, data); + popup_hints_update(obj); + evas_object_event_callback_add(obj, EVAS_CALLBACK_CHANGED_SIZE_HINTS, popup_hints, data); +} + +static void +popup_added(void *data, Evas_Object *obj EINA_UNUSED, void *event_info) +{ + Instance *inst = data; + Evas_Object *bx; + + if (!efl_wl_surface_extract(event_info)) return; + inst->extracted = eina_list_append(inst->extracted, event_info); + + inst->ctxpopup = elm_ctxpopup_add(inst->box); + elm_object_style_set(inst->ctxpopup, "noblock"); + evas_object_smart_callback_add(inst->ctxpopup, "dismissed", popup_dismissed, inst); + evas_object_event_callback_add(event_info, EVAS_CALLBACK_DEL, popup_hide, inst); + + bx = elm_box_add(inst->ctxpopup); + popup_hints_update(event_info); + E_FILL(event_info); + evas_object_event_callback_add(event_info, EVAS_CALLBACK_CHANGED_SIZE_HINTS, popup_hints, inst); + evas_object_show(bx); + elm_box_pack_end(bx, event_info); + elm_box_recalculate(bx); + evas_object_data_set(bx, "extracted", event_info); + evas_object_event_callback_add(bx, EVAS_CALLBACK_DEL, popup_del, inst); + elm_object_content_set(inst->ctxpopup, bx); + + e_gadget_util_ctxpopup_place(inst->box, inst->ctxpopup, NULL); + evas_object_show(inst->ctxpopup); + evas_object_focus_set(event_info, 1); +} + +static void +runner_hints(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + Instance *inst = data; + int w, h; + Evas_Aspect_Control aspect; + + evas_object_size_hint_min_get(obj, &w, &h); + evas_object_size_hint_min_set(inst->box, w, h); + evas_object_size_hint_max_get(obj, &w, &h); + evas_object_size_hint_max_set(inst->box, w, h); + evas_object_size_hint_aspect_get(obj, &aspect, &w, &h); + evas_object_size_hint_aspect_set(inst->box, aspect, w, h); +} + +static Evas_Object * +gadget_create(Evas_Object *parent, Config_Item *ci, int *id, E_Gadget_Site_Orient orient EINA_UNUSED) +{ + Instance *inst; + int ar_version; + + inst = E_NEW(Instance, 1); + instances = eina_list_append(instances, inst); + inst->ci = ci; + if (!inst->ci) + inst->ci = _conf_item_get(id); + inst->ci->inst = inst; + inst->allowed_pids = eina_hash_int32_new(NULL); + inst->obj = efl_wl_add(e_comp->evas); + E_EXPAND(inst->obj); + E_FILL(inst->obj); + evas_object_show(inst->obj); + efl_wl_aspect_set(inst->obj, 1); + efl_wl_minmax_set(inst->obj, 1); + efl_wl_global_add(inst->obj, &e_gadget_interface, 1, inst, gadget_bind); + evas_object_smart_callback_add(inst->obj, "child_added", child_added, inst); + evas_object_smart_callback_add(inst->obj, "popup_added", popup_added, inst); + e_comp_wl_extension_action_route_interface_get(&ar_version); + efl_wl_global_add(inst->obj, &action_route_interface, ar_version, inst, ar_bind); + evas_object_data_set(inst->obj, "runner", inst); + evas_object_event_callback_add(inst->obj, EVAS_CALLBACK_MOUSE_DOWN, mouse_down, inst); + evas_object_smart_callback_add(parent, "gadget_created", runner_created, inst); + evas_object_smart_callback_add(parent, "gadget_removed", runner_removed, inst); + evas_object_smart_callback_add(parent, "gadget_site_anchor", runner_site_anchor, inst); + evas_object_smart_callback_add(parent, "gadget_site_gravity", runner_site_gravity, inst); + runner_run(inst); + ecore_exe_data_set(inst->exe, inst); + inst->box = elm_box_add(e_comp->elm); + evas_object_event_callback_add(inst->box, EVAS_CALLBACK_DEL, runner_del, inst); + evas_object_event_callback_add(inst->obj, EVAS_CALLBACK_CHANGED_SIZE_HINTS, runner_hints, inst); + elm_box_homogeneous_set(inst->box, 1); + elm_box_pack_end(inst->box, inst->obj); + return inst->box; +} + +static Evas_Object * +runner_create(Evas_Object *parent, int *id, E_Gadget_Site_Orient orient) +{ + Evas_Object *obj; + Config_Item *ci = NULL; + + if (orient) return NULL; + if (*id > 0) ci = _conf_item_get(id); + if ((*id < 0) || ci->inst) + { + obj = elm_image_add(parent); + elm_image_file_set(obj, e_theme_edje_file_get(NULL, "e/icons/modules-launcher"), "e/icons/modules-launcher"); + evas_object_size_hint_aspect_set(obj, EVAS_ASPECT_CONTROL_BOTH, 1, 1); + return obj; + } + return gadget_create(parent, ci, id, orient); +} + +static Eina_Bool +runner_exe_del(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Exe_Event_Del *ev) +{ + Instance *inst = ecore_exe_data_get(ev->exe); + + if ((!inst) || (!instances) || (!eina_list_data_find(instances, inst))) return ECORE_CALLBACK_RENEW; + switch (inst->ci->exit_mode) + { + case EXIT_MODE_RESTART: + /* FIXME: probably notify? */ + if (ev->exit_code == 255) //exec error + e_gadget_del(inst->box); + else + { + runner_run(inst); + ecore_exe_data_set(inst->exe, inst); + } + break; + case EXIT_MODE_DELETE: + e_gadget_del(inst->box); + break; + } + return ECORE_CALLBACK_RENEW; +} + +/////////////////////////////// + +static Evas_Object * +sandbox_create(Evas_Object *parent, const char *type, int *id, E_Gadget_Site_Orient orient) +{ + Efreet_Desktop *ed = eina_hash_find(sandbox_gadgets, type); + Config_Item *ci = NULL; + + if (*id > 0) ci = _conf_item_get(id); + if ((*id < 0) || (ci && ci->inst)) + { + if (ed->x) + { + const char *orients = eina_hash_find(ed->x, "X-Gadget-Orientations"); + + if (orients) + { + const char *ostring[] = + { + "None", + "Horizontal", + "Vertical", + }; + const char *v, *val = orients; + Eina_Bool found = EINA_FALSE; + + v = strchr(val, ';'); + do + { + if (v) + { + if (!memcmp(val, ostring[orient], v - val)) + { + found = EINA_TRUE; + break; + } + val = v + 1; + v = strchr(val, ';'); + } + else + { + if (!strcmp(val, ostring[orient])) + found = EINA_TRUE; + break; + } + } while (val[0]); + if (!found) return NULL; + } + } + if (ed->icon) + { + int w, h; + Eina_Bool fail = EINA_FALSE; + Evas_Object *obj; + + obj = elm_image_add(parent); + if (ed->icon[0] == '/') + { + if (eina_str_has_extension(ed->icon, ".edj")) + fail = !elm_image_file_set(obj, ed->icon, "icon"); + else + fail = !elm_image_file_set(obj, ed->icon, NULL); + } + else + { + if (!elm_image_file_set(obj, e_theme_edje_file_get(NULL, ed->icon), ed->icon)) + fail = !elm_icon_standard_set(obj, ed->icon); + } + if (!fail) + { + elm_image_object_size_get(obj, &w, &h); + if (w && h) + evas_object_size_hint_aspect_set(obj, EVAS_ASPECT_CONTROL_BOTH, w, h); + return obj; + } + evas_object_del(obj); + } + } + if (!ci) + { + ci = _conf_item_get(id); + ci->cmd = eina_stringshare_add(ed->exec); + ci->exit_mode = EXIT_MODE_RESTART; + } + return gadget_create(parent, ci, id, orient); +} + +static char * +sandbox_name(const char *filename) +{ + Efreet_Desktop *ed = eina_hash_find(sandbox_gadgets, filename); + const char *name = ed->name ?: ed->generic_name; + char buf[1024]; + + if (name) return strdup(name); + strncpy(buf, ed->orig_path, sizeof(buf) - 1); + buf[0] = toupper(buf[0]); + return strdup(buf); +} + +/////////////////////////////// + +static void +gadget_dir_add(const char *filename) +{ + const char *file; + char buf[PATH_MAX]; + Efreet_Desktop *ed; + + file = ecore_file_file_get(filename); + snprintf(buf, sizeof(buf), "%s/%s.desktop", filename, file); + ed = efreet_desktop_new(buf); + EINA_SAFETY_ON_NULL_RETURN(ed); + eina_hash_add(sandbox_gadgets, filename, ed); + e_gadget_external_type_add("runner_sandbox", filename, sandbox_create, NULL); + e_gadget_external_type_name_cb_set("runner_sandbox", filename, sandbox_name); +} + +static Eina_Bool +monitor_dir_create(void *d EINA_UNUSED, int t EINA_UNUSED, Eio_Monitor_Event *ev) +{ + if (!eina_hash_find(sandbox_gadgets, ev->filename)) + gadget_dir_add(ev->filename); + return ECORE_CALLBACK_RENEW; +} + +static Eina_Bool +monitor_dir_del(void *d EINA_UNUSED, int t EINA_UNUSED, Eio_Monitor_Event *ev) +{ + eina_hash_del_by_key(sandbox_gadgets, ev->filename); + e_gadget_external_type_del("runner_sandbox", ev->filename); + return ECORE_CALLBACK_RENEW; +} + +static Eina_Bool +monitor_error(void *d EINA_UNUSED, int t EINA_UNUSED, Eio_Monitor_Error *ev EINA_UNUSED) +{ + /* panic? */ + return ECORE_CALLBACK_RENEW; +} + + +static Eina_Bool +list_filter_cb(void *d EINA_UNUSED, Eio_File *ls EINA_UNUSED, const Eina_File_Direct_Info *info) +{ + struct stat st; + char buf[PATH_MAX]; + + if (info->type != EINA_FILE_DIR) return EINA_FALSE; + if (info->path[info->name_start] == '.') return EINA_FALSE; + snprintf(buf, sizeof(buf), "%s/%s.desktop", info->path, info->path + info->name_start); + return !stat(info->path, &st); +} + +static void +list_main_cb(void *d EINA_UNUSED, Eio_File *ls EINA_UNUSED, const Eina_File_Direct_Info *info) +{ + gadget_dir_add(info->path); +} + +static void +list_done_cb(void *d EINA_UNUSED, Eio_File *ls EINA_UNUSED) +{ + gadget_lister = NULL; +} + +static void +list_error_cb(void *d EINA_UNUSED, Eio_File *ls EINA_UNUSED, int error EINA_UNUSED) +{ + gadget_lister = NULL; +} + +EINTERN void +e_gadget_runner_init(void) +{ + conf_item_edd = E_CONFIG_DD_NEW("Config_Item", Config_Item); +#undef T +#undef D +#define T Config_Item +#define D conf_item_edd + E_CONFIG_VAL(D, T, id, INT); + E_CONFIG_VAL(D, T, exit_mode, INT); + E_CONFIG_VAL(D, T, cmd, STR); + + conf_edd = E_CONFIG_DD_NEW("RConfig", RConfig); +#undef T +#undef D +#define T RConfig +#define D conf_edd + E_CONFIG_LIST(D, T, items, conf_item_edd); + + rconfig = e_config_domain_load("e_gadget_runner", conf_edd); + if (!rconfig) rconfig = E_NEW(RConfig, 1); + + e_gadget_type_add("runner", runner_create, runner_wizard); + { + char buf[PATH_MAX]; + + snprintf(buf, sizeof(buf), "%s/enlightenment/gadgets/%s", e_prefix_lib_get(), MODULE_ARCH); + gadget_monitor = eio_monitor_add(buf); + gadget_lister = eio_file_direct_ls(buf, list_filter_cb, list_main_cb, list_done_cb, list_error_cb, NULL); + } + E_LIST_HANDLER_APPEND(handlers, ECORE_EXE_EVENT_DEL, runner_exe_del, NULL); + E_LIST_HANDLER_APPEND(handlers, EIO_MONITOR_DIRECTORY_CREATED, monitor_dir_create, NULL); + E_LIST_HANDLER_APPEND(handlers, EIO_MONITOR_DIRECTORY_DELETED, monitor_dir_del, NULL); + E_LIST_HANDLER_APPEND(handlers, EIO_MONITOR_ERROR, monitor_error, NULL); + + sandbox_gadgets = eina_hash_string_superfast_new((Eina_Free_Cb)efreet_desktop_free); + { + char buf[PATH_MAX]; + + snprintf(buf, sizeof(buf), "/proc/%d/ns/pid", getpid()); + ns_fd = open(buf, O_RDONLY); + } +} + +EINTERN void +e_gadget_runner_shutdown(void) +{ + e_gadget_type_del("runner"); + e_gadget_external_type_del("runner_sandbox", NULL); + + if (rconfig) + { + Config_Item *ci; + + if (rconfig->config_dialog) + { + evas_object_hide(rconfig->config_dialog); + evas_object_del(rconfig->config_dialog); + } + + EINA_LIST_FREE(rconfig->items, ci) + { + eina_stringshare_del(ci->cmd); + free(ci); + } + + } + E_FREE(rconfig); + E_CONFIG_DD_FREE(conf_edd); + E_CONFIG_DD_FREE(conf_item_edd); + E_FREE_LIST(handlers, ecore_event_handler_del); + E_FREE_FUNC(sandbox_gadgets, eina_hash_free); + E_FREE_FUNC(gadget_lister, eio_file_cancel); + close(ns_fd); + ns_fd = -1; +} + +EINTERN void +runner_save(void) +{ + e_config_domain_save("e_gadget_runner", conf_edd, rconfig); +} diff --git a/src/bin/generated/meson.build b/src/bin/generated/meson.build index 2872c6362..0b373503e 100644 --- a/src/bin/generated/meson.build +++ b/src/bin/generated/meson.build @@ -4,6 +4,7 @@ protos = [ '../../protocol/www.xml', '../../protocol/efl-aux-hints.xml', '../../protocol/action_route.xml', + '../../protocol/e-gadget.xml', '@0@/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml'.format(dir_wayland_protocols), '@0@/unstable/relative-pointer/relative-pointer-unstable-v1.xml'.format(dir_wayland_protocols), '@0@/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'.format(dir_wayland_protocols), @@ -17,5 +18,17 @@ foreach p: protos proto_c += gen_scanner_impl.process(p) endforeach +gadget_loader_protos = [ + '../../protocol/action_route.xml', + '../../protocol/e-gadget.xml', +] + +gadget_loader_proto_files = [] + +foreach p: protos + gadget_loader_proto_files += gen_scanner_client.process(p) + gadget_loader_proto_files += gen_scanner_impl.process(p) +endforeach + wayland_proto_c = proto_c wayland_proto_h = proto_h diff --git a/src/bin/meson.build b/src/bin/meson.build index 52181abcb..d20165c6a 100644 --- a/src/bin/meson.build +++ b/src/bin/meson.build @@ -400,6 +400,7 @@ if config_h.has('HAVE_WAYLAND') == true 'e_comp_wl.c', 'e_comp_wl_extensions.c', 'e_comp_wl_extensions_tizen.c', + 'e_gadget_runner.c', wayland_proto_c, wayland_proto_h ] @@ -563,4 +564,17 @@ if freebsd == true suid_exes += join_paths(dir_e_utils, 'enlightenment_ckpasswd') endif +if config_h.has('HAVE_WAYLAND') == true + shared_library('loader', + ['e_gadget_loader.c', gadget_loader_proto_files], + name_prefix: '', + include_directories: include_directories('../..'), + dependencies: [ + dependency('elementary'), + dependency('ecore-wl2'), + dependency('wayland-client'), + cc.find_library('uuid')], + install_dir: join_paths([dir_lib, 'enlightenment/gadgets/', module_arch]), + install: true) +endif subdir('e_fm') diff --git a/src/protocol/e-gadget.xml b/src/protocol/e-gadget.xml new file mode 100644 index 000000000..837c2606b --- /dev/null +++ b/src/protocol/e-gadget.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +