diff --git a/configure.ac b/configure.ac index 9d4fa7e52..0489347e7 100644 --- a/configure.ac +++ b/configure.ac @@ -860,6 +860,7 @@ AC_E_OPTIONAL_MODULE([everything], true) AC_E_OPTIONAL_MODULE([systray], true) AC_E_OPTIONAL_MODULE([comp], true) AC_E_OPTIONAL_MODULE([physics], true, [CHECK_MODULE_PHYSICS]) +AC_E_OPTIONAL_MODULE([quickaccess], true) AC_E_OPTIONAL_MODULE([shot], true) AC_E_OPTIONAL_MODULE([backlight], true) AC_E_OPTIONAL_MODULE([tasks], true) @@ -1029,6 +1030,8 @@ src/modules/comp/Makefile src/modules/comp/module.desktop src/modules/physics/Makefile src/modules/physics/module.desktop +src/modules/quickaccess/Makefile +src/modules/quickaccess/module.desktop src/modules/shot/Makefile src/modules/shot/module.desktop src/modules/backlight/Makefile diff --git a/src/modules/Makefile.am b/src/modules/Makefile.am index e65791342..6215dd443 100644 --- a/src/modules/Makefile.am +++ b/src/modules/Makefile.am @@ -179,6 +179,10 @@ if USE_MODULE_PHYSICS SUBDIRS += physics endif +if USE_MODULE_QUICKACCESS +SUBDIRS += quickaccess +endif + if USE_MODULE_OFONO SUBDIRS += ofono endif diff --git a/src/modules/quickaccess/Makefile.am b/src/modules/quickaccess/Makefile.am new file mode 100644 index 000000000..3e5bde20e --- /dev/null +++ b/src/modules/quickaccess/Makefile.am @@ -0,0 +1,34 @@ +MAINTAINERCLEANFILES = Makefile.in +MODULE = quickaccess + +# data files for the module +filesdir = $(libdir)/enlightenment/modules/$(MODULE) +files_DATA = e-module-$(MODULE).edj module.desktop + +EXTRA_DIST = $(files_DATA) + +# the module .so file +INCLUDES = -I. \ + -I$(top_srcdir) \ + -I$(top_srcdir)/src/modules/$(MODULE) \ + -I$(top_srcdir)/src/bin \ + -I$(top_builddir)/src/bin \ + -I$(top_srcdir)/src/modules \ + @e_cflags@ + +pkgdir = $(libdir)/enlightenment/modules/$(MODULE)/$(MODULE_ARCH) +pkg_LTLIBRARIES = module.la + +module_la_SOURCES = e_mod_main.h \ + e_mod_main.c \ + e_mod_quickaccess.c \ + e_quickaccess_bindings.c \ + e_quickaccess_db.c \ + e_mod_config.c + +module_la_LIBADD = @e_libs@ @dlopen_libs@ +module_la_LDFLAGS = -module -avoid-version +module_la_DEPENDENCIES = $(top_builddir)/config.h + +uninstall: + rm -rf $(DESTDIR)$(libdir)/enlightenment/modules/$(MODULE) diff --git a/src/modules/quickaccess/e-module-quickaccess.edj b/src/modules/quickaccess/e-module-quickaccess.edj new file mode 100644 index 000000000..300a961d1 Binary files /dev/null and b/src/modules/quickaccess/e-module-quickaccess.edj differ diff --git a/src/modules/quickaccess/e_mod_config.c b/src/modules/quickaccess/e_mod_config.c new file mode 100644 index 000000000..10f426a01 --- /dev/null +++ b/src/modules/quickaccess/e_mod_config.c @@ -0,0 +1,555 @@ +#include "e_mod_main.h" + +typedef struct Config_Entry +{ + EINA_INLIST; + const char *id; + E_Quick_Access_Entry *entry; +} Config_Entry; + +struct _E_Config_Dialog_Data +{ + const char *entry; + Evas_Object *o_list_entry; + Evas_Object *o_list_transient; + E_Entry_Dialog *ed; + + Eina_Inlist *entries; + Eina_Inlist *transient_entries; + + int autohide; + int hide_when_behind; + int skip_taskbar; + int skip_pager; + int dont_bug_me; +}; + +/** + * in priority order: + * + * @todo configure match name (to be used by actions) + * + * @todo configure match icccm match class and name. + * + * @todo configure show/hide effects: + * - fullscreen + * - centered + * - slide from top, bottom, left or right + * + * @todo match more than one, doing tabs (my idea is to do another + * tabbing module first, experiment with that, maybe use/reuse + * it here) + */ + +static Config_Entry * +_config_entry_new(E_Quick_Access_Entry *entry) +{ + Config_Entry *ce; + + ce = E_NEW(Config_Entry, 1); + ce->entry = entry; + entry->cfg_entry = ce; + return ce; +} + +static void +_config_entry_free(Config_Entry *ce) +{ + if (!ce) return; + ce->entry->cfg_entry = NULL; + eina_stringshare_del(ce->id); + if (ce->entry->transient) + qa_mod->cfd->cfdata->transient_entries = eina_inlist_remove(qa_mod->cfd->cfdata->transient_entries, EINA_INLIST_GET(ce)); + else + qa_mod->cfd->cfdata->entries = eina_inlist_remove(qa_mod->cfd->cfdata->entries, EINA_INLIST_GET(ce)); + free(ce); +} + +static void +_fill_data(E_Config_Dialog_Data *cfdata) +{ + Eina_List *l; + E_Quick_Access_Entry *entry; + + cfdata->autohide = qa_config->autohide; + cfdata->hide_when_behind = qa_config->hide_when_behind; + cfdata->skip_taskbar = qa_config->skip_taskbar; + cfdata->skip_pager = qa_config->skip_pager; + + EINA_LIST_FOREACH(qa_config->entries, l, entry) + cfdata->entries = eina_inlist_append(cfdata->entries, EINA_INLIST_GET(_config_entry_new(entry))); + + EINA_LIST_FOREACH(qa_config->transient_entries, l, entry) + cfdata->transient_entries = eina_inlist_append(cfdata->transient_entries, EINA_INLIST_GET(_config_entry_new(entry))); +} + +static void * +_create_data(E_Config_Dialog *cfd) +{ + E_Config_Dialog_Data *cfdata; + + cfdata = E_NEW(E_Config_Dialog_Data, 1); + _fill_data(cfdata); + qa_mod->cfd = cfd; + return cfdata; +} + +static void +_free_data(E_Config_Dialog *cfd __UNUSED__, E_Config_Dialog_Data *cfdata) +{ + Eina_Inlist *l; + Config_Entry *ce; + EINA_INLIST_FOREACH_SAFE(cfdata->entries, l, ce) + _config_entry_free(ce); + EINA_INLIST_FOREACH_SAFE(cfdata->transient_entries, l, ce) + _config_entry_free(ce); + free(cfdata); + qa_mod->cfd = NULL; +} + +static int +_advanced_check_changed(E_Config_Dialog *cfd __UNUSED__, E_Config_Dialog_Data *cfdata) +{ + Config_Entry *ce; +#define CHECK(X) \ + if (cfdata->X != qa_config->X) return 1 + + CHECK(dont_bug_me); + + EINA_INLIST_FOREACH(cfdata->entries, ce) + if (ce->id) return 1; + EINA_INLIST_FOREACH(cfdata->transient_entries, ce) + if (ce->id) return 1; + +#undef CHECK + return 0; +} + +static int +_basic_check_changed(E_Config_Dialog *cfd __UNUSED__, E_Config_Dialog_Data *cfdata) +{ +#define CHECK(X) \ + if (cfdata->X != qa_config->X) return 1 + + CHECK(autohide); + CHECK(skip_pager); + CHECK(skip_taskbar); + CHECK(hide_when_behind); + +#undef CHECK + return 0; +} + +static void +_list_select(void *data) +{ + Config_Entry *ce = data; +} + +static void +_list_fill(E_Config_Dialog_Data *cfdata, Evas_Object *list, Eina_Bool transient) +{ + Config_Entry *ce; + EINA_INLIST_FOREACH(transient ? cfdata->transient_entries : cfdata->entries, ce) + { + e_widget_ilist_append(list, NULL, ce->id ?: ce->entry->id, _list_select, ce, ce->entry->id); + } + e_widget_ilist_selected_set(list, 0); +} + +static void +_rename_del(void *data) +{ + E_Config_Dialog_Data *cfdata = e_object_data_get(data); + if (!cfdata) return; + cfdata->ed = NULL; +} + +static void +_rename_ok(char *text, void *data) +{ + Config_Entry *ce = data; + const char *name; + Evas_Object *list; + + name = eina_stringshare_add(text); + if (name == ce->id) + { + eina_stringshare_del(name); + return; + } + if (name == ce->entry->id) + { + eina_stringshare_del(name); + if (!ce->id) return; + eina_stringshare_replace(&ce->id, NULL); + } + else + eina_stringshare_replace(&ce->id, text); + list = ce->entry->transient ? qa_mod->cfd->cfdata->o_list_transient : qa_mod->cfd->cfdata->o_list_entry; + e_widget_ilist_clear(list); + _list_fill(qa_mod->cfd->cfdata, list, ce->entry->transient); +} + +static void +_list_delete(void *data __UNUSED__, void *list) +{ + Config_Entry *ce; + + ce = e_widget_ilist_selected_data_get(list); + if (!ce) return; + e_qa_entry_free(ce->entry); +} + +static void +_list_rename(void *data, void *list) +{ + E_Config_Dialog_Data *cfdata = data; + Config_Entry *ce; + + if (cfdata->ed) + { + e_win_raise(cfdata->ed->dia->win); + return; + } + + ce = e_widget_ilist_selected_data_get(list); + if (!ce) return; + cfdata->ed = e_entry_dialog_show(_("Rename"), "edit-rename", _("Enter a unique name for this entry"), + ce->id ?: ce->entry->id, NULL, NULL, + _rename_ok, NULL, ce); + e_object_data_set(E_OBJECT(cfdata->ed), cfdata); + e_object_del_attach_func_set(E_OBJECT(cfdata->ed), _rename_del); +} + +static Evas_Object * +_advanced_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata) +{ + Evas_Object *ob, *ol, *otb, *tab; + int w, h; + + tab = e_widget_table_add(evas, 0); + evas_object_name_set(tab, "dia_table"); + + otb = e_widget_toolbook_add(evas, 48 * e_scale, 48 * e_scale); + +///////////////////////////////////////////////////////////////// + ol = e_widget_list_add(evas, 0, 0); + ob = e_widget_check_add(evas, _("Disable Warning Dialogs"), (int*)&(cfdata->dont_bug_me)); + e_widget_list_object_append(ol, ob, 1, 0, 0.5); + + e_widget_toolbook_page_append(otb, NULL, _("Behavior"), ol, 1, 1, 1, 1, 0.5, 0.5); + +///////////////////////////////////////////////////////////////// + ol = e_widget_table_add(evas, 0); + e_widget_table_freeze(ol); + + cfdata->o_list_entry = ob = e_widget_ilist_add(evas, 0, 0, &cfdata->entry); + evas_event_freeze(evas_object_evas_get(ob)); + edje_freeze(); + e_widget_ilist_freeze(ob); + + _list_fill(cfdata, ob, EINA_FALSE); + + e_widget_size_min_get(ob, &w, &h); + e_widget_size_min_set(ob, MIN(w, 200), MIN(h, 100)); + e_widget_ilist_go(ob); + e_widget_ilist_thaw(ob); + edje_thaw(); + evas_event_thaw(evas_object_evas_get(ol)); + + e_widget_table_object_append(ol, ob, 0, 0, 2, 1, 1, 1, 1, 1); + + ob = e_widget_button_add(evas, _("Rename"), "edit-rename", _list_rename, cfdata, cfdata->o_list_entry); + e_widget_table_object_append(ol, ob, 0, 1, 1, 1, 1, 1, 0, 0); + + ob = e_widget_button_add(evas, _("Delete"), "edit-delete", _list_delete, cfdata, cfdata->o_list_entry); + e_widget_table_object_append(ol, ob, 1, 1, 1, 1, 1, 1, 0, 0); + + + e_widget_table_thaw(ol); + + e_widget_toolbook_page_append(otb, NULL, _("Entries"), ol, 1, 1, 1, 1, 0.5, 0.5); +///////////////////////////////////////////////////////////////// + ol = e_widget_table_add(evas, 0); + e_widget_table_freeze(ol); + + cfdata->o_list_transient = ob = e_widget_ilist_add(evas, 0, 0, &cfdata->entry); + evas_event_freeze(evas_object_evas_get(ob)); + edje_freeze(); + e_widget_ilist_freeze(ob); + + _list_fill(cfdata, ob, EINA_TRUE); + + e_widget_size_min_get(ob, &w, &h); + e_widget_size_min_set(ob, MIN(w, 200), MIN(h, 100)); + e_widget_ilist_go(ob); + e_widget_ilist_thaw(ob); + edje_thaw(); + evas_event_thaw(evas_object_evas_get(ol)); + + e_widget_table_object_append(ol, ob, 0, 0, 2, 1, 1, 1, 1, 1); + + ob = e_widget_button_add(evas, _("Rename"), "edit-rename", _list_rename, cfdata, cfdata->o_list_transient); + e_widget_table_object_append(ol, ob, 0, 1, 1, 1, 1, 1, 0, 0); + + ob = e_widget_button_add(evas, _("Delete"), "edit-delete", _list_delete, cfdata, cfdata->o_list_transient); + e_widget_table_object_append(ol, ob, 1, 1, 1, 1, 1, 1, 0, 0); + + e_widget_table_thaw(ol); + + e_widget_toolbook_page_append(otb, NULL, _("Transients"), ol, 1, 1, 1, 1, 0.5, 0.5); +///////////////////////////////////////////////////////////////// + e_widget_toolbook_page_show(otb, 0); + + e_dialog_resizable_set(cfd->dia, 1); + + e_widget_table_object_append(tab, otb, 0, 0, 1, 1, 1, 1, 1, 1); + return tab; +} + +static Evas_Object * +_basic_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata) +{ + Evas_Object *ob, *ol, *otb, *tab; + + cfdata->o_list_entry = cfdata->o_list_transient = NULL; + + tab = e_widget_table_add(evas, 0); + evas_object_name_set(tab, "dia_table"); + + otb = e_widget_toolbook_add(evas, 48 * e_scale, 48 * e_scale); + + ol = e_widget_list_add(evas, 0, 0); + + ob = e_widget_check_add(evas, _("Hide Instead Of Raising"), &cfdata->hide_when_behind); + e_widget_list_object_append(ol, ob, 1, 0, 0.5); + + ob = e_widget_check_add(evas, _("Hide If Focus Lost"), &cfdata->autohide); + e_widget_list_object_append(ol, ob, 1, 0, 0.5); + + ob = e_widget_check_add(evas, _("Skip Taskbar"), &cfdata->skip_taskbar); + e_widget_list_object_append(ol, ob, 1, 0, 0.5); + + ob = e_widget_check_add(evas, _("Skip Pager"), &cfdata->skip_pager); + e_widget_list_object_append(ol, ob, 1, 0, 0.5); + + e_widget_toolbook_page_append(otb, NULL, _("Behavior"), ol, 1, 1, 1, 1, 0.5, 0.5); + + e_widget_toolbook_page_show(otb, 0); + + e_dialog_resizable_set(cfd->dia, 1); + + e_widget_table_object_append(tab, otb, 0, 0, 1, 1, 1, 1, 1, 1); + return tab; +} + + +static int +_advanced_apply_data(E_Config_Dialog *cfd __UNUSED__, E_Config_Dialog_Data *cfdata) +{ + Config_Entry *ce; + Eina_Bool entry_changed = EINA_FALSE, transient_changed = EINA_FALSE; +#define SET(X) qa_config->X = cfdata->X + + SET(dont_bug_me); + + EINA_INLIST_FOREACH(cfdata->entries, ce) + { + if (!ce->id) continue; + if (!e_qa_entry_rename(ce->entry, ce->id)) + entry_changed = EINA_TRUE; + eina_stringshare_replace(&ce->id, NULL); + } + EINA_INLIST_FOREACH(cfdata->transient_entries, ce) + { + if (!ce->id) continue; + if (!e_qa_entry_rename(ce->entry, ce->id)) + transient_changed = EINA_TRUE; + eina_stringshare_replace(&ce->id, NULL); + } + if (entry_changed) + { + e_widget_ilist_clear(cfdata->o_list_entry); + _list_fill(qa_mod->cfd->cfdata, cfdata->o_list_entry, EINA_FALSE); + } + if (transient_changed) + { + e_widget_ilist_clear(cfdata->o_list_transient); + _list_fill(qa_mod->cfd->cfdata, cfdata->o_list_transient, EINA_TRUE); + } + e_config_save_queue(); + return 1; +} + +static int +_basic_apply_data(E_Config_Dialog *cfd __UNUSED__, E_Config_Dialog_Data *cfdata) +{ +#define SET(X) qa_config->X = cfdata->X + SET(autohide); + SET(hide_when_behind); + SET(skip_taskbar); + SET(skip_pager); + e_qa_entries_update(); + e_config_save_queue(); + return 1; +} + +static void +_list_item_add(E_Quick_Access_Entry *entry) +{ + Evas_Object *list; + Config_Entry *ce = entry->cfg_entry; + + list = ce->entry->transient ? qa_mod->cfd->cfdata->o_list_transient : qa_mod->cfd->cfdata->o_list_entry; + if (!list) return; + e_widget_ilist_append(list, NULL, ce->id ?: ce->entry->id, _list_select, ce, ce->entry->id); + if (e_widget_ilist_selected_get(list) == -1) e_widget_ilist_selected_set(list, 0); +} + +static void +_list_item_delete(E_Quick_Access_Entry *entry) +{ + Eina_List *l, *ll; + E_Ilist_Item *ili; + Evas_Object *list; + unsigned int x = 0; + + list = entry->transient ? qa_mod->cfd->cfdata->o_list_transient : qa_mod->cfd->cfdata->o_list_entry; + if (!list) return; + l = e_widget_ilist_items_get(list); + EINA_LIST_FOREACH(l, ll, ili) + { + if (e_widget_ilist_item_data_get(ili) == entry->cfg_entry) + { + e_widget_ilist_remove_num(list, x); + break; + } + x++; + } + if (e_widget_ilist_selected_get(list) == -1) e_widget_ilist_selected_set(list, 0); +} +////////////////////////////////////////////////////////////////////////////// +E_Config_DD * +e_qa_config_dd_new(void) +{ + E_Config_DD *conf_edd, *entry_edd; + + conf_edd = E_CONFIG_DD_NEW("Quickaccess_Config", Config); + entry_edd = E_CONFIG_DD_NEW("E_Quick_Access_Entry", E_Quick_Access_Entry); + +#undef T +#undef D +#define T E_Quick_Access_Entry +#define D entry_edd + E_CONFIG_VAL(D, T, id, STR); + E_CONFIG_VAL(D, T, name, STR); + E_CONFIG_VAL(D, T, class, STR); + E_CONFIG_VAL(D, T, cmd, STR); + E_CONFIG_VAL(D, T, win, UINT); + E_CONFIG_VAL(D, T, config.autohide, UCHAR); + E_CONFIG_VAL(D, T, config.relaunch, UCHAR); + E_CONFIG_VAL(D, T, config.hidden, UCHAR); + E_CONFIG_VAL(D, T, transient, UCHAR); +#undef T +#undef D +#define T Config +#define D conf_edd + E_CONFIG_VAL(D, T, config_version, UINT); + E_CONFIG_LIST(D, T, entries, entry_edd); + E_CONFIG_LIST(D, T, transient_entries, entry_edd); + E_CONFIG_VAL(D, T, autohide, UCHAR); + E_CONFIG_VAL(D, T, hide_when_behind, UCHAR); + E_CONFIG_VAL(D, T, skip_taskbar, UCHAR); + E_CONFIG_VAL(D, T, skip_pager, UCHAR); + E_CONFIG_VAL(D, T, dont_bug_me, UCHAR); + return conf_edd; +} + +void * +e_qa_config_dd_free(E_Config_DD *conf_dd) +{ + free(conf_dd); + return NULL; +} + +void +e_qa_config_free(Config *conf) +{ + if (!conf) return; + E_FREE_LIST(conf->entries, e_qa_entry_free); + E_FREE_LIST(conf->transient_entries, e_qa_entry_free); + free(conf); +} + +Config * +e_qa_config_new(void) +{ + Config *conf; + + conf = E_NEW(Config, 1); + conf->skip_taskbar = 1; + conf->skip_pager = 1; + return conf; +} + +void +e_qa_config_entry_transient_convert(E_Quick_Access_Entry *entry) +{ + if (!entry) return; + if (!entry->cfg_entry) return; + _list_item_delete(entry); + entry->transient = !entry->transient; + _list_item_add(entry); + entry->transient = !entry->transient; +} + +void +e_qa_config_entry_free(E_Quick_Access_Entry *entry) +{ + if (!entry) return; + if (!entry->cfg_entry) return; + _list_item_delete(entry); + _config_entry_free(entry->cfg_entry); + entry->cfg_entry = NULL; +} + +void +e_qa_config_entry_add(E_Quick_Access_Entry *entry) +{ + Config_Entry *ce; + + if (!entry) return; + if (!qa_mod->cfd) return; + ce = _config_entry_new(entry); + if (entry->transient) + qa_mod->cfd->cfdata->transient_entries = eina_inlist_append(qa_mod->cfd->cfdata->transient_entries, EINA_INLIST_GET(ce)); + else + qa_mod->cfd->cfdata->entries = eina_inlist_append(qa_mod->cfd->cfdata->entries, EINA_INLIST_GET(ce)); + _list_item_add(entry); +} + +E_Config_Dialog * +e_int_config_qa_module(E_Container *con, const char *params __UNUSED__) +{ + E_Config_Dialog *cfd; + E_Config_Dialog_View *v; + char buf[PATH_MAX]; + + if (qa_mod->cfd) return NULL; + v = E_NEW(E_Config_Dialog_View, 1); + + v->create_cfdata = _create_data; + v->free_cfdata = _free_data; + v->basic.apply_cfdata = _basic_apply_data; + v->basic.create_widgets = _basic_create_widgets; + v->basic.check_changed = _basic_check_changed; + v->advanced.apply_cfdata = _advanced_apply_data; + v->advanced.create_widgets = _advanced_create_widgets; + v->advanced.check_changed = _advanced_check_changed; + + snprintf(buf, sizeof(buf), "%s/e-module-quickaccess.edj", e_module_dir_get(qa_mod->module)); + cfd = e_config_dialog_new(con, _("Quickaccess Settings"), + "E", "launcher/quickaccess", buf, 32, v, NULL); + return cfd; +} diff --git a/src/modules/quickaccess/e_mod_main.c b/src/modules/quickaccess/e_mod_main.c new file mode 100644 index 000000000..6cdcd8a2e --- /dev/null +++ b/src/modules/quickaccess/e_mod_main.c @@ -0,0 +1,119 @@ +#include "e_mod_main.h" + +EINTERN int _e_quick_access_log_dom = -1; +static E_Config_DD *conf_edd = NULL; +Mod *qa_mod = NULL; +Config *qa_config = NULL; + + +/** + * in priority order: + * + * @todo config (see e_mod_config.c) + * + * @todo custom border based on E_Quick_Access_Entry_Mode/E_Gadcon_Orient + * + * @todo show/hide effects: + * - fullscreen + * - centered + * - slide from top, bottom, left or right + * + * @todo match more than one, doing tabs (my idea is to do another + * tabbing module first, experiment with that, maybe use/reuse + * it here) + */ + +EAPI E_Module_Api e_modapi = {E_MODULE_API_VERSION, "Quickaccess"}; + +static Eina_Bool +_e_mod_cb_config_timer(void *data) +{ + e_util_dialog_show(_("Quickaccess Settings Updated"), "%s", (char *)data); + return ECORE_CALLBACK_CANCEL; +} + +////////////////////////////// +EAPI void * +e_modapi_init(E_Module *m) +{ + char buf[PATH_MAX]; + + snprintf(buf, sizeof(buf), "%s/e-module-quickaccess.edj", e_module_dir_get(m)); + e_configure_registry_category_add("launcher", 80, _("Launcher"), NULL, + "preferences-extensions"); + e_configure_registry_item_add("launcher/quickaccess", 1, _("Quickaccess"), NULL, + buf, e_int_config_qa_module); + + qa_mod = E_NEW(Mod, 1); + qa_mod->module = m; + m->data = qa_mod; + conf_edd = e_qa_config_dd_new(); + qa_config = e_config_domain_load("module.quickaccess", conf_edd); + if (qa_config) + { + if ((qa_config->config_version >> 16) < MOD_CONFIG_FILE_EPOCH) + { + e_qa_config_free(qa_config); + qa_config = NULL; + ecore_timer_add(1.0, _e_mod_cb_config_timer, + _("Quickaccess module settings data needed upgrading. Your old configuration
" + "has been wiped and a new set of defaults initialized. This
" + "will happen regularly during development, so don't report a
" + "bug. This means Quickaccess module needs new configuration
" + "data by default for usable functionality that your old
" + "configuration lacked. This new set of defaults will fix
" + "that by adding it in. You can re-configure things now to your
" + "liking. Sorry for the hiccup in your configuration.
")); + } + else if (qa_config->config_version > MOD_CONFIG_FILE_VERSION) + { + e_qa_config_free(qa_config); + qa_config = NULL; + ecore_timer_add(1.0, _e_mod_cb_config_timer, + _("Your Quickaccess module configuration is NEWER than Quickaccess module version. This is very
" + "strange. This should not happen unless you downgraded
" + "the Quickaccess Module or copied the configuration from a place where
" + "a newer version of the Quickaccess Module was running. This is bad and
" + "as a precaution your configuration has been now restored to
" + "defaults. Sorry for the inconvenience.
")); + } + } + + if (!qa_config) qa_config = e_qa_config_new(); + qa_config->config_version = MOD_CONFIG_FILE_VERSION; + + _e_quick_access_log_dom = eina_log_domain_register("quickaccess", EINA_COLOR_ORANGE); + eina_log_domain_level_set("quickaccess", EINA_LOG_LEVEL_DBG); + + if (!e_qa_init()) + { + eina_log_domain_unregister(_e_quick_access_log_dom); + _e_quick_access_log_dom = -1; + return NULL; + } + + return m; +} + +EAPI int +e_modapi_shutdown(E_Module *m __UNUSED__) +{ + e_qa_shutdown(); + + conf_edd = e_qa_config_dd_free(conf_edd); + eina_log_domain_unregister(_e_quick_access_log_dom); + _e_quick_access_log_dom = -1; + e_qa_config_free(qa_config); + free(qa_mod); + qa_config = NULL; + qa_mod = NULL; + return 1; +} + +EAPI int +e_modapi_save(E_Module *m __UNUSED__) +{ + e_config_domain_save("module.quickaccess", conf_edd, qa_config); + return 1; +} + diff --git a/src/modules/quickaccess/e_mod_main.h b/src/modules/quickaccess/e_mod_main.h new file mode 100644 index 000000000..dc17de655 --- /dev/null +++ b/src/modules/quickaccess/e_mod_main.h @@ -0,0 +1,93 @@ +#ifndef E_MOD_MAIN_H +#define E_MOD_MAIN_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "e.h" + +/* Increment for Major Changes */ +#define MOD_CONFIG_FILE_EPOCH 0x0001 +/* Increment for Minor Changes (ie: user doesn't need a new config) */ +#define MOD_CONFIG_FILE_GENERATION 0x0001 +#define MOD_CONFIG_FILE_VERSION ((MOD_CONFIG_FILE_EPOCH << 16) | MOD_CONFIG_FILE_GENERATION) + +typedef struct E_Quick_Access_Entry +{ + const char *id; /* entry identifier (config, actions...), stringshared */ + const char *name; /* icccm name, stringshared */ + const char *class; /* icccm class, stringshared */ + const char *cmd; /* stringshared */ + Ecore_X_Window win; /* current window */ + E_Border *border; /* associated border, if any */ + Ecore_Event_Handler *exe_handler; /* for catching exe delete */ + Ecore_Exe *exe; /* if executed cmd but still no border associated */ + E_Dialog *dia; // used for option handling + void *cfg_entry; // created by config dialog + + struct + { + Eina_Bool autohide; // hide when focus lost + Eina_Bool hide_when_behind; // hide when window is not focused instead of raising + Eina_Bool hidden; // FIXME: used for tracking current state to restore on restart + Eina_Bool relaunch; // reopen on exit + } config; + Eina_Bool transient; +} E_Quick_Access_Entry; + +typedef struct Config +{ + unsigned int config_version; + Eina_List *entries; + Eina_List *transient_entries; + + Eina_Bool autohide; + Eina_Bool hide_when_behind; + Eina_Bool skip_taskbar; + Eina_Bool skip_pager; + Eina_Bool dont_bug_me; +} Config; + +typedef struct Mod +{ + E_Module *module; + E_Config_Dialog *cfd; +} Mod; + +extern Config *qa_config; +extern Mod *qa_mod; +extern int _e_quick_access_log_dom; +extern const char *_act_toggle; +#undef DBG +#undef INF +#undef WRN +#undef ERR +#undef CRIT +#define DBG(...) EINA_LOG_DOM_DBG(_e_quick_access_log_dom, __VA_ARGS__) +#define INF(...) EINA_LOG_DOM_INFO(_e_quick_access_log_dom, __VA_ARGS__) +#define WRN(...) EINA_LOG_DOM_WARN(_e_quick_access_log_dom, __VA_ARGS__) +#define ERR(...) EINA_LOG_DOM_ERR(_e_quick_access_log_dom, __VA_ARGS__) +#define CRIT(...) EINA_LOG_DOM_CRIT(_e_quick_access_log_dom, __VA_ARGS__) + +Eina_Bool e_qa_init(void); +void e_qa_shutdown(void); +void e_qa_entry_free(E_Quick_Access_Entry *entry); +E_Quick_Access_Entry *e_qa_entry_new(const char *id, Eina_Bool transient); +void e_qa_entries_update(void); +Eina_Bool e_qa_entry_rename(E_Quick_Access_Entry *entry, const char *name); + +E_Config_DD *e_qa_config_dd_new(void); +void e_qa_config_free(Config *conf); +Config *e_qa_config_new(void); +void *e_qa_config_dd_free(E_Config_DD *conf_dd); +void e_qa_config_entry_free(E_Quick_Access_Entry *entry); +void e_qa_config_entry_add(E_Quick_Access_Entry *entry); +void e_qa_config_entry_transient_convert(E_Quick_Access_Entry *entry); +E_Config_Dialog *e_int_config_qa_module(E_Container *con, const char *params __UNUSED__); + +char *e_qa_db_class_lookup(const char *class); + +void e_qa_entry_bindings_cleanup(E_Quick_Access_Entry *entry); +void e_qa_entry_bindings_rename(E_Quick_Access_Entry *entry, const char *name); +#endif diff --git a/src/modules/quickaccess/e_mod_quickaccess.c b/src/modules/quickaccess/e_mod_quickaccess.c new file mode 100644 index 000000000..1dd85ac81 --- /dev/null +++ b/src/modules/quickaccess/e_mod_quickaccess.c @@ -0,0 +1,955 @@ +#include "e_mod_main.h" + +EINTERN int _e_qa_log_dom = -1; +static E_Action *_e_qa_toggle = NULL; +static E_Action *_e_qa_add = NULL; +static E_Action *_e_qa_del = NULL; +static const char _e_qa_name[] = "Quickaccess"; +static const char _lbl_toggle[] = "Toggle Visibility"; +static const char _lbl_add[] = "Add Quickaccess For Current Window"; +static const char _lbl_del[] = "Remove Quickaccess From Current Window"; +const char *_act_toggle = NULL; +static const char _act_add[] = "qa_add"; +static const char _act_del[] = "qa_del"; +static E_Grab_Dialog *eg = NULL; +static Eina_List *_e_qa_border_hooks = NULL; +static Eina_List *_e_qa_event_handlers = NULL; + +static E_Border_Menu_Hook *border_hook = NULL; + +static Eina_Bool qa_running = EINA_FALSE; + + +static void _e_qa_bd_menu_add(void *data, E_Menu *m, E_Menu_Item *mi); +static void _e_qa_bd_menu_del(void *data, E_Menu *m, E_Menu_Item *mi); +static void _e_qa_entry_transient_convert(E_Quick_Access_Entry *entry); + +/** + * in priority order: + * + * @todo config (see e_mod_config.c) + * + * @todo custom border based on E_Quick_Access_Entry_Mode/E_Gadcon_Orient + * + * @todo show/hide effects: + * - fullscreen + * - centered + * - slide from top, bottom, left or right + * + * @todo match more than one, doing tabs (my idea is to do another + * tabbing module first, experiment with that, maybe use/reuse + * it here) + */ + +static void +_e_qa_entry_dia_hide(void *data) +{ + E_Quick_Access_Entry *entry; + entry = e_object_data_get(data); + if (entry) entry->dia = NULL; +} + +/* note: id must be stringshared! */ +static E_Quick_Access_Entry * +_e_qa_entry_find(const char *id) +{ + E_Quick_Access_Entry *entry; + const Eina_List *n; + EINA_LIST_FOREACH(qa_config->transient_entries, n, entry) + if (entry->id == id) + return entry; + EINA_LIST_FOREACH(qa_config->entries, n, entry) + if (entry->id == id) + return entry; + return NULL; +} + +static E_Quick_Access_Entry * +_e_qa_entry_find_exe(const Ecore_Exe *exe) +{ + E_Quick_Access_Entry *entry; + const Eina_List *n; + EINA_LIST_FOREACH(qa_config->transient_entries, n, entry) + if (entry->exe == exe) + return entry; + EINA_LIST_FOREACH(qa_config->entries, n, entry) + if (entry->exe == exe) + return entry; + return NULL; +} + +static E_Quick_Access_Entry * +_e_qa_entry_find_border(const E_Border *bd) +{ + E_Quick_Access_Entry *entry; + const Eina_List *n; + EINA_LIST_FOREACH(qa_config->transient_entries, n, entry) + if ((entry->win == bd->client.win) || (entry->border == bd)) + return entry; + EINA_LIST_FOREACH(qa_config->entries, n, entry) + if (entry->border == bd) + return entry; + return NULL; +} + +static E_Quick_Access_Entry * +_e_qa_entry_find_match_stringshared(const char *name, const char *class) +{ + E_Quick_Access_Entry *entry; + const Eina_List *n; + EINA_LIST_FOREACH(qa_config->transient_entries, n, entry) + { + if (entry->win) continue; + if (entry->class != class) continue; + /* no entry name matches all */ + if ((entry->name) && (entry->name != name)) continue; + + return entry; + } + EINA_LIST_FOREACH(qa_config->entries, n, entry) + { + if (entry->win) continue; + if (entry->class != class) continue; + /* no entry name matches all */ + if ((entry->name) && (entry->name != name)) continue; + + return entry; + } + + return NULL; +} + +static E_Quick_Access_Entry * +_e_qa_entry_find_match(const E_Border *bd) +{ + const char *name, *class; + E_Quick_Access_Entry *entry; + + name = bd->client.icccm.name; + class = bd->client.icccm.class; + entry = _e_qa_entry_find_match_stringshared(name, class); + + return entry; +} + +static Eina_Bool +_e_qa_event_exe_del_cb(void *data, int type __UNUSED__, Ecore_Exe_Event_Del *ev) +{ + E_Quick_Access_Entry *entry; + + if (!data) return ECORE_CALLBACK_RENEW; + + entry = _e_qa_entry_find_exe(ev->exe); + if (!entry) return ECORE_CALLBACK_RENEW; + entry->exe = NULL; /* not waiting/running anymore */ + if (entry->exe_handler) ecore_event_handler_del(entry->exe_handler); + entry->exe_handler = NULL; + + return ECORE_CALLBACK_RENEW; +} + +static void +_e_qa_entry_border_props_restore(E_Quick_Access_Entry *entry __UNUSED__, E_Border *bd) +{ +#undef SET +#define SET(X) \ + bd->X = 0 + + SET(lock_user_iconify); + SET(lock_client_iconify); + SET(lock_user_sticky); + SET(lock_client_sticky); + SET(user_skip_winlist); + SET(sticky); +#undef SET + + bd->client.netwm.state.skip_taskbar = 0; + bd->client.netwm.state.skip_pager = 0; + bd->changed = 1; +} + +static void +_e_qa_border_activate(E_Quick_Access_Entry *entry) +{ + entry->config.hidden = 0; + if (!entry->border) return; + e_border_raise(entry->border); + e_border_show(entry->border); + e_border_focus_set(entry->border, 1, 1); + +} + +static void +_e_qa_border_deactivate(E_Quick_Access_Entry *entry) +{ + entry->config.hidden = 1; + if (!entry->border) return; + e_border_hide(entry->border, 1); +} + +static void +_e_qa_entry_border_props_apply(E_Quick_Access_Entry *entry) +{ + if (!entry->border) return; + + if (entry->config.autohide && (!entry->border->focused)) + _e_qa_border_deactivate(entry); + if (qa_config->skip_taskbar) + entry->border->client.netwm.state.skip_taskbar = 1; + if (qa_config->skip_pager) + entry->border->client.netwm.state.skip_pager = 1; +} + +static void +_e_qa_entry_border_associate(E_Quick_Access_Entry *entry, E_Border *bd) +{ + if (entry->exe) entry->exe = NULL; /* not waiting anymore */ + + if (!entry->border) + { + entry->border = bd; + +#define SET(X) \ + bd->X = 1 + + SET(lock_user_iconify); + SET(lock_client_iconify); + SET(lock_user_sticky); + SET(lock_client_sticky); + SET(user_skip_winlist); + SET(sticky); +#undef SET + + //bd->client.e.state.centered = 1; + } + /* FIXME: doesn't work, causes window to flicker on associate + if (entry->config.hidden) + _e_qa_border_deactivate(entry); + else + */ + _e_qa_entry_border_props_apply(entry); +} + +static void +_e_qa_entry_relaunch_setup_continue(void *data, E_Dialog *dia) +{ + E_Quick_Access_Entry *entry = data; + char buf[8192]; + int i; + + if (dia) e_object_del(E_OBJECT(dia)); + entry->dia = NULL; + if (!entry->border->client.icccm.command.argv) + { + e_util_dialog_show(_("Quickaccess Error"), _("Could not determine command for starting this application!")); + /* FIXME: e_entry_dialog? */ + return; + } + entry->config.relaunch = 1; + buf[0] = 0; + for (i = 0; i < entry->border->client.icccm.command.argc; i++) + { + if ((sizeof(buf) - strlen(buf)) < + (strlen(entry->border->client.icccm.command.argv[i]) - 2)) + break; + strcat(buf, entry->border->client.icccm.command.argv[i]); + strcat(buf, " "); + } + entry->cmd = eina_stringshare_add(buf); + if (entry->transient) + _e_qa_entry_transient_convert(entry); +} + +static void +_e_qa_entry_relaunch_setup_cancel(void *data, E_Dialog *dia) +{ + E_Quick_Access_Entry *entry = data; + + e_object_del(E_OBJECT(dia)); + entry->config.relaunch = 0; +} + +static void +_e_qa_entry_relaunch_setup_help(void *data, E_Dialog *dia) +{ + E_Quick_Access_Entry *entry = data; + char buf[8192]; + + e_object_del(E_OBJECT(dia)); + entry->dia = NULL; + entry->dia = dia = e_dialog_new(NULL, "E", "_quickaccess_cmd_help_dialog"); + + snprintf(buf, sizeof(buf), "%s
%s/e-module-quickaccess.edj
%s
" + "data.item: \"%s\" \"--OPT\";", _("The reopen option is meant to be used
" + "with terminal applications to create a persistent
" + "terminal which reopens when closed, generally seen
" + "in quake-style drop-down terminals.
" + "Either the selected application is not a terminal
" + "or the cmdline flag for changing the terminal's window
" + "name is not known. Feel free to submit a bug report if this
" + "is a terminal which can change its window name.
" + "Alternatively, you can add a data.item to"), + e_module_dir_get(qa_mod->module), + _("Like so:"), entry->class); + + e_dialog_title_set(dia, _("Quickaccess Help")); + e_dialog_icon_set(dia, "enlightenment", 64); + e_dialog_text_set(dia, buf); + e_dialog_button_add(dia, _("Cancel"), NULL, _e_qa_entry_relaunch_setup_cancel, entry); + e_win_centered_set(dia->win, 1); + e_dialog_show(dia); + e_object_data_set(E_OBJECT(dia), entry); + e_object_del_attach_func_set(E_OBJECT(dia), _e_qa_entry_dia_hide); +} + +static void +_e_qa_entry_relaunch_setup(E_Quick_Access_Entry *entry) +{ + char *opt; + const char *name; + int i; + char buf[4096]; + Eina_List *l; + E_Quick_Access_Entry *e; + + if (entry->dia) + { + e_win_raise(entry->dia->win); + return; + } + if ((!entry->class) || (!entry->name)) + { + e_util_dialog_show(_("Quickaccess Error"), _("Cannot set relaunch for window without name and class!")); + entry->config.relaunch = 0; + return; + } + if (!strcmp(entry->name, "E")) + { + /* can't set relaunch for internal E dialogs; safety #2 */ + e_util_dialog_show(_("Quickaccess Error"), _("Cannot set relaunch for internal E dialog!")); + entry->config.relaunch = 0; + return; + } + opt = e_qa_db_class_lookup(entry->class); + if ((!opt) || (!opt[0])) + { + E_Dialog *dia; + + free(opt); + if (qa_config->dont_bug_me) + { + _e_qa_entry_relaunch_setup_continue(entry, NULL); + return; + } + entry->dia = dia = e_dialog_new(NULL, "E", "_quickaccess_cmd_dialog"); + + snprintf(buf, sizeof(buf), "%s
%s
%s
%s
%s", _("The selected window created with name:"), + entry->name, _("and class:"), entry->class, _("could not be found in the quicklaunch app database" + "or it is not intended for use with this option.
" + "Please choose an action to take:")); + + e_dialog_title_set(dia, _("Quickaccess Error")); + e_dialog_icon_set(dia, "enlightenment", 64); + e_dialog_text_set(dia, buf); + e_dialog_button_add(dia, _("Continue"), NULL, _e_qa_entry_relaunch_setup_continue, entry); + e_dialog_button_add(dia, _("More Help"), NULL, _e_qa_entry_relaunch_setup_help, entry); + e_dialog_button_add(dia, _("Cancel"), NULL, _e_qa_entry_relaunch_setup_cancel, entry); + e_win_centered_set(dia->win, 1); + e_dialog_show(dia); + e_object_data_set(E_OBJECT(dia), entry); + e_object_del_attach_func_set(E_OBJECT(dia), _e_qa_entry_dia_hide); + entry->config.relaunch = 0; + return; + } + if (!entry->border->client.icccm.command.argv) + { + free(opt); + e_util_dialog_show(_("Quickaccess Error"), _("Could not determine command for starting this application!")); + /* FIXME: e_entry_dialog? */ + return; + } + + buf[0] = 0; + for (i = 0; i < entry->border->client.icccm.command.argc; i++) + { + if ((sizeof(buf) - strlen(buf)) < + (strlen(entry->border->client.icccm.command.argv[i]) - 2)) + break; + strcat(buf, entry->border->client.icccm.command.argv[i]); + strcat(buf, " "); + } + name = entry->name; + entry->name = eina_stringshare_printf("e-%s-%u", entry->name, entry->border->client.netwm.pid); + while (i) + { + i = 0; + EINA_LIST_FOREACH(qa_config->entries, l, e) + { + if (e == entry) continue; + if (e->class != entry->class) continue; + if ((e->name == entry->name) || (e->id == entry->name)) + { + eina_stringshare_del(entry->name); + entry->name = eina_stringshare_printf("e-%s-%u%d", entry->name, entry->border->client.netwm.pid, i); + i++; + break; + } + } + } + eina_stringshare_del(name); + entry->cmd = eina_stringshare_printf("%s %s \"%s\"", buf, opt, entry->name); + entry->config.relaunch = 1; + if (entry->transient) + _e_qa_entry_transient_convert(entry); + free(opt); +} + +static void +_e_qa_border_new(E_Quick_Access_Entry *entry) +{ + E_Exec_Instance *ei; + + if (!entry->cmd) return; + if (entry->exe) + { + INF("already waiting '%s' to start for '%s' (name=%s, class=%s), " + "run request ignored.", entry->cmd, entry->id, entry->name, entry->class); + return; + } + + INF("start quick access '%s' (name=%s, class=%s), " + "run command '%s'", entry->id, entry->name, entry->class, entry->cmd); + + ei = e_exec(NULL, NULL, entry->cmd, NULL, NULL); + if ((!ei) || (!ei->exe)) + { + ERR("could not execute '%s'", entry->cmd); + return; + } + + entry->exe = ei->exe; + entry->exe_handler = ecore_event_handler_add(ECORE_EXE_EVENT_DEL, (Ecore_Event_Handler_Cb)_e_qa_event_exe_del_cb, entry); +} + +static void +_e_qa_del_cb(E_Object *obj __UNUSED__, const char *params __UNUSED__) +{ + _e_qa_bd_menu_del(_e_qa_entry_find_border(e_border_focused_get()), NULL, NULL); +} + +static void +_e_qa_add_cb(E_Object *obj __UNUSED__, const char *params __UNUSED__) +{ + _e_qa_bd_menu_del(e_border_focused_get(), NULL, NULL); +} + +static void +_e_qa_toggle_cb(E_Object *obj __UNUSED__, const char *params) +{ + E_Quick_Access_Entry *entry; + + if (!params) + { + ERR("%s got params == NULL", _act_toggle); + return; + } + /* params is stringshared according to e_bindings.c */ + DBG("%s %s (stringshared=%p)", _act_toggle, params, params); + + entry = _e_qa_entry_find(params); + if (!entry) + { + e_util_dialog_show(_("Quickaccess Error"), _("The requested Quickaccess entry does not exist!")); + return; + } + + if (entry->border) + { + if (entry->border->focused || entry->config.hide_when_behind) + { + _e_qa_border_deactivate(entry); + return; + } + + DBG("activate border for identifier '%s' (name=%s, class=%s).", + entry->id, entry->name, entry->class); + _e_qa_border_activate(entry); + } + else + { + DBG("no border known for identifier '%s' (name=%s, class=%s).", + entry->id, entry->name, entry->class); + _e_qa_border_new(entry); + } +} + +static void +_e_qa_border_eval_pre_post_fetch_cb(void *data __UNUSED__, void *border) +{ + E_Border *bd = border; + E_Quick_Access_Entry *entry; + + if ((!bd->new_client) || (bd->internal)) return; + if ((!bd->client.icccm.class) || (!bd->client.icccm.class[0])) return; + if ((!bd->client.icccm.name) || (!bd->client.icccm.name[0])) return; + + entry = _e_qa_entry_find_match(bd); + if (!entry) return; + DBG("border=%p matches entry %s", bd, entry->id); + _e_qa_entry_border_associate(entry, bd); +} + +static Eina_Bool +_e_qa_event_border_focus_out_cb(void *data __UNUSED__, int type __UNUSED__, E_Event_Border_Focus_Out *ev) +{ + E_Quick_Access_Entry *entry; + + entry = _e_qa_entry_find_border(ev->border); + if (entry && entry->config.autohide) _e_qa_border_deactivate(entry); + return ECORE_CALLBACK_RENEW; +} + +static Eina_Bool +_e_qa_event_module_init_end_cb(void *data __UNUSED__, int type __UNUSED__, void *ev __UNUSED__) +{ + Eina_List *l, *ll; + E_Quick_Access_Entry *entry; + unsigned int count; + /* assume that by now, e has successfully placed all windows */ + count = eina_list_count(qa_config->transient_entries); + EINA_LIST_FOREACH_SAFE(qa_config->transient_entries, l, ll, entry) + { + if (entry->border) continue; + entry->border = e_border_find_by_client_window(entry->win); + if (entry->border) + { + DBG("qa window for %u:transient:%s still exists; restoring", entry->win, entry->id); + _e_qa_entry_border_associate(entry, entry->border); + continue; + } + DBG("qa window for %u:transient:%s no longer exists; deleting", entry->win, entry->id); + e_qa_entry_free(entry); + } + if (count != eina_list_count(qa_config->transient_entries)) e_bindings_reset(); + qa_running = EINA_TRUE; + return ECORE_CALLBACK_RENEW; +} + +static Eina_Bool +_e_qa_event_border_remove_cb(void *data __UNUSED__, int type __UNUSED__, E_Event_Border_Remove *ev) +{ + E_Quick_Access_Entry *entry; + + entry = _e_qa_entry_find_border(ev->border); + if (!entry) return ECORE_CALLBACK_RENEW; + if (entry->transient) + { + DBG("closed transient qa border: deleting keybind and entry"); + e_qa_entry_free(entry); + } + else if (entry->config.relaunch) _e_qa_border_new(entry); + entry->border = NULL; + + return ECORE_CALLBACK_RENEW; +} + +static void +_e_qa_entry_transient_convert(E_Quick_Access_Entry *entry) +{ + e_qa_config_entry_transient_convert(entry); + if (entry->transient) + { + entry->transient = EINA_FALSE; + entry->win = 0; + eina_list_move(&qa_config->entries, &qa_config->transient_entries, entry); + return; + } + entry->transient = EINA_TRUE; + entry->win = entry->border->client.win; + eina_list_move(&qa_config->transient_entries, &qa_config->entries, entry); + eina_stringshare_replace(&entry->cmd, NULL); + entry->config.relaunch = 0; +} + +static E_Quick_Access_Entry * +_e_qa_entry_transient_new(E_Border *bd) +{ + E_Quick_Access_Entry *entry; + char buf[8192]; + + snprintf(buf, sizeof(buf), "%s:%u:%s", bd->client.icccm.name, bd->client.win, bd->client.icccm.class); + + entry = e_qa_entry_new(buf, EINA_TRUE); + entry->win = bd->client.win; + entry->name = eina_stringshare_ref(bd->client.icccm.name); + entry->class = eina_stringshare_ref(bd->client.icccm.class); + _e_qa_entry_border_associate(entry, bd); + qa_config->transient_entries = eina_list_append(qa_config->transient_entries, entry); + e_config_save_queue(); + return entry; +} + +static Eina_Bool +_grab_key_down_cb(void *data, int type __UNUSED__, void *event) +{ + Ecore_Event_Key *ev = event; + E_Border *bd = data; + E_Config_Binding_Key *bi; + E_Quick_Access_Entry *entry; + unsigned int mod = E_BINDING_MODIFIER_NONE; + + if (!strcmp(ev->keyname, "Control_L") || !strcmp(ev->keyname, "Control_R") || + !strcmp(ev->keyname, "Shift_L") || !strcmp(ev->keyname, "Shift_R") || + !strcmp(ev->keyname, "Alt_L") || !strcmp(ev->keyname, "Alt_R") || + !strcmp(ev->keyname, "Super_L") || !strcmp(ev->keyname, "Super_R")) + return ECORE_CALLBACK_RENEW; + + if (ev->modifiers & ECORE_EVENT_MODIFIER_SHIFT) + mod |= E_BINDING_MODIFIER_SHIFT; + if (ev->modifiers & ECORE_EVENT_MODIFIER_CTRL) + mod |= E_BINDING_MODIFIER_CTRL; + if (ev->modifiers & ECORE_EVENT_MODIFIER_ALT) + mod |= E_BINDING_MODIFIER_ALT; + if (ev->modifiers & ECORE_EVENT_MODIFIER_WIN) + mod |= E_BINDING_MODIFIER_WIN; + + if (e_util_binding_match(NULL, ev, NULL, NULL)) + { + e_util_dialog_show("Keybind Error", "The keybinding you have entered is already in use!"); + e_object_del(E_OBJECT(eg)); + return ECORE_CALLBACK_RENEW; + } + entry = _e_qa_entry_transient_new(bd); + + bi = E_NEW(E_Config_Binding_Key, 1); + + bi->context = E_BINDING_CONTEXT_ANY; + bi->modifiers = mod; + bi->key = eina_stringshare_add(ev->keyname); + bi->action = eina_stringshare_ref(_act_toggle); + bi->params = eina_stringshare_ref(entry->id); + + e_managers_keys_ungrab(); + e_config->key_bindings = eina_list_append(e_config->key_bindings, bi); + e_bindings_key_add(bi->context, bi->key, bi->modifiers, bi->any_mod, bi->action, bi->params); + e_managers_keys_grab(); + e_config_save_queue(); + e_object_del(E_OBJECT(eg)); + return ECORE_CALLBACK_RENEW; +} + +static void +_grab_wnd_hide(void *data __UNUSED__) +{ + eg = NULL; +} + +static void +_e_qa_bd_menu_transient(void *data, E_Menu *m __UNUSED__, E_Menu_Item *mi __UNUSED__) +{ + E_Quick_Access_Entry *entry = data; + _e_qa_entry_transient_convert(entry); +} + +static void +_e_qa_bd_menu_relaunch(void *data, E_Menu *m __UNUSED__, E_Menu_Item *mi __UNUSED__) +{ + E_Quick_Access_Entry *entry = data; + + entry->config.relaunch = !entry->config.relaunch; + if (!entry->config.relaunch) return; + _e_qa_entry_relaunch_setup(entry); + if (!entry->config.relaunch) return; + /* a relaunchable entry cannot be transient */ + if (entry->transient) _e_qa_entry_transient_convert(entry); +} + +static void +_e_qa_bd_menu_hideraise(void *data, E_Menu *m __UNUSED__, E_Menu_Item *mi __UNUSED__) +{ + E_Quick_Access_Entry *entry = data; + + entry->config.hide_when_behind = !entry->config.hide_when_behind; +} + +static void +_e_qa_bd_menu_autohide(void *data, E_Menu *m __UNUSED__, E_Menu_Item *mi __UNUSED__) +{ + E_Quick_Access_Entry *entry = data; + + entry->config.autohide = !entry->config.autohide; + _e_qa_entry_border_props_apply(entry); +} + +static void +_e_qa_bd_menu_del(void *data, E_Menu *m __UNUSED__, E_Menu_Item *mi __UNUSED__) +{ + E_Quick_Access_Entry *entry = data; + + if (!entry) return; + + e_qa_entry_free(entry); +} + +static void +_e_qa_bd_menu_config(void *data __UNUSED__, E_Menu *m __UNUSED__, E_Menu_Item *mi __UNUSED__) +{ + e_configure_registry_call("launcher/quickaccess", NULL, NULL); +} + +static void +_e_qa_bd_menu_add(void *data, E_Menu *m __UNUSED__, E_Menu_Item *mi __UNUSED__) +{ + E_Border *bd = data; + if (!bd) return; + if (eg) return; + eg = e_grab_dialog_show(NULL, EINA_FALSE, _grab_key_down_cb, NULL, NULL, bd); + e_object_data_set(E_OBJECT(eg), bd); + e_object_del_attach_func_set(E_OBJECT(eg), _grab_wnd_hide); +} + +static void +_e_qa_bd_menu_pre(void *data, E_Menu *m __UNUSED__, E_Menu_Item *mi) +{ + E_Quick_Access_Entry *entry = data; + E_Menu *subm; + + subm = e_menu_new(); + e_menu_title_set(subm, entry->id); + e_object_data_set(E_OBJECT(subm), entry); + e_menu_item_submenu_set(mi, subm); + + mi = e_menu_item_new(subm); + e_menu_item_check_set(mi, 1); + e_menu_item_toggle_set(mi, entry->config.autohide); + e_menu_item_label_set(mi, _("Autohide")); + e_menu_item_callback_set(mi, _e_qa_bd_menu_autohide, entry); + + mi = e_menu_item_new(subm); + e_menu_item_check_set(mi, 1); + e_menu_item_toggle_set(mi, entry->config.hide_when_behind); + e_menu_item_label_set(mi, _("Hide Instead Of Raise")); + e_menu_item_callback_set(mi, _e_qa_bd_menu_hideraise, entry); + + /* can't set relaunch for internal E dialogs; safety #1 */ + if (strcmp(entry->name, "E")) + { + mi = e_menu_item_new(subm); + e_menu_item_check_set(mi, 1); + e_menu_item_toggle_set(mi, entry->config.relaunch); + e_menu_item_label_set(mi, _("Automatically Reopen When Closed")); + e_menu_item_callback_set(mi, _e_qa_bd_menu_relaunch, entry); + } + + mi = e_menu_item_new(subm); + e_menu_item_check_set(mi, 1); + e_menu_item_toggle_set(mi, entry->transient); + e_menu_item_label_set(mi, _("Transient")); + e_menu_item_callback_set(mi, _e_qa_bd_menu_transient, entry); + + mi = e_menu_item_new(subm); + e_menu_item_separator_set(mi, 1); + + mi = e_menu_item_new(subm); + e_menu_item_label_set(mi, _("Remove Quickaccess")); + e_menu_item_callback_set(mi, _e_qa_bd_menu_del, entry); +} + +static void +_e_qa_bd_menu_hook(void *d __UNUSED__, E_Border *bd) +{ + E_Menu_Item *mi; + E_Menu *m; + E_Quick_Access_Entry *entry; + char buf[PATH_MAX]; + + if (!bd->border_menu) return; + m = bd->border_menu; + + /* position menu item just before first separator */ + mi = m->items->next->data; + mi = e_menu_item_new_relative(m, mi); + entry = _e_qa_entry_find_border(bd); + if (entry) + { + e_menu_item_label_set(mi, _("Quickaccess...")); + e_menu_item_submenu_pre_callback_set(mi, _e_qa_bd_menu_pre, entry); + e_menu_item_callback_set(mi, _e_qa_bd_menu_config, NULL); + } + else + { + e_menu_item_label_set(mi, _("Add Quickaccess")); + e_menu_item_callback_set(mi, _e_qa_bd_menu_add, bd); + } + snprintf(buf, sizeof(buf), "%s/e-module-quickaccess.edj", e_module_dir_get(qa_mod->module)); + e_menu_item_icon_edje_set(mi, buf, "icon"); +} + +static void +_e_qa_entry_config_apply(E_Quick_Access_Entry *entry) +{ +#define SET(X) entry->config.X = qa_config->X + SET(autohide); + SET(hide_when_behind); +#undef SET +} + +////////////////////////////////////////////////////////////////////////////// + +Eina_Bool +e_qa_init(void) +{ + Ecore_Event_Handler *eh; + E_Border_Hook *h; + + _act_toggle = eina_stringshare_add("qa_toggle"); + _e_qa_toggle = e_action_add(_act_toggle); + _e_qa_add = e_action_add(_act_add); + _e_qa_del = e_action_add(_act_del); + if ((!_e_qa_toggle) || (!_e_qa_add) || (!_e_qa_del)) + { + CRIT("could not register %s E_Action", _act_toggle); + e_action_del(_act_toggle); + e_action_del(_act_add); + e_action_del(_act_del); + _e_qa_add = _e_qa_del = _e_qa_toggle = NULL; + eina_stringshare_replace(&_act_toggle, NULL); + return EINA_FALSE; + } +#define CB(id, func) \ + h = e_border_hook_add(E_BORDER_HOOK_##id, _e_qa_border_##func##_cb, NULL); \ + _e_qa_border_hooks = eina_list_append(_e_qa_border_hooks, h) + + CB(EVAL_PRE_POST_FETCH, eval_pre_post_fetch); +#undef CB + +#define CB(id, func) \ + eh = ecore_event_handler_add(id, (Ecore_Event_Handler_Cb)_e_qa_event_##func##_cb, NULL); \ + _e_qa_event_handlers = eina_list_append(_e_qa_event_handlers, eh) + + CB(E_EVENT_BORDER_FOCUS_OUT, border_focus_out); + CB(E_EVENT_BORDER_REMOVE, border_remove); + CB(E_EVENT_MODULE_INIT_END, module_init_end); + CB(ECORE_EXE_EVENT_DEL, exe_del); +#undef CB + + _e_qa_toggle->func.go = _e_qa_toggle_cb; + e_action_predef_name_set(_(_e_qa_name), _(_lbl_toggle), _act_toggle, NULL, _("quick access name/identifier"), 1); + _e_qa_add->func.go = _e_qa_add_cb; + e_action_predef_name_set(_(_e_qa_name), _(_lbl_add), _act_add, NULL, NULL, 0); + _e_qa_del->func.go = _e_qa_del_cb; + e_action_predef_name_set(_(_e_qa_name), _(_lbl_del), _act_del, NULL, NULL, 0); + INF("loaded qa module, registered %s action.", _act_toggle); + + border_hook = e_int_border_menu_hook_add(_e_qa_bd_menu_hook, NULL); + // TODO: on first usage (ie: no config), show instructions that user + // should set a match and keybinding + + return EINA_TRUE; +} + +void +e_qa_shutdown(void) +{ + if (_e_qa_toggle) + { + e_action_predef_name_del(_(_e_qa_name), _(_lbl_toggle)); + + e_action_del(_act_toggle); + _e_qa_toggle = NULL; + } + if (_e_qa_add) + { + e_action_predef_name_del(_(_e_qa_name), _(_lbl_add)); + + e_action_del(_act_add); + _e_qa_add = NULL; + } + if (_e_qa_del) + { + e_action_predef_name_del(_(_e_qa_name), _(_lbl_del)); + + e_action_del(_act_del); + _e_qa_del = NULL; + } + + E_FREE_LIST(_e_qa_event_handlers, ecore_event_handler_del); + E_FREE_LIST(_e_qa_border_hooks, e_border_hook_del); + + e_int_border_menu_hook_del(border_hook); + border_hook = NULL; + INF("unloaded quickaccess module, unregistered %s action.", _act_toggle); + eina_stringshare_del(_act_toggle); + _act_toggle = NULL; + qa_running = EINA_FALSE; +} + +void +e_qa_entry_free(E_Quick_Access_Entry *entry) +{ + if (entry->exe_handler) ecore_event_handler_del(entry->exe_handler); + if (entry->border) _e_qa_entry_border_props_restore(entry, entry->border); + if (entry->cfg_entry) e_qa_config_entry_free(entry); + e_qa_entry_bindings_cleanup(entry); + e_bindings_reset(); + eina_stringshare_del(entry->id); + eina_stringshare_del(entry->name); + eina_stringshare_del(entry->class); + eina_stringshare_del(entry->cmd); + if (entry->transient) + qa_config->transient_entries = eina_list_remove(qa_config->transient_entries, entry); + else + qa_config->entries = eina_list_remove(qa_config->entries, entry); + free(entry); + e_config_save_queue(); +} + +E_Quick_Access_Entry * +e_qa_entry_new(const char *id, Eina_Bool transient) +{ + E_Quick_Access_Entry *entry; + + entry = E_NEW(E_Quick_Access_Entry, 1); + entry->id = eina_stringshare_add(id); + entry->transient = !!transient; + entry->config.autohide = qa_config->autohide; + entry->config.hide_when_behind = qa_config->hide_when_behind; + if (qa_mod->cfd) e_qa_config_entry_add(entry); + return entry; +} + +Eina_Bool +e_qa_entry_rename(E_Quick_Access_Entry *entry, const char *name) +{ + Eina_List *l; + E_Quick_Access_Entry *e; + + /* ensure we don't get duplicates as a result of rename */ + EINA_LIST_FOREACH(qa_config->entries, l, e) + if (e->id == name) return EINA_FALSE; + EINA_LIST_FOREACH(qa_config->transient_entries, l, e) + if (e->id == name) return EINA_FALSE; + e_qa_entry_bindings_rename(entry, name); + eina_stringshare_replace(&entry->id, name); + e_config_save_queue(); + return EINA_TRUE; +} + +void +e_qa_entries_update(void) +{ + E_Quick_Access_Entry *entry; + Eina_List *l; + + EINA_LIST_FOREACH(qa_config->entries, l, entry) + { + _e_qa_entry_config_apply(entry); + _e_qa_entry_border_props_apply(entry); + } + EINA_LIST_FOREACH(qa_config->transient_entries, l, entry) + { + _e_qa_entry_config_apply(entry); + _e_qa_entry_border_props_apply(entry); + } +} diff --git a/src/modules/quickaccess/e_quickaccess_bindings.c b/src/modules/quickaccess/e_quickaccess_bindings.c new file mode 100644 index 000000000..8ec767423 --- /dev/null +++ b/src/modules/quickaccess/e_quickaccess_bindings.c @@ -0,0 +1,112 @@ +#include "e_mod_main.h" + + +void +e_qa_entry_bindings_cleanup(E_Quick_Access_Entry *entry) +{ + Eina_List *l, *ll; + E_Config_Binding_Key *kbi; + E_Config_Binding_Mouse *mbi; + E_Config_Binding_Edge *ebi; + E_Config_Binding_Wheel *wbi; + E_Config_Binding_Acpi *abi; + E_Config_Binding_Signal *sbi; + + EINA_LIST_FOREACH_SAFE(e_config->key_bindings, l, ll, kbi) + { + if ((kbi->action == _act_toggle) && (kbi->params == entry->id)) + { + DBG("removed keybind for %s", entry->id); + e_config->key_bindings = eina_list_remove_list(e_config->key_bindings, l); + eina_stringshare_del(kbi->key); + eina_stringshare_del(kbi->action); + eina_stringshare_del(kbi->params); + free(kbi); + } + } + EINA_LIST_FOREACH_SAFE(e_config->mouse_bindings, l, ll, mbi) + { + if ((mbi->action == _act_toggle) && (mbi->params == entry->id)) + { + DBG("removed mousebind for %s", entry->id); + e_config->mouse_bindings = eina_list_remove_list(e_config->mouse_bindings, l); + eina_stringshare_del(mbi->action); + eina_stringshare_del(mbi->params); + free(mbi); + } + } + EINA_LIST_FOREACH_SAFE(e_config->edge_bindings, l, ll, ebi) + { + if ((ebi->action == _act_toggle) && (ebi->params == entry->id)) + { + DBG("removed edgebind for %s", entry->id); + e_config->edge_bindings = eina_list_remove_list(e_config->edge_bindings, l); + eina_stringshare_del(ebi->action); + eina_stringshare_del(ebi->params); + free(ebi); + } + } + EINA_LIST_FOREACH_SAFE(e_config->wheel_bindings, l, ll, wbi) + { + if ((wbi->action == _act_toggle) && (wbi->params == entry->id)) + { + DBG("removed wheelbind for %s", entry->id); + e_config->wheel_bindings = eina_list_remove_list(e_config->wheel_bindings, l); + eina_stringshare_del(wbi->action); + eina_stringshare_del(wbi->params); + free(wbi); + } + } + EINA_LIST_FOREACH_SAFE(e_config->acpi_bindings, l, ll, abi) + { + if ((abi->action == _act_toggle) && (abi->params == entry->id)) + { + DBG("removed acpibind for %s", entry->id); + e_config->acpi_bindings = eina_list_remove_list(e_config->acpi_bindings, l); + eina_stringshare_del(abi->action); + eina_stringshare_del(abi->params); + free(abi); + } + } + EINA_LIST_FOREACH_SAFE(e_config->signal_bindings, l, ll, sbi) + { + if ((sbi->action == _act_toggle) && (sbi->params == entry->id)) + { + DBG("removed signalbind for %s", entry->id); + e_config->signal_bindings = eina_list_remove_list(e_config->signal_bindings, l); + eina_stringshare_del(sbi->action); + eina_stringshare_del(sbi->params); + free(sbi); + } + } +} + +void +e_qa_entry_bindings_rename(E_Quick_Access_Entry *entry, const char *name) +{ + Eina_List *l; + E_Config_Binding_Key *kbi; + E_Config_Binding_Mouse *mbi; + E_Config_Binding_Edge *ebi; + E_Config_Binding_Wheel *wbi; + E_Config_Binding_Acpi *abi; + E_Config_Binding_Signal *sbi; + +#define RENAME(TYPE, VAR) do {\ + EINA_LIST_FOREACH(e_config->TYPE##_bindings, l, VAR) \ + { \ + if ((VAR->action == _act_toggle) && (VAR->params == entry->id)) \ + { \ + DBG("removed %sbind for %s", #TYPE, entry->id); \ + eina_stringshare_replace(&VAR->params, name); \ + } \ + } \ + } while (0) + RENAME(key, kbi); + RENAME(mouse, mbi); + RENAME(edge, ebi); + RENAME(wheel, wbi); + RENAME(acpi, abi); + RENAME(signal, sbi); + e_bindings_reset(); +} diff --git a/src/modules/quickaccess/e_quickaccess_db.c b/src/modules/quickaccess/e_quickaccess_db.c new file mode 100644 index 000000000..7f07ff996 --- /dev/null +++ b/src/modules/quickaccess/e_quickaccess_db.c @@ -0,0 +1,34 @@ +#include "e_mod_main.h" + +static const char *_e_qa_db[] = +{ + "XTerm", + "URxvt", + "terminology", + NULL +}; + +static const char *_e_qa_arg_db[] = +{ + "-name", + "-name", + "--name", + NULL +}; + +char * +e_qa_db_class_lookup(const char *class) +{ + int x; + char buf[PATH_MAX]; + + if (!class) return NULL; + for (x = 0; _e_qa_db[x]; x++) + { + if (!strcmp(_e_qa_db[x], class)) + return strdup(_e_qa_arg_db[x]); + } + /* allows user-added name options for weird/obscure terminals */ + snprintf(buf, sizeof(buf), "%s/e-module-quickaccess.edj", e_module_dir_get(qa_mod->module)); + return edje_file_data_get(buf, class); +} diff --git a/src/modules/quickaccess/module.desktop.in b/src/modules/quickaccess/module.desktop.in new file mode 100644 index 000000000..cd76405dc --- /dev/null +++ b/src/modules/quickaccess/module.desktop.in @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Link +Name=Quickaccess +Icon=e-module-quickaccess +Comment=Enlightenment Quickaccess Launcher +X-Enlightenment-ModuleType=launcher