Add a description (for GUI tooltip) to LXHotkeyAttr.
[lxde/lxhotkey.git] / src / lxhotkey.c
1 /*
2 * Copyright (C) 2016 Andriy Grytsenko <andrej@rep.kiev.ua>
3 *
4 * This file is a part of LXHotkey project.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include "lxhotkey.h"
26
27 #include <glib/gi18n.h>
28
29 #include <X11/Xlib.h>
30 #include <X11/Xatom.h>
31
32 #include <stdlib.h>
33
34 #ifdef HAVE_LIBUNISTRING
35 # include <unistdio.h>
36 # define ulc_printf(...) ulc_fprintf(stdout,__VA_ARGS__)
37 #else
38 # define ulc_printf printf
39 #endif
40
41 /* for errors */
42 static GQuark LXKEYS_ERROR;
43 typedef enum {
44 LXKEYS_BAD_ARGS, /* invalid commandline arguments */
45 LXKEYS_NOT_SUPPORTED /* operation not supported */
46 } LXHotkeyError;
47
48 /* simple functions to manage LXHotkeyAttr data type */
49 static inline LXHotkeyAttr *lxhotkey_attr_new(void)
50 {
51 return g_slice_new0(LXHotkeyAttr);
52 }
53
54 #define free_actions(acts) g_list_free_full(acts, (GDestroyNotify)lkxeys_attr_free)
55
56 static void lkxeys_attr_free(LXHotkeyAttr *data)
57 {
58 g_free(data->name);
59 g_list_free_full(data->values, g_free);
60 free_actions(data->subopts);
61 g_free(data->desc);
62 g_slice_free(LXHotkeyAttr, data);
63 }
64
65 #define NONULL(a) (a) ? ((char *)a) : ""
66
67 /* this function is taken from wmctrl utility */
68 #define MAX_PROPERTY_VALUE_LEN 4096
69
70 static gchar *get_property (Display *disp, Window win, /*{{{*/
71 Atom xa_prop_type, gchar *prop_name, unsigned long *size)
72 {
73 Atom xa_prop_name;
74 Atom xa_ret_type;
75 int ret_format;
76 unsigned long ret_nitems;
77 unsigned long ret_bytes_after;
78 unsigned long tmp_size;
79 unsigned char *ret_prop;
80 gchar *ret;
81
82 xa_prop_name = XInternAtom(disp, prop_name, False);
83
84 /* MAX_PROPERTY_VALUE_LEN / 4 explanation (XGetWindowProperty manpage):
85 *
86 * long_length = Specifies the length in 32-bit multiples of the
87 * data to be retrieved.
88 *
89 * NOTE: see
90 * http://mail.gnome.org/archives/wm-spec-list/2003-March/msg00067.html
91 * In particular:
92 *
93 * When the X window system was ported to 64-bit architectures, a
94 * rather peculiar design decision was made. 32-bit quantities such
95 * as Window IDs, atoms, etc, were kept as longs in the client side
96 * APIs, even when long was changed to 64 bits.
97 *
98 */
99 if (XGetWindowProperty(disp, win, xa_prop_name, 0, MAX_PROPERTY_VALUE_LEN / 4, False,
100 xa_prop_type, &xa_ret_type, &ret_format,
101 &ret_nitems, &ret_bytes_after, &ret_prop) != Success) {
102 return NULL;
103 }
104
105 if (xa_ret_type != xa_prop_type) {
106 XFree(ret_prop);
107 return NULL;
108 }
109
110 /* null terminate the result to make string handling easier */
111 tmp_size = (ret_format / 8) * ret_nitems;
112 /* Correct 64 Architecture implementation of 32 bit data */
113 if(ret_format==32) tmp_size *= sizeof(long)/4;
114 ret = g_malloc(tmp_size + 1);
115 memcpy(ret, ret_prop, tmp_size);
116 ret[tmp_size] = '\0';
117
118 if (size) {
119 *size = tmp_size;
120 }
121
122 XFree(ret_prop);
123 return ret;
124 } /*}}}*/
125
126 static gchar *get_wm_info(void)
127 {
128 /* this code is taken from wmctrl utility, adapted
129 Copyright (C) 2003, Tomas Styblo <tripie@cpan.org> */
130 Display *disp;
131 Window *sup_window = NULL;
132 gchar *wm_name = NULL;
133
134 if (!(disp = XOpenDisplay(NULL))) {
135 fputs("Cannot open display.\n", stderr);
136 return NULL;
137 }
138
139 if (!(sup_window = (Window *)get_property(disp, DefaultRootWindow(disp),
140 XA_WINDOW, "_NET_SUPPORTING_WM_CHECK", NULL))) {
141 if (!(sup_window = (Window *)get_property(disp, DefaultRootWindow(disp),
142 XA_CARDINAL, "_WIN_SUPPORTING_WM_CHECK", NULL))) {
143 fputs("Cannot get window manager info properties.\n"
144 "(_NET_SUPPORTING_WM_CHECK or _WIN_SUPPORTING_WM_CHECK)\n", stderr);
145 return NULL;
146 }
147 }
148
149 /* WM_NAME */
150 if (!(wm_name = get_property(disp, *sup_window,
151 XInternAtom(disp, "UTF8_STRING", False), "_NET_WM_NAME", NULL))) {
152 if (!(wm_name = get_property(disp, *sup_window,
153 XA_STRING, "_NET_WM_NAME", NULL))) {
154 fputs("Cannot get name of the window manager (_NET_WM_NAME).\n", stderr);
155 } else {
156 gchar *_wm_name = wm_name;
157
158 wm_name = g_locale_to_utf8(_wm_name, -1, NULL, NULL, NULL);
159 if (wm_name)
160 g_free(_wm_name);
161 else
162 /* Cannot convert string from locale charset to UTF-8. */
163 wm_name = _wm_name;
164 }
165 }
166
167 return wm_name;
168 }
169
170 /* test if we are called from X which is local */
171 static gboolean test_X_is_local(void)
172 {
173 return TRUE; // FIXME: TODO!
174 }
175
176
177 /* WM plugins */
178 typedef struct LXHotkeyPlugin {
179 struct LXHotkeyPlugin *next;
180 gchar *name;
181 LXHotkeyPluginInit *t;
182 } LXHotkeyPlugin;
183
184 static LXHotkeyPlugin *plugins = NULL;
185
186 FM_MODULE_DEFINE_TYPE(lxhotkey, LXHotkeyPluginInit, 1)
187 static gboolean fm_module_callback_lxhotkey(const char *name, gpointer init, int ver)
188 {
189 LXHotkeyPlugin *plugin;
190
191 /* ignore ver for now, only 1 exists */
192 /* FIXME: need to check for duplicates? */
193 plugin = g_new(LXHotkeyPlugin, 1);
194 plugin->next = plugins;
195 plugin->name = g_strdup(name);
196 plugin->t = init;
197 plugins = plugin;
198 return TRUE;
199 }
200
201
202 /* GUI plugins */
203 typedef struct LXHotkeyGUIPlugin {
204 struct LXHotkeyGUIPlugin *next;
205 gchar *name;
206 LXHotkeyGUIPluginInit *t;
207 } LXHotkeyGUIPlugin;
208
209 static LXHotkeyGUIPlugin *gui_plugins = NULL;
210
211 FM_MODULE_DEFINE_TYPE(lxhotkey_gui, LXHotkeyGUIPluginInit, 1)
212 static gboolean fm_module_callback_lxhotkey_gui(const char *name, gpointer init, int ver)
213 {
214 LXHotkeyGUIPlugin *plugin;
215
216 /* ignore ver for now, only 1 exists */
217 /* FIXME: need to check for duplicates? */
218 plugin = g_new(LXHotkeyGUIPlugin, 1);
219 plugin->next = gui_plugins;
220 plugin->name = g_strdup(name);
221 plugin->t = init;
222 gui_plugins = plugin;
223 return TRUE;
224 }
225
226
227 static int _print_help(const char *cmd)
228 {
229 printf(_("Usage: %s global [<action>] - show keys bound to action(s)\n"), cmd);
230 printf(_(" %s global <action> <key> - bind a key to the action\n"), cmd);
231 printf(_(" %s app [<exec>] - show keys bound to exec line\n"), cmd);
232 printf(_(" %s app <exec> <key> - bind a key to some exec line\n"), cmd);
233 printf(_(" %s app <exec> -- - unbind all keys from exec line\n"), cmd);
234 printf(_(" %s show <key> - show the action bound to a key\n"), cmd);
235 printf(_(" %s --gui=<type> - start with GUI\n"), cmd);
236 return 0;
237 }
238
239 /* convert text line to LXHotkeyAttr list
240 text may be formatted like this:
241 startupnotify=yes:attr1=val1:attr2=val2&action=val
242 any ':','&','\' in any value should be escaped with '\' */
243 static GList *actions_from_str(const char *line, GError **error)
244 {
245 GString *str = g_string_sized_new(16);
246 LXHotkeyAttr *data = NULL, *attr = NULL;
247 GList *list;
248
249 data = lxhotkey_attr_new();
250 list = g_list_prepend(NULL, data);
251 for (; *line; line++) {
252 switch (*line) {
253 case '=':
254 if (!data->name) { /* this is new data value */
255 if (str->len == 0)
256 goto _empty_name;
257 data->name = g_strdup(str->str);
258 g_string_truncate(str, 0);
259 } else if (attr && !attr->name) { /* this is an option */
260 if (str->len == 0)
261 goto _empty_opt;
262 attr->name = g_strdup(str->str);
263 g_string_truncate(str, 0);
264 } else /* '=' in value, continue processing */
265 g_string_append_c(str, *line);
266 break;
267 case ':':
268 if (!data->name) {
269 if (str->len == 0) /* empty action name */
270 goto _empty_name;
271 data->name = g_strdup(str->str);
272 } else if (attr) { /* finish previous attr */
273 if (!attr->name) {
274 if (str->len == 0)
275 goto _empty_opt;
276 attr->name = g_strdup(str->str);
277 } else
278 attr->values = g_list_prepend(NULL, g_strdup(str->str));
279 } else /* got value for the action */
280 data->values = g_list_prepend(NULL, g_strdup(str->str));
281 g_string_truncate(str, 0);
282 attr = lxhotkey_attr_new();
283 data->subopts = g_list_prepend(data->subopts, attr);
284 break;
285 case '&':
286 if (!data->name) {
287 if (str->len == 0) /* empty action name */
288 goto _empty_name;
289 data->name = g_strdup(str->str);
290 } else if (attr) { /* finish last attr */
291 if (!attr->name) {
292 if (str->len == 0)
293 goto _empty_opt;
294 attr->name = g_strdup(str->str);
295 } else
296 attr->values = g_list_prepend(NULL, g_strdup(str->str));
297 } else /* got value for the action */
298 data->values = g_list_prepend(NULL, g_strdup(str->str));
299 g_string_truncate(str, 0);
300 attr = NULL; /* previous action just finished */
301 data->subopts = g_list_reverse(data->subopts);
302 data = lxhotkey_attr_new();
303 list = g_list_prepend(list, data);
304 break;
305 case '\\':
306 /* do nothing, this was an escape char */
307 break;
308 default:
309 g_string_append_c(str, *line);
310 }
311 }
312 if (!data->name) {
313 if (str->len == 0) /* empty action name */
314 goto _empty_name;
315 data->name = g_strdup(str->str);
316 } else if (attr) { /* finish last attr */
317 if (!attr->name) {
318 if (str->len == 0)
319 goto _empty_opt;
320 attr->name = g_strdup(str->str);
321 } else
322 attr->values = g_list_prepend(NULL, g_strdup(str->str));
323 } else /* got value for the action */
324 data->values = g_list_prepend(NULL, g_strdup(str->str));
325 list = g_list_reverse(list);
326 g_string_free(str, TRUE);
327 return list;
328
329 _empty_opt:
330 g_set_error_literal(error, LXKEYS_ERROR, LXKEYS_BAD_ARGS, _("empty option name."));
331 goto _failed;
332 _empty_name:
333 g_set_error_literal(error, LXKEYS_ERROR, LXKEYS_BAD_ARGS, _("empty action name."));
334 _failed:
335 free_actions(list);
336 g_string_free(str, TRUE);
337 return NULL;
338 }
339
340 /* check if action list matches origin
341 if origin==NULL then error is possibly set already */
342 static gboolean validate_actions(const GList *act, const GList *origin,
343 const LXHotkeyAttr *action, gchar *wm_name,
344 GError **error)
345 {
346 const LXHotkeyAttr *data, *ordata;
347 const GList *l, *olist;
348 char *endptr;
349
350 if (!origin)
351 return FALSE;
352 if (action)
353 olist = action->subopts; /* action is ordata on recursion actually */
354 else
355 olist = origin;
356 while (act) {
357 data = act->data;
358 /* find corresponding descriptor in the origin list */
359 for (l = olist; l; l = l->next)
360 if (g_strcmp0(data->name, (ordata = l->data)->name) == 0)
361 break;
362 if (l == NULL) {
363 if (action)
364 g_set_error(error, LXKEYS_ERROR, LXKEYS_BAD_ARGS,
365 _("no matching option '%s' found for action '%s'."),
366 data->name, action->name);
367 else
368 g_set_error(error, LXKEYS_ERROR, LXKEYS_BAD_ARGS,
369 _("action '%s' isn't supported by WM %s."),
370 data->name, wm_name);
371 return FALSE;
372 }
373 /* if ordata->values isn't NULL and data->values isn't NULL
374 then it must match, ordata->values==NULL means anything matches */
375 if (data->values != NULL && ordata->values != NULL) {
376 for (l = ordata->values; l; l = l->next)
377 if (g_strcmp0(data->values->data, l->data) == 0 ||
378 /* check for numeric value too */
379 (strtol(data->values->data, &endptr, 10) < LONG_MAX &&
380 ((g_strcmp0(l->data, "#") == 0 && endptr[0] == '\0') ||
381 (g_strcmp0(l->data, "%") == 0 && endptr[0] == '%'))))
382 break;
383 if (l == NULL) {
384 if (action)
385 g_set_error(error, LXKEYS_ERROR, LXKEYS_BAD_ARGS,
386 _("value '%s' is not supported for option '%s'."),
387 (char *)data->values->data, data->name);
388 else
389 g_set_error(error, LXKEYS_ERROR, LXKEYS_BAD_ARGS,
390 _("value '%s' is not supported for action '%s'."),
391 (char *)data->values->data, data->name);
392 return FALSE;
393 }
394 }
395 /* for each data->subopts do recursion against ordata->subopts */
396 if (!data->subopts) ;
397 else if (ordata->has_actions) {
398 /* test against origin actions list, not suboptions */
399 if (!validate_actions(data->subopts, origin, NULL, wm_name, error))
400 return FALSE;
401 } else if (!ordata->subopts) {
402 g_set_error(error, LXKEYS_ERROR, LXKEYS_BAD_ARGS,
403 _("action '%s' does not support options."), data->name);
404 return FALSE;
405 } else if (!validate_actions(data->subopts, origin, ordata, wm_name, error))
406 return FALSE;
407 act = act->next;
408 }
409 return TRUE;
410 }
411
412 /* convert list to a text line */
413 //static char *actions_to_str(const GList *act)
414 //{
415 //}
416
417 static void print_suboptions(GList *sub, int indent)
418 {
419 indent += 3;
420 while (sub) {
421 LXHotkeyAttr *action = sub->data;
422 if (action->values && action->values->data)
423 printf("%*s%s=%s\n", indent, "", action->name,
424 action->values->data);
425 else
426 printf("%*s%s\n", indent, "", action->name);
427 print_suboptions(action->subopts, indent);
428 sub = sub->next;
429 }
430 }
431
432
433 int main(int argc, char *argv[])
434 {
435 const char *cmd;
436 gchar *wm_name;
437 LXHotkeyPlugin *plugin;
438 LXHotkeyGUIPlugin *gui_plugin = NULL;
439 int ret = 1; /* failure */
440 gpointer config = NULL;
441 GError *error = NULL;
442 gboolean do_gui = FALSE;
443
444 /* init localizations */
445 setlocale(LC_ALL, "");
446 #ifdef ENABLE_NLS
447 bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
448 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
449 textdomain(GETTEXT_PACKAGE);
450 #endif
451
452 /* parse args first, show help if "help" "-h" or "--help" */
453 if (argc < 2)
454 cmd = "--gui=gtk";
455 else
456 cmd = argv[1];
457 if (cmd[0] == '-' && cmd[1] == '-') /* skip leading "--" if given */
458 cmd += 2;
459 if (strcmp(cmd, "help") == 0 || (cmd[0] == '-' && cmd[1] == 'h'))
460 return _print_help(argv[0]);
461 if (memcmp(cmd, "gui=", 4) == 0) {
462 do_gui = TRUE;
463 cmd += 4;
464 }
465
466 if (!test_X_is_local()) {
467 fprintf(stderr, _("LXHotkey: sorry, cannot configure keys remotely.\n"));
468 return 1;
469 }
470
471 /* init LibFM and FmModule */
472 fm_init(NULL);
473 fm_modules_add_directory(PACKAGE_PLUGINS_DIR);
474 fm_module_register_lxhotkey();
475 if (do_gui)
476 fm_module_register_lxhotkey_gui();
477
478 LXKEYS_ERROR = g_quark_from_static_string("lxhotkey-error");
479
480 /* detect current WM and find a module for it */
481 wm_name = get_wm_info();
482 if (!wm_name)
483 goto _exit;
484 CHECK_MODULES();
485 for (plugin = plugins; plugin; plugin = plugin->next)
486 if (g_ascii_strcasecmp(plugin->name, wm_name) == 0)
487 break;
488 if (do_gui) /* load GUI plugin if requested */
489 for (gui_plugin = gui_plugins; gui_plugin; gui_plugin = gui_plugin->next)
490 if (g_ascii_strcasecmp(gui_plugin->name, cmd) == 0)
491 break;
492 if (!plugin) {
493 g_set_error(&error, LXKEYS_ERROR, LXKEYS_NOT_SUPPORTED,
494 _("Window manager %s isn't supported now, sorry."), wm_name);
495 goto _exit;
496 }
497
498 /* load the found module */
499 config = plugin->t->load(NULL, &error);
500 if (!config) {
501 g_prefix_error(&error, _("Problems loading configuration: "));
502 goto _exit;
503 }
504
505 if (do_gui) {
506 if (gui_plugin && gui_plugin->t->run)
507 gui_plugin->t->run(wm_name, plugin->t, config, &error);
508 else
509 g_set_error(&error, LXKEYS_ERROR, LXKEYS_NOT_SUPPORTED,
510 _("GUI type %s currently isn't supported."), cmd);
511 goto _exit;
512 }
513
514 /* doing commandline: call module function depending on args */
515 if (strcmp(argv[1], "global") == 0) { /* lxhotkey global ... */
516 if (argc > 3) { /* set */
517 LXHotkeyGlobal data;
518
519 if (plugin->t->set_wm_key == NULL)
520 goto _not_supported;
521 /* parse and validate actions */
522 data.actions = actions_from_str(argv[2], &error);
523 if (error ||
524 (plugin->t->get_wm_actions != NULL &&
525 !validate_actions(data.actions, plugin->t->get_wm_actions(config, &error),
526 NULL, wm_name, &error))) { /* invalid request */
527 g_prefix_error(&error, _("Invalid request: "));
528 goto _exit;
529 }
530 // FIXME: validate key
531 data.accel1 = argv[3];
532 data.accel2 = NULL;
533 if (argc > 4)
534 data.accel2 = argv[4];
535 if (!plugin->t->set_wm_key(config, &data, &error) ||
536 !plugin->t->save(config, &error)) {
537 g_prefix_error(&error, _("Problems saving configuration: "));
538 free_actions(data.actions);
539 goto _exit;
540 }
541 free_actions(data.actions);
542 } else { /* show by mask */
543 const char *mask = NULL;
544 GList *keys, *key;
545 LXHotkeyGlobal *data;
546 GList *act;
547 LXHotkeyAttr *action;
548
549 if (plugin->t->get_wm_keys == NULL)
550 goto _not_supported;
551 if (argc > 2)
552 mask = argv[2]; /* mask given */
553 keys = plugin->t->get_wm_keys(config, mask, NULL);
554 ulc_printf(" %-24s %s\n", _("ACTION(s)"), _("KEY(s)"));
555 for (key = keys; key; key = key->next) {
556 data = key->data;
557 for (act = data->actions; act; act = act->next)
558 {
559 action = act->data;
560 if (act != data->actions)
561 printf("+ %s\n", action->name);
562 else if (data->accel2)
563 ulc_printf("%-24s %s %s\n", action->name, data->accel1,
564 data->accel2);
565 else
566 ulc_printf("%-24s %s\n", action->name, data->accel1);
567 print_suboptions(action->subopts, 0);
568 }
569 }
570 g_list_free(keys);
571 }
572 } else if (strcmp(argv[1], "app") == 0) { /* lxhotkey app ... */
573 if (argc > 3) { /* set */
574 GList *keys = NULL;
575 LXHotkeyApp data;
576
577 if (plugin->t->set_app_key == NULL)
578 goto _not_supported;
579 /* check if exec already has a key */
580 if (plugin->t->get_app_keys != NULL)
581 keys = plugin->t->get_app_keys(config, argv[2], NULL);
582 if (keys && keys->next) /* mask in exec line isn't supported */
583 goto _not_supported;
584 // FIXME: validate key
585 data.accel2 = NULL;
586 if (strcmp(argv[3], "--") == 0) { /* remove all bindings */
587 data.accel1 = NULL;
588 } else if (keys && ((LXHotkeyApp *)keys->data)->accel1) {
589 data.accel1 = ((LXHotkeyApp *)keys->data)->accel1;
590 data.accel2 = argv[3];
591 } else {
592 data.accel1 = argv[3];
593 }
594 g_list_free(keys);
595 cmd = strchr(argv[2], '&');
596 if (cmd) {
597 data.options = actions_from_str(&cmd[1], &error);
598 if (error ||
599 (plugin->t->get_app_options != NULL &&
600 !validate_actions(data.options,
601 plugin->t->get_app_options(config, &error),
602 NULL, wm_name, &error))) { /* invalid request */
603 g_prefix_error(&error, _("Invalid request: "));
604 free_actions(data.options);
605 goto _exit;
606 }
607 data.exec = g_strndup(argv[2], cmd - argv[2]);
608 } else {
609 data.options = NULL;
610 data.exec = g_strdup(argv[2]);
611 }
612 // FIXME: validate exec
613 if (!plugin->t->set_app_key(config, &data, &error) ||
614 !plugin->t->save(config, &error)) {
615 g_prefix_error(&error, _("Problems saving configuration: "));
616 free_actions(data.options);
617 g_free(data.exec);
618 goto _exit;
619 }
620 free_actions(data.options);
621 g_free(data.exec);
622 } else { /* show by mask */
623 const char *mask = NULL;
624 GList *keys, *key;
625 LXHotkeyApp *data;
626
627 if (plugin->t->get_app_keys == NULL)
628 goto _not_supported;
629 if (argc > 2)
630 mask = argv[2]; /* mask given */
631 keys = plugin->t->get_app_keys(config, mask, NULL);
632 ulc_printf(" %-48s %s\n", _("EXEC"), _("KEY(s)"));
633 for (key = keys; key; key = key->next) {
634 data = key->data;
635 if (data->accel2)
636 ulc_printf("%-48s %s %s\n", data->exec, data->accel1,
637 data->accel2);
638 else
639 ulc_printf("%-48s %s\n", data->exec, data->accel1);
640 print_suboptions(data->options, 0);
641 }
642 g_list_free(keys);
643 }
644 } else if (strcmp(argv[1], "show") == 0) { /* lxhotkey show ... */
645 // FIXME: TODO!
646 } else
647 goto _exit;
648 ret = 0; /* success */
649 goto _exit;
650
651 _not_supported:
652 g_set_error_literal(&error, LXKEYS_ERROR, LXKEYS_NOT_SUPPORTED,
653 _("Requested operation isn't supported."));
654
655 /* release resources */
656 _exit:
657 if (config)
658 plugin->t->free(config);
659 if (error) {
660 /* if do_gui then show an alert window instead of stderr */
661 if (gui_plugin && gui_plugin->t->alert)
662 gui_plugin->t->alert(error);
663 else
664 fprintf(stderr, "LXHotkey: %s\n", error->message);
665 g_error_free(error);
666 }
667 fm_module_unregister_type("lxhotkey");
668 if (do_gui)
669 fm_module_unregister_type("lxhotkey_gui");
670 while (plugins) {
671 plugin = plugins;
672 plugins = plugin->next;
673 g_free(plugin->name);
674 g_free(plugin);
675 }
676 while (gui_plugins) {
677 gui_plugin = gui_plugins;
678 gui_plugins = gui_plugin->next;
679 g_free(gui_plugin->name);
680 g_free(gui_plugin);
681 }
682 g_free(wm_name);
683
684 return ret;
685 }