diff --git a/configure.ac b/configure.ac index 6082c5e..60dee98 100644 --- a/configure.ac +++ b/configure.ac @@ -63,7 +63,7 @@ else fi dnl Test for required libraries -pkg_modules="libfm >= 1.2.0 x11" +pkg_modules="glib-2.0 libxml-2.0 x11" PKG_CHECK_MODULES(PACKAGE, [$pkg_modules]) AC_SUBST(PACKAGE_CFLAGS) AC_SUBST(PACKAGE_LIBS) @@ -96,9 +96,6 @@ AM_CONDITIONAL(WITH_GTK, [test x$with_gtk = xyes]) AC_SUBST(GTK_CFLAGS) AC_SUBST(GTK_LIBS) -dnl Test for libunistring for correct UTF-8 printf -AC_CHECK_LIB(unistring, ulc_fprintf) - dnl Supress extra linking AC_MSG_CHECKING([whether $LD accepts --as-needed]) case `$LD --as-needed -v 2>&1 = 1.2.0 Version: @VERSION@ Libs: Cflags: -I${includedir} diff --git a/man/lxhotkey.1.in b/man/lxhotkey.1.in index 7a2fdba..63c0f7c 100644 --- a/man/lxhotkey.1.in +++ b/man/lxhotkey.1.in @@ -8,9 +8,9 @@ LXHotkey \- A lightweight global keyboard shortcuts configurator .B lxhotkey [ command ] .SH DESCRIPTION -\fBLXHotkey\fP is an universal application for the Lightweight X11 Desktop -Environment (\fBLXDE\fP) which allows view and change global keyboard shortcuts -for Window Manager actions, including ones to start applications. +\fBLXHotkey\fP allows viewing and changing global keyboard shortcuts +for supported window managers and compositors, including shortcuts +used to start applications. .SH OPTIONS .SS Command line interface: .TP 20 @@ -38,6 +38,8 @@ start with GUI of \fItype\fR .SH SUPPORTED WINDOW MANAGERS .PD 0 .TP +.I labwc +.TP .I Openbox .SH AUTHOR diff --git a/plugins/Makefile.am b/plugins/Makefile.am index 315a631..520c16f 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -1,32 +1,5 @@ ## Process this file with automake to produce Makefile.in -## common flags for all plugins -AM_CPPFLAGS = \ - -I$(top_srcdir) \ - -I$(top_srcdir)/src \ - $(PACKAGE_CFLAGS) - -AM_LDFLAGS = \ - -module -avoid-version -shared -export-dynamic -no-undefined \ - -rpath $(libdir)/lxhotkey $(PACKAGE_LIBS) - -## modules list -pkglibdir = $(libdir)/lxhotkey -pkglib_LTLIBRARIES = ob.la -if WITH_GTK -pkglib_LTLIBRARIES += gtk.la -endif - -## Openbox module -ob_la_SOURCES = openbox/openbox.c -ob_la_LIBADD = -lfm-extra - -# GTK module -gtk_la_SOURCES = gtk/gtk.c gtk/edit.c -gtk_la_CFLAGS = $(GTK_CFLAGS) -gtk_la_LIBADD = $(GTK_LIBS) - - # .desktop files desktopdir=$(datadir)/applications desktop_in_files = @@ -38,13 +11,8 @@ desktop_DATA = $(desktop_in_files:.desktop.in=.desktop) EXTRA_DIST = \ gtk/lxhotkey-gtk.desktop.in gtk/lxhotkey-gtk.desktop \ - gtk/edit.h + gtk/edit.h \ + gtk/gtk.c gtk/edit.c \ + openbox/openbox.c openbox/xml_compat.h openbox/xml_compat.c DISTCLEANFILES = gtk/lxhotkey-gtk.desktop - -install-exec-hook: - rm -f $(DESTDIR)$(pkglibdir)/*.la - -PLUGINS_INSTALLED = $(pkglib_LTLIBRARIES:.la=.so) -uninstall-hook: - cd $(DESTDIR)$(pkglibdir) && rm -f $(PLUGINS_INSTALLED) || true diff --git a/plugins/gtk/gtk.c b/plugins/gtk/gtk.c index c7c7ed3..5daa9a0 100644 --- a/plugins/gtk/gtk.c +++ b/plugins/gtk/gtk.c @@ -172,13 +172,17 @@ static void on_about(GtkAction *act, PluginData *data) "Andriy Grytsenko ", NULL }; + const gchar *labwc[] = { + "sfs + GPT-5.4", + NULL + }; /* TRANSLATORS: Replace this string with your names, one name per line. */ gchar *translators = _("translator-credits"); about = GTK_ABOUT_DIALOG(gtk_about_dialog_new()); gtk_window_set_icon_name(GTK_WINDOW(about), LXHOTKEY_ICON); gtk_about_dialog_set_version(about, VERSION); - gtk_about_dialog_set_program_name(about, "LXHotkey"); //FIXME: translated? + gtk_about_dialog_set_program_name(about, "labhotkey"); gtk_about_dialog_set_logo_icon_name(about, LXHOTKEY_ICON); gtk_about_dialog_set_copyright(about, _("Copyright (C) 2016-2025")); @@ -198,6 +202,7 @@ static void on_about(GtkAction *act, PluginData *data) "Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA."); gtk_about_dialog_set_website(about, "http://lxde.org/"); gtk_about_dialog_set_authors(about, authors); + gtk_about_dialog_add_credit_section(about, "labwc", labwc); gtk_about_dialog_set_translator_credits(about, translators); gtk_dialog_run(GTK_DIALOG(about)); gtk_widget_destroy(GTK_WIDGET(about)); @@ -383,6 +387,7 @@ static void module_gtk_run(const gchar *wm, const LXHotkeyPluginInit *cb, win = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size(GTK_WINDOW(win), 800, 480); gtk_window_set_icon_name(GTK_WINDOW(win), LXHOTKEY_ICON); + gtk_window_set_title(GTK_WINDOW(win), "labhotkey"); g_signal_connect(win, "unmap", G_CALLBACK(gtk_main_quit), NULL); vbox = (GtkBox *)gtk_vbox_new(FALSE, 0); @@ -513,8 +517,6 @@ static void module_gtk_init(int argc, char **argv) inited = 1; } -FM_DEFINE_MODULE(lxhotkey_gui, gtk) - LXHotkeyGUIPluginInit fm_module_init_lxhotkey_gui = { .run = module_gtk_run, .alert = module_gtk_alert, diff --git a/plugins/gtk/edit.c b/plugins/gtk/edit.c index 1bce084..e8e91ed 100644 --- a/plugins/gtk/edit.c +++ b/plugins/gtk/edit.c @@ -1143,12 +1143,10 @@ void _edit_action(PluginData *data, GError **error) gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 4, 0); gtk_container_add(GTK_CONTAINER(align), GTK_WIDGET(data->edit_exec)); gtk_box_pack_start(xbox, align, FALSE, TRUE, 0); - align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0); - gtk_container_add(GTK_CONTAINER(align), gtk_label_new(_("Options:"))); - gtk_box_pack_start(xbox, align, FALSE, TRUE, 0); } data->edit_tree = GTK_TREE_VIEW(gtk_tree_view_new()); - gtk_box_pack_start(xbox, GTK_WIDGET(data->edit_tree), TRUE, TRUE, 0); + if (is_action) + gtk_box_pack_start(xbox, GTK_WIDGET(data->edit_tree), TRUE, TRUE, 0); gtk_container_add(GTK_CONTAINER(widget), GTK_WIDGET(xbox)); gtk_box_pack_start(vbox, widget, FALSE, TRUE, 0); gtk_tree_view_insert_column_with_attributes(data->edit_tree, 0, NULL, @@ -1235,7 +1233,12 @@ void _edit_action(PluginData *data, GError **error) { /* AddAction is visible for action only */ GtkAction *act = gtk_ui_manager_get_action(ui, "/toolbar/AddAction"); + gtk_action_set_visible(act, FALSE); + gtk_action_set_visible(data->add_option_button, FALSE); + gtk_action_set_visible(data->rm_option_button, FALSE); + gtk_action_set_visible(data->edit_option_button, FALSE); + gtk_action_set_visible(data->add_suboption_button, FALSE); } gtk_container_add(GTK_CONTAINER(data->edit_window), GTK_WIDGET(vbox)); g_signal_connect(gtk_tree_view_get_selection(data->edit_tree), "changed", @@ -1243,5 +1246,8 @@ void _edit_action(PluginData *data, GError **error) update_options_tree(data); update_edit_toolbar(data); gtk_window_present(data->edit_window); - gtk_widget_grab_focus(GTK_WIDGET(data->edit_tree)); + if (data->edit_exec) + gtk_widget_grab_focus(GTK_WIDGET(data->edit_exec)); + else + gtk_widget_grab_focus(GTK_WIDGET(data->edit_tree)); } diff --git a/plugins/openbox/openbox.c b/plugins/openbox/openbox.c index 74ee21a..aff16cc 100644 --- a/plugins/openbox/openbox.c +++ b/plugins/openbox/openbox.c @@ -30,7 +30,8 @@ #include #include -#include +#include +#include "xml_compat.h" #include #include @@ -50,6 +51,47 @@ enum LXHotkeyObError { LXKEYS_PARSE_ERROR }; +static gboolean string_mentions_labwc(const char *value) +{ + char *lower; + gboolean ret; + + if (value == NULL || value[0] == '\0') + return FALSE; + lower = g_ascii_strdown(value, -1); + ret = g_strrstr(lower, "labwc") != NULL; + g_free(lower); + return ret; +} + +static gboolean session_is_labwc(void) +{ + return (g_getenv("LABWC_PID") != NULL && g_getenv("LABWC_PID")[0] != '\0') + || string_mentions_labwc(g_getenv("DESKTOP_SESSION")) + || string_mentions_labwc(g_getenv("GDMSESSION")) + || string_mentions_labwc(g_getenv("XDG_CURRENT_DESKTOP")); +} + +static gboolean reconfigure_labwc(GError **error) +{ + const char *pid = g_getenv("LABWC_PID"); + gint status = 0; + + if (pid != NULL && pid[0] != '\0') { + gint64 labwc_pid = g_ascii_strtoll(pid, NULL, 10); + + if (labwc_pid > 0 && kill((int)labwc_pid, SIGHUP) == 0) + return TRUE; + } + if (g_spawn_command_line_sync("labwc --reconfigure", NULL, NULL, &status, NULL) + && g_spawn_check_exit_status(status, NULL)) + return TRUE; + + g_set_error_literal(error, LXKEYS_OB_ERROR, LXKEYS_FILE_ERROR, + _("Failed to reconfigure labwc.")); + return FALSE; +} + /* simple functions to manage LXHotkeyAttr data type */ static inline LXHotkeyAttr *lxhotkey_attr_new(void) @@ -168,6 +210,12 @@ static gboolean restart_openbox(GError **error) XEvent ce; gboolean ret = TRUE; + if (dpy == NULL) { + g_set_error_literal(error, LXKEYS_OB_ERROR, LXKEYS_FILE_ERROR, + _("Failed to reconfigure Openbox.")); + return FALSE; + } + ce.xclient.type = ClientMessage; ce.xclient.message_type = XInternAtom(dpy, "_OB_CONTROL", True); ce.xclient.display = dpy; @@ -251,17 +303,7 @@ static gboolean restart_openbox(GError **error) static char * values_enabled[] = { N_("yes"), N_("no"), NULL }; -static LXHotkeyAttr options_startupnotify[] = { - { N_("enabled"), BOOLEAN_VALUES, NULL, NULL, FALSE }, - { N_("wmclass"), NULL, NULL, NULL, FALSE }, - { N_("name"), NULL, NULL, NULL, FALSE }, - { N_("icon"), NULL, NULL, NULL, FALSE }, - { NULL } -}; - static LXHotkeyAttr options_Execute[] = { { N_("command"), NULL, NULL, NULL, FALSE }, - { N_("prompt"), NULL, NULL, NULL, FALSE }, - { N_("startupnotify"), NULL, TO_BE_CONVERTED(options_startupnotify), NULL, FALSE }, { NULL } }; @@ -334,6 +376,5 @@ static LXHotkeyAttr options_Restart[] = { }; static LXHotkeyAttr options_Exit[] = { - { N_("prompt"), BOOLEAN_VALUES, NULL, NULL, FALSE }, { NULL } }; @@ -480,6 +528,7 @@ typedef struct { typedef struct { char *path; + gboolean is_labwc; FmXmlFile *xml; FmXmlFileItem *keyboard; /* the section */ GList *actions; /* no-exec actions, in reverse order */ @@ -819,7 +868,7 @@ static void obcfg_free(gpointer config) ObXmlFile *cfg = (ObXmlFile *)config; g_free(cfg->path); - g_object_unref(cfg->xml); + fm_xml_file_free(cfg->xml); g_list_free_full(cfg->actions, (GDestroyNotify)lkxeys_action_free); g_list_free_full(cfg->execs, (GDestroyNotify)lkxeys_app_free); clear_stack(cfg); @@ -839,7 +888,7 @@ static gpointer obcfg_load(gpointer config, GError **error) FmXmlFile *old_xml = cfg->xml; cfg->xml = fm_xml_file_new(old_xml); - g_object_unref(old_xml); + fm_xml_file_free(old_xml); g_list_free_full(cfg->actions, (GDestroyNotify)lkxeys_action_free); g_list_free_full(cfg->execs, (GDestroyNotify)lkxeys_app_free); cfg->actions = NULL; @@ -847,6 +896,7 @@ static gpointer obcfg_load(gpointer config, GError **error) cfg->keyboard = NULL; } else { const char *session; + char *labwc_path; /* prepare data */ cfg = g_new0(ObXmlFile, 1); @@ -869,6 +919,7 @@ static gpointer obcfg_load(gpointer config, GError **error) session = g_getenv("GDMSESSION"); if (session == NULL) session = g_getenv("XDG_CURRENT_DESKTOP"); + labwc_path = g_build_filename(g_get_user_config_dir(), "labwc", "rc.xml", NULL); if (g_strcmp0(session, "Lubuntu") == 0) cfg->path = g_build_filename(g_get_user_config_dir(), "openbox", "lubuntu-rc.xml", NULL); @@ -878,19 +929,26 @@ static gpointer obcfg_load(gpointer config, GError **error) else if (g_strcmp0(session, "LXDE-pi") == 0) cfg->path = g_build_filename(g_get_user_config_dir(), "openbox", "lxde-pi-rc.xml", NULL); + else if (session_is_labwc() || g_file_test(labwc_path, G_FILE_TEST_EXISTS)) { + cfg->path = labwc_path; + cfg->is_labwc = TRUE; + labwc_path = NULL; + } else cfg->path = g_build_filename(g_get_user_config_dir(), "openbox", "rc.xml", NULL); + g_free(labwc_path); } - /* try to load ~/.config/openbox/$xml */ + /* try to load the user rc.xml first */ if (!g_file_get_contents(cfg->path, &contents, &len, NULL)) { - /* if it does not exist then try to load $XDG_SYSTEM_CONFDIR/openbox/rc.xml */ + /* if it does not exist then try system rc.xml in XDG config dirs */ const gchar * const *dirs; char *path = NULL; for (dirs = g_get_system_config_dirs(); dirs[0]; dirs++) { - path = g_build_filename(dirs[0], "openbox", "rc.xml", NULL); + path = g_build_filename(dirs[0], cfg->is_labwc ? "labwc" : "openbox", + "rc.xml", NULL); if (g_file_get_contents(path, &contents, &len, NULL)) break; g_free(path); @@ -923,7 +981,7 @@ static gboolean obcfg_save(gpointer config, GError **error) gsize len; gboolean ret = FALSE; - /* save as ~/.config/openbox/$xml */ + /* save as ~/.config/{openbox,labwc}/$xml */ contents = fm_xml_file_to_data(cfg->xml, &len, error); if (contents) { /* workaround on libfm-extra bug on save data without DTD */ @@ -934,7 +992,7 @@ static gboolean obcfg_save(gpointer config, GError **error) g_free(contents); } if (ret) - ret = restart_openbox(error); + ret = cfg->is_labwc ? reconfigure_labwc(error) : restart_openbox(error); return ret; } @@ -974,7 +1032,7 @@ static FmXmlFileItem *make_new_xml_item(ObXmlFile *cfg, LXHotkeyAttr *opt, LXHotkeyAttr *act = NULL; if (is_action) { - item = fm_xml_file_item_new(ObXmlFile_action); + item = fm_xml_file_item_new(cfg->xml, ObXmlFile_action); fm_xml_file_item_set_attribute(item, "name", opt->name); } else { FmXmlFileTag tag = FM_XML_FILE_TAG_NOT_HANDLED; @@ -990,7 +1048,7 @@ static FmXmlFileItem *make_new_xml_item(ObXmlFile *cfg, LXHotkeyAttr *opt, cfg->added_tags = g_list_prepend(cfg->added_tags, GUINT_TO_POINTER(tag)); } else tag = GPOINTER_TO_UINT(l->data); - item = fm_xml_file_item_new(tag); + item = fm_xml_file_item_new(cfg->xml, tag); if (opt->values) fm_xml_file_item_append_text(item, opt->values->data, -1, FALSE); } @@ -1016,7 +1074,7 @@ static FmXmlFileItem *make_new_xml_binding(ObXmlFile *cfg, GList *actions, const gchar *accel, GList **opts, const gchar *exec) { - FmXmlFileItem *binding = fm_xml_file_item_new(ObXmlFile_keybind); + FmXmlFileItem *binding = fm_xml_file_item_new(cfg->xml, ObXmlFile_keybind); FmXmlFileItem *item, *opt; char *obkey = key_to_obkey(accel); @@ -1026,10 +1084,10 @@ static FmXmlFileItem *make_new_xml_binding(ObXmlFile *cfg, GList *actions, if (exec) { /* make exec..opts.. instead of .... */ - item = fm_xml_file_item_new(ObXmlFile_action); + item = fm_xml_file_item_new(cfg->xml, ObXmlFile_action); fm_xml_file_item_set_attribute(item, "name", "Execute"); fm_xml_file_item_append_child(binding, item); - opt = fm_xml_file_item_new(ObXmlFile_command); + opt = fm_xml_file_item_new(cfg->xml, ObXmlFile_command); fm_xml_file_item_append_text(opt, exec, -1, FALSE); fm_xml_file_item_append_child(item, opt); } @@ -1395,8 +1453,6 @@ static GList *obcfg_get_app_options(gpointer config, GError **error) } -FM_DEFINE_MODULE(lxhotkey, Openbox) - LXHotkeyPluginInit fm_module_init_lxhotkey = { .load = obcfg_load, .save = obcfg_save, diff --git a/plugins/openbox/xml_compat.c b/plugins/openbox/xml_compat.c new file mode 100644 index 0000000..64599c8 --- /dev/null +++ b/plugins/openbox/xml_compat.c @@ -0,0 +1,425 @@ +#include "xml_compat.h" + +#include +#include + +struct _FmXmlFileItem { + FmXmlFile *file; + xmlNodePtr node; +}; + +typedef struct { + char *name; + FmXmlFileHandler handler; +} FmXmlTagDef; + +struct _FmXmlFile { + GPtrArray *tags; + GHashTable *tag_by_name; + xmlDocPtr doc; +}; + +static void free_tag_def(gpointer data) +{ + FmXmlTagDef *tag = data; + + if (tag == NULL) + return; + g_free(tag->name); + g_free(tag); +} + +static GQuark lxhotkey_xml_error_quark(void) +{ + return g_quark_from_static_string("lxhotkey-xml-error"); +} + +static void init_registry(FmXmlFile *file) +{ + FmXmlTagDef *tag; + + file->tags = g_ptr_array_new_with_free_func(free_tag_def); + file->tag_by_name = g_hash_table_new(g_str_hash, g_str_equal); + + g_ptr_array_add(file->tags, NULL); + + tag = g_new0(FmXmlTagDef, 1); + tag->name = g_strdup("#text"); + g_ptr_array_add(file->tags, tag); +} + +static FmXmlFileItem *wrap_node(FmXmlFile *file, xmlNodePtr node) +{ + FmXmlFileItem *item; + + if (node == NULL) + return NULL; + item = node->_private; + if (item != NULL) + return item; + item = g_new0(FmXmlFileItem, 1); + item->file = file; + item->node = node; + node->_private = item; + return item; +} + +static void free_wrappers(xmlNodePtr node) +{ + xmlNodePtr child; + xmlNodePtr next; + + for (child = node ? node->children : NULL; child; child = next) { + next = child->next; + free_wrappers(child); + } + if (node && node->_private) { + g_free(node->_private); + node->_private = NULL; + } +} + +static void free_doc(FmXmlFile *file) +{ + if (file->doc == NULL) + return; + free_wrappers(xmlDocGetRootElement(file->doc)); + xmlFreeDoc(file->doc); + file->doc = NULL; +} + +static FmXmlTagDef *get_tag_def(FmXmlFile *file, FmXmlFileTag tag) +{ + if (file == NULL || tag >= file->tags->len) + return NULL; + return g_ptr_array_index(file->tags, tag); +} + +static FmXmlFileTag get_tag_by_name(FmXmlFile *file, const char *name) +{ + gpointer value; + + if (name == NULL) + return FM_XML_FILE_TAG_NOT_HANDLED; + value = g_hash_table_lookup(file->tag_by_name, name); + if (value == NULL) + return FM_XML_FILE_TAG_NOT_HANDLED; + return GPOINTER_TO_UINT(value); +} + +static gboolean text_is_blank(xmlNodePtr node) +{ + const xmlChar *content; + + if (node == NULL || node->type != XML_TEXT_NODE) + return FALSE; + content = node->content; + if (content == NULL) + return TRUE; + for (; *content; content++) + if (!g_ascii_isspace(*content)) + return FALSE; + return TRUE; +} + +static GList *node_children(FmXmlFile *file, xmlNodePtr node) +{ + GList *children = NULL; + xmlNodePtr child; + + for (child = node ? node->children : NULL; child; child = child->next) { + if (child->type != XML_ELEMENT_NODE && child->type != XML_TEXT_NODE) + continue; + if (text_is_blank(child)) + continue; + children = g_list_append(children, wrap_node(file, child)); + } + return children; +} + +static gboolean run_handlers(FmXmlFile *file, xmlNodePtr node, GError **error, + gpointer user_data) +{ + xmlNodePtr child; + xmlNodePtr next; + FmXmlFileItem *item; + FmXmlTagDef *tag; + FmXmlFileTag tag_id; + GList *children; + char **attr_names = NULL; + char **attr_values = NULL; + guint n_attrs = 0; + guint i = 0; + xmlAttrPtr attr; + + if (node == NULL) + return TRUE; + for (child = node->children; child; child = next) { + next = child->next; + if (child->type == XML_ELEMENT_NODE) { + if (!run_handlers(file, child, error, user_data)) + return FALSE; + } + } + if (node->type != XML_ELEMENT_NODE) + return TRUE; + + tag_id = get_tag_by_name(file, (const char *)node->name); + tag = get_tag_def(file, tag_id); + if (tag == NULL || tag->handler == NULL) + return TRUE; + + for (attr = node->properties; attr; attr = attr->next) + n_attrs++; + if (n_attrs > 0) { + attr_names = g_new0(char *, n_attrs + 1); + attr_values = g_new0(char *, n_attrs + 1); + for (attr = node->properties; attr; attr = attr->next, i++) { + xmlChar *value = xmlNodeListGetString(node->doc, attr->children, 1); + + attr_names[i] = g_strdup((const char *)attr->name); + attr_values[i] = g_strdup((const char *)(value ? value : BAD_CAST "")); + if (value) + xmlFree(value); + } + } + + item = wrap_node(file, node); + children = node_children(file, node); + if (!tag->handler(item, children, attr_names, attr_values, n_attrs, + (gint)xmlGetLineNo(node), 0, error, user_data)) { + g_list_free(children); + g_strfreev(attr_names); + g_strfreev(attr_values); + return FALSE; + } + g_list_free(children); + g_strfreev(attr_names); + g_strfreev(attr_values); + return TRUE; +} + +FmXmlFile *fm_xml_file_new(FmXmlFile *other) +{ + FmXmlFile *file = g_new0(FmXmlFile, 1); + guint i; + + init_registry(file); + if (other == NULL) + return file; + for (i = 2; i < other->tags->len; i++) { + FmXmlTagDef *old = g_ptr_array_index(other->tags, i); + FmXmlTagDef *tag = g_new0(FmXmlTagDef, 1); + + tag->name = g_strdup(old->name); + tag->handler = old->handler; + g_ptr_array_add(file->tags, tag); + g_hash_table_insert(file->tag_by_name, tag->name, GUINT_TO_POINTER(i)); + } + return file; +} + +void fm_xml_file_free(FmXmlFile *file) +{ + if (file == NULL) + return; + free_doc(file); + g_ptr_array_free(file->tags, TRUE); + g_hash_table_destroy(file->tag_by_name); + g_free(file); +} + +FmXmlFileTag fm_xml_file_set_handler(FmXmlFile *file, const char *name, + FmXmlFileHandler handler, gboolean recursive, + gpointer unused) +{ + FmXmlFileTag tag_id; + FmXmlTagDef *tag; + + (void)recursive; + (void)unused; + + tag_id = get_tag_by_name(file, name); + if (tag_id != FM_XML_FILE_TAG_NOT_HANDLED) { + tag = get_tag_def(file, tag_id); + tag->handler = handler; + return tag_id; + } + + tag = g_new0(FmXmlTagDef, 1); + tag->name = g_strdup(name); + tag->handler = handler; + tag_id = file->tags->len; + g_ptr_array_add(file->tags, tag); + g_hash_table_insert(file->tag_by_name, tag->name, GUINT_TO_POINTER(tag_id)); + return tag_id; +} + +const char *fm_xml_file_get_tag_name(FmXmlFile *file, FmXmlFileTag tag) +{ + FmXmlTagDef *tag_def = get_tag_def(file, tag); + + return tag_def ? tag_def->name : NULL; +} + +gboolean fm_xml_file_parse_data(FmXmlFile *file, const char *contents, gsize len, + GError **error, gpointer user_data) +{ + xmlNodePtr root; + + free_doc(file); + xmlResetLastError(); + file->doc = xmlReadMemory(contents, (int)len, NULL, NULL, + XML_PARSE_NOBLANKS | XML_PARSE_NONET); + if (file->doc == NULL) { + const xmlError *xml_error = xmlGetLastError(); + + g_set_error(error, lxhotkey_xml_error_quark(), 0, + "%s", xml_error && xml_error->message ? xml_error->message + : "Failed to parse XML."); + return FALSE; + } + root = xmlDocGetRootElement(file->doc); + if (root == NULL) { + g_set_error_literal(error, lxhotkey_xml_error_quark(), 0, + "Failed to parse XML."); + return FALSE; + } + wrap_node(file, root); + return run_handlers(file, root, error, user_data); +} + +FmXmlFileItem *fm_xml_file_finish_parse(FmXmlFile *file, GError **error) +{ + (void)error; + return wrap_node(file, xmlDocGetRootElement(file->doc)); +} + +char *fm_xml_file_to_data(FmXmlFile *file, gsize *len, GError **error) +{ + xmlChar *buffer = NULL; + int size = 0; + char *copy; + + (void)error; + xmlDocDumpFormatMemoryEnc(file->doc, &buffer, &size, "UTF-8", 1); + if (buffer == NULL) + return NULL; + copy = g_strndup((const char *)buffer, size); + xmlFree(buffer); + if (len) + *len = (gsize)strlen(copy); + return copy; +} + +FmXmlFileTag fm_xml_file_item_get_tag(FmXmlFileItem *item) +{ + if (item == NULL || item->node == NULL) + return FM_XML_FILE_TAG_NOT_HANDLED; + if (item->node->type == XML_TEXT_NODE) + return FM_XML_FILE_TEXT; + return get_tag_by_name(item->file, (const char *)item->node->name); +} + +const char *fm_xml_file_item_get_tag_name(FmXmlFileItem *item) +{ + if (item == NULL || item->node == NULL) + return NULL; + if (item->node->type == XML_TEXT_NODE) + return "#text"; + return (const char *)item->node->name; +} + +GList *fm_xml_file_item_get_children(FmXmlFileItem *item) +{ + if (item == NULL) + return NULL; + return node_children(item->file, item->node); +} + +FmXmlFileItem *fm_xml_file_item_get_parent(FmXmlFileItem *item) +{ + if (item == NULL || item->node == NULL || item->node->parent == NULL) + return NULL; + if (item->node->parent->type == XML_DOCUMENT_NODE) + return NULL; + return wrap_node(item->file, item->node->parent); +} + +FmXmlFileItem *fm_xml_file_item_find_child(FmXmlFileItem *item, FmXmlFileTag tag) +{ + xmlNodePtr child; + + if (item == NULL) + return NULL; + for (child = item->node->children; child; child = child->next) { + FmXmlFileItem *wrapped; + + if (child->type != XML_ELEMENT_NODE && child->type != XML_TEXT_NODE) + continue; + if (text_is_blank(child)) + continue; + wrapped = wrap_node(item->file, child); + if (fm_xml_file_item_get_tag(wrapped) == tag) + return wrapped; + } + return NULL; +} + +const char *fm_xml_file_item_get_data(FmXmlFileItem *item, gpointer unused) +{ + (void)unused; + if (item == NULL || item->node == NULL) + return NULL; + return (const char *)item->node->content; +} + +FmXmlFileItem *fm_xml_file_item_new(FmXmlFile *file, FmXmlFileTag tag) +{ + const char *name = fm_xml_file_get_tag_name(file, tag); + xmlNodePtr node; + + if (name == NULL || tag == FM_XML_FILE_TEXT) + return NULL; + node = xmlNewNode(NULL, BAD_CAST name); + return wrap_node(file, node); +} + +void fm_xml_file_item_set_attribute(FmXmlFileItem *item, const char *name, + const char *value) +{ + if (item == NULL || item->node == NULL) + return; + xmlSetProp(item->node, BAD_CAST name, BAD_CAST value); +} + +void fm_xml_file_item_append_text(FmXmlFileItem *item, const char *text, gssize len, + gboolean escaped) +{ + xmlNodePtr node; + + (void)escaped; + if (item == NULL || item->node == NULL) + return; + if (len < 0) + len = (gssize)strlen(text); + node = xmlNewTextLen(BAD_CAST text, (int)len); + xmlAddChild(item->node, node); + wrap_node(item->file, node); +} + +void fm_xml_file_item_append_child(FmXmlFileItem *item, FmXmlFileItem *child) +{ + if (item == NULL || child == NULL) + return; + xmlAddChild(item->node, child->node); +} + +void fm_xml_file_item_destroy(FmXmlFileItem *item) +{ + if (item == NULL || item->node == NULL) + return; + xmlUnlinkNode(item->node); + free_wrappers(item->node); + xmlFreeNode(item->node); +} diff --git a/plugins/openbox/xml_compat.h b/plugins/openbox/xml_compat.h new file mode 100644 index 0000000..ead46c8 --- /dev/null +++ b/plugins/openbox/xml_compat.h @@ -0,0 +1,44 @@ +#ifndef LXHOTKEY_XML_COMPAT_H +#define LXHOTKEY_XML_COMPAT_H + +#include + +typedef struct _FmXmlFile FmXmlFile; +typedef struct _FmXmlFileItem FmXmlFileItem; +typedef guint FmXmlFileTag; + +#define FM_XML_FILE_TAG_NOT_HANDLED 0 +#define FM_XML_FILE_TEXT 1 + +typedef gboolean (*FmXmlFileHandler)(FmXmlFileItem *item, GList *children, + char * const *attribute_names, + char * const *attribute_values, + guint n_attributes, gint line, gint pos, + GError **error, gpointer user_data); + +FmXmlFile *fm_xml_file_new(FmXmlFile *other); +void fm_xml_file_free(FmXmlFile *file); +FmXmlFileTag fm_xml_file_set_handler(FmXmlFile *file, const char *name, + FmXmlFileHandler handler, gboolean recursive, + gpointer unused); +const char *fm_xml_file_get_tag_name(FmXmlFile *file, FmXmlFileTag tag); +gboolean fm_xml_file_parse_data(FmXmlFile *file, const char *contents, gsize len, + GError **error, gpointer user_data); +FmXmlFileItem *fm_xml_file_finish_parse(FmXmlFile *file, GError **error); +char *fm_xml_file_to_data(FmXmlFile *file, gsize *len, GError **error); + +FmXmlFileTag fm_xml_file_item_get_tag(FmXmlFileItem *item); +const char *fm_xml_file_item_get_tag_name(FmXmlFileItem *item); +GList *fm_xml_file_item_get_children(FmXmlFileItem *item); +FmXmlFileItem *fm_xml_file_item_get_parent(FmXmlFileItem *item); +FmXmlFileItem *fm_xml_file_item_find_child(FmXmlFileItem *item, FmXmlFileTag tag); +const char *fm_xml_file_item_get_data(FmXmlFileItem *item, gpointer unused); +FmXmlFileItem *fm_xml_file_item_new(FmXmlFile *file, FmXmlFileTag tag); +void fm_xml_file_item_set_attribute(FmXmlFileItem *item, const char *name, + const char *value); +void fm_xml_file_item_append_text(FmXmlFileItem *item, const char *text, gssize len, + gboolean escaped); +void fm_xml_file_item_append_child(FmXmlFileItem *item, FmXmlFileItem *child); +void fm_xml_file_item_destroy(FmXmlFileItem *item); + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index 6900326..9f8c4bd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,13 +4,28 @@ bin_PROGRAMS = lxhotkey lxhotkey_CPPFLAGS = \ -I$(top_srcdir) \ - -DPACKAGE_PLUGINS_DIR=\""$(libdir)/lxhotkey"\" \ - -DPACKAGE_LOCALE_DIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/plugins/openbox \ + -DPACKAGE_LOCALE_DIR=\"$(prefix)/$(DATADIRNAME)/locale\" \ $(PACKAGE_CFLAGS) -lxhotkey_SOURCES = lxhotkey.c +lxhotkey_SOURCES = \ + lxhotkey.c \ + $(top_srcdir)/plugins/openbox/openbox.c \ + $(top_srcdir)/plugins/openbox/xml_compat.c +if WITH_GTK +lxhotkey_CPPFLAGS += \ + -I$(top_srcdir)/plugins/gtk \ + -DWITH_GTK=1 \ + $(GTK_CFLAGS) +lxhotkey_SOURCES += \ + $(top_srcdir)/plugins/gtk/gtk.c \ + $(top_srcdir)/plugins/gtk/edit.c +lxhotkey_LDADD = $(PACKAGE_LIBS) $(GTK_LIBS) +else lxhotkey_LDADD = $(PACKAGE_LIBS) +endif lxhotkey_includedir = $(includedir)/lxhotkey lxhotkey_include_HEADERS = lxhotkey.h + +install-exec-hook: + ln -sf lxhotkey "$(DESTDIR)$(bindir)/labhotkey" + +uninstall-hook: + rm -f "$(DESTDIR)$(bindir)/labhotkey" diff --git a/src/lxhotkey.c b/src/lxhotkey.c index a7512a7..e704d6a 100644 --- a/src/lxhotkey.c +++ b/src/lxhotkey.c @@ -30,6 +30,8 @@ #include #include +#include +#include #include #ifdef HAVE_LIBUNISTRING @@ -124,6 +125,19 @@ static gchar *get_property (Display *disp, Window win, /*{{{*/ return ret; } /*}}}*/ +static gboolean string_mentions_labwc(const char *value) +{ + char *lower; + gboolean ret; + + if (value == NULL || value[0] == '\0') + return FALSE; + lower = g_ascii_strdown(value, -1); + ret = g_strrstr(lower, "labwc") != NULL; + g_free(lower); + return ret; +} + static gchar *get_wm_info(void) { /* this code is taken from wmctrl utility, adapted @@ -132,6 +146,12 @@ static gchar *get_wm_info(void) Window *sup_window = NULL; gchar *wm_name = NULL; + if (g_getenv("LABWC_PID") != NULL + || string_mentions_labwc(g_getenv("DESKTOP_SESSION")) + || string_mentions_labwc(g_getenv("GDMSESSION")) + || string_mentions_labwc(g_getenv("XDG_CURRENT_DESKTOP"))) + return g_strdup("labwc"); + if (!(disp = XOpenDisplay(NULL))) { fputs("Cannot open display.\n", stderr); return NULL; @@ -143,6 +163,7 @@ static gchar *get_wm_info(void) XA_CARDINAL, "_WIN_SUPPORTING_WM_CHECK", NULL))) { fputs("Cannot get window manager info properties.\n" "(_NET_SUPPORTING_WM_CHECK or _WIN_SUPPORTING_WM_CHECK)\n", stderr); + XCloseDisplay(disp); return NULL; } } @@ -165,6 +186,7 @@ static gchar *get_wm_info(void) } } + XCloseDisplay(disp); return wm_name; } @@ -175,6 +197,9 @@ static gboolean test_X_is_local(void) int Xnum; char lockfile[32], socket[32]; + if (g_getenv("WAYLAND_DISPLAY") != NULL) + return TRUE; + if (display) display = strchr(display, ':'); if (!display || display[1] < '0' || display[1] > '9') @@ -198,19 +223,14 @@ typedef struct LXHotkeyPlugin { static LXHotkeyPlugin *plugins = NULL; -FM_MODULE_DEFINE_TYPE(lxhotkey, LXHotkeyPluginInit, 1) -static gboolean fm_module_callback_lxhotkey(const char *name, gpointer init, int ver) +static void register_plugin(const char *name, LXHotkeyPluginInit *init) { - LXHotkeyPlugin *plugin; + LXHotkeyPlugin *plugin = g_new(LXHotkeyPlugin, 1); - /* ignore ver for now, only 1 exists */ - /* FIXME: need to check for duplicates? */ - plugin = g_new(LXHotkeyPlugin, 1); plugin->next = plugins; plugin->name = g_strdup(name); plugin->t = init; plugins = plugin; - return TRUE; } @@ -223,19 +243,25 @@ typedef struct LXHotkeyGUIPlugin { static LXHotkeyGUIPlugin *gui_plugins = NULL; -FM_MODULE_DEFINE_TYPE(lxhotkey_gui, LXHotkeyGUIPluginInit, 1) -static gboolean fm_module_callback_lxhotkey_gui(const char *name, gpointer init, int ver) +static void register_gui_plugin(const char *name, LXHotkeyGUIPluginInit *init) { - LXHotkeyGUIPlugin *plugin; + LXHotkeyGUIPlugin *plugin = g_new(LXHotkeyGUIPlugin, 1); - /* ignore ver for now, only 1 exists */ - /* FIXME: need to check for duplicates? */ - plugin = g_new(LXHotkeyGUIPlugin, 1); plugin->next = gui_plugins; plugin->name = g_strdup(name); plugin->t = init; gui_plugins = plugin; - return TRUE; +} + +static void register_builtin_plugins(gboolean do_gui) +{ + register_plugin("Openbox", &fm_module_init_lxhotkey); +#ifdef WITH_GTK + if (do_gui) + register_gui_plugin("gtk", &fm_module_init_lxhotkey_gui); +#else + (void)do_gui; +#endif } @@ -478,16 +504,10 @@ int main(int argc, char *argv[]) cmd += 4; } - /* init LibFM and FmModule */ - fm_init(NULL); - fm_modules_add_directory(PACKAGE_PLUGINS_DIR); - fm_module_register_lxhotkey(); - if (do_gui) - fm_module_register_lxhotkey_gui(); + register_builtin_plugins(do_gui); LXKEYS_ERROR = g_quark_from_static_string("lxhotkey-error"); - CHECK_MODULES(); if (do_gui) /* load GUI plugin if requested */ for (gui_plugin = gui_plugins; gui_plugin; gui_plugin = gui_plugin->next) if (g_ascii_strcasecmp(gui_plugin->name, cmd) == 0) @@ -511,6 +531,10 @@ int main(int argc, char *argv[]) for (plugin = plugins; plugin; plugin = plugin->next) if (g_ascii_strcasecmp(plugin->name, wm_name) == 0) break; + if (!plugin && g_ascii_strcasecmp(wm_name, "labwc") == 0) + for (plugin = plugins; plugin; plugin = plugin->next) + if (g_ascii_strcasecmp(plugin->name, "Openbox") == 0) + break; if (!plugin) { g_set_error(&error, LXKEYS_ERROR, LXKEYS_NOT_SUPPORTED, _("Could not find a plugin for window manager %s."), wm_name); @@ -687,9 +711,6 @@ _exit: fprintf(stderr, "LXHotkey: %s\n", error->message); g_error_free(error); } - fm_module_unregister_type("lxhotkey"); - if (do_gui) - fm_module_unregister_type("lxhotkey_gui"); while (plugins) { plugin = plugins; plugins = plugin->next; diff --git a/src/lxhotkey.h b/src/lxhotkey.h index b123c74..fbeced6 100644 --- a/src/lxhotkey.h +++ b/src/lxhotkey.h @@ -21,7 +21,7 @@ #ifndef _LXKEYS_H_ #define _LXKEYS_H_ 1 -#include +#include G_BEGIN_DECLS