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