GTK+ plugin: implementation of edit window. No save available yet, just look.
[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)lxkeys_attr_free)
55
56 static void lxkeys_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 // const char *display = g_getenv("DISPLAY");
174
175 // if (!display)
176 // return FALSE;
177 // display = strchr(display, ':');
178 // return (display && display[1] == '0');
179 return TRUE; // FIXME: TODO!
180 }
181
182
183 /* WM plugins */
184 typedef struct LXHotkeyPlugin {
185 struct LXHotkeyPlugin *next;
186 gchar *name;
187 LXHotkeyPluginInit *t;
188 } LXHotkeyPlugin;
189
190 static LXHotkeyPlugin *plugins = NULL;
191
192 FM_MODULE_DEFINE_TYPE(lxhotkey, LXHotkeyPluginInit, 1)
193 static gboolean fm_module_callback_lxhotkey(const char *name, gpointer init, int ver)
194 {
195 LXHotkeyPlugin *plugin;
196
197 /* ignore ver for now, only 1 exists */
198 /* FIXME: need to check for duplicates? */
199 plugin = g_new(LXHotkeyPlugin, 1);
200 plugin->next = plugins;
201 plugin->name = g_strdup(name);
202 plugin->t = init;
203 plugins = plugin;
204 return TRUE;
205 }
206
207
208 /* GUI plugins */
209 typedef struct LXHotkeyGUIPlugin {
210 struct LXHotkeyGUIPlugin *next;
211 gchar *name;
212 LXHotkeyGUIPluginInit *t;
213 } LXHotkeyGUIPlugin;
214
215 static LXHotkeyGUIPlugin *gui_plugins = NULL;
216
217 FM_MODULE_DEFINE_TYPE(lxhotkey_gui, LXHotkeyGUIPluginInit, 1)
218 static gboolean fm_module_callback_lxhotkey_gui(const char *name, gpointer init, int ver)
219 {
220 LXHotkeyGUIPlugin *plugin;
221
222 /* ignore ver for now, only 1 exists */
223 /* FIXME: need to check for duplicates? */
224 plugin = g_new(LXHotkeyGUIPlugin, 1);
225 plugin->next = gui_plugins;
226 plugin->name = g_strdup(name);
227 plugin->t = init;
228 gui_plugins = plugin;
229 return TRUE;
230 }
231
232
233 static int _print_help(const char *cmd)
234 {
235 printf(_("Usage: %s global [<action>] - show keys bound to action(s)\n"), cmd);
236 printf(_(" %s global <action> <key> - bind a key to the action\n"), cmd);
237 printf(_(" %s app [<exec>] - show keys bound to exec line\n"), cmd);
238 printf(_(" %s app <exec> <key> - bind a key to some exec line\n"), cmd);
239 printf(_(" %s app <exec> -- - unbind all keys from exec line\n"), cmd);
240 printf(_(" %s show <key> - show the action bound to a key\n"), cmd);
241 printf(_(" %s --gui=<type> - start with GUI\n"), cmd);
242 return 0;
243 }
244
245 /* convert text line to LXHotkeyAttr list
246 text may be formatted like this:
247 startupnotify=yes:attr1=val1:attr2=val2&action=val
248 any ':','&','\' in any value should be escaped with '\' */
249 static GList *actions_from_str(const char *line, GError **error)
250 {
251 GString *str = g_string_sized_new(16);
252 LXHotkeyAttr *data = NULL, *attr = NULL;
253 GList *list;
254
255 data = lxhotkey_attr_new();
256 list = g_list_prepend(NULL, data);
257 for (; *line; line++) {
258 switch (*line) {
259 case '=':
260 if (!data->name) { /* this is new data value */
261 if (str->len == 0)
262 goto _empty_name;
263 data->name = g_strdup(str->str);
264 g_string_truncate(str, 0);
265 } else if (attr && !attr->name) { /* this is an option */
266 if (str->len == 0)
267 goto _empty_opt;
268 attr->name = g_strdup(str->str);
269 g_string_truncate(str, 0);
270 } else /* '=' in value, continue processing */
271 g_string_append_c(str, *line);
272 break;
273 case ':':
274 if (!data->name) {
275 if (str->len == 0) /* empty action name */
276 goto _empty_name;
277 data->name = g_strdup(str->str);
278 } else if (attr) { /* finish previous attr */
279 if (!attr->name) {
280 if (str->len == 0)
281 goto _empty_opt;
282 attr->name = g_strdup(str->str);
283 } else
284 attr->values = g_list_prepend(NULL, g_strdup(str->str));
285 } else /* got value for the action */
286 data->values = g_list_prepend(NULL, g_strdup(str->str));
287 g_string_truncate(str, 0);
288 attr = lxhotkey_attr_new();
289 data->subopts = g_list_prepend(data->subopts, attr);
290 break;
291 case '&':
292 if (!data->name) {
293 if (str->len == 0) /* empty action name */
294 goto _empty_name;
295 data->name = g_strdup(str->str);
296 } else if (attr) { /* finish last attr */
297 if (!attr->name) {
298 if (str->len == 0)
299 goto _empty_opt;
300 attr->name = g_strdup(str->str);
301 } else
302 attr->values = g_list_prepend(NULL, g_strdup(str->str));
303 } else /* got value for the action */
304 data->values = g_list_prepend(NULL, g_strdup(str->str));
305 g_string_truncate(str, 0);
306 attr = NULL; /* previous action just finished */
307 data->subopts = g_list_reverse(data->subopts);
308 data = lxhotkey_attr_new();
309 list = g_list_prepend(list, data);
310 break;
311 case '\\':
312 /* do nothing, this was an escape char */
313 break;
314 default:
315 g_string_append_c(str, *line);
316 }
317 }
318 if (!data->name) {
319 if (str->len == 0) /* empty action name */
320 goto _empty_name;
321 data->name = g_strdup(str->str);
322 } else if (attr) { /* finish last attr */
323 if (!attr->name) {
324 if (str->len == 0)
325 goto _empty_opt;
326 attr->name = g_strdup(str->str);
327 } else
328 attr->values = g_list_prepend(NULL, g_strdup(str->str));
329 } else /* got value for the action */
330 data->values = g_list_prepend(NULL, g_strdup(str->str));
331 list = g_list_reverse(list);
332 g_string_free(str, TRUE);
333 return list;
334
335 _empty_opt:
336 g_set_error_literal(error, LXKEYS_ERROR, LXKEYS_BAD_ARGS, _("empty option name."));
337 goto _failed;
338 _empty_name:
339 g_set_error_literal(error, LXKEYS_ERROR, LXKEYS_BAD_ARGS, _("empty action name."));
340 _failed:
341 free_actions(list);
342 g_string_free(str, TRUE);
343 return NULL;
344 }
345
346 /* check if action list matches origin
347 if origin==NULL then error is possibly set already */
348 static gboolean validate_actions(const GList *act, const GList *origin,
349 const LXHotkeyAttr *action, gchar *wm_name,
350 GError **error)
351 {
352 const LXHotkeyAttr *data, *ordata;
353 const GList *l, *olist;
354 char *endptr;
355
356 if (!origin)
357 return FALSE;
358 if (action)
359 olist = action->subopts; /* action is ordata on recursion actually */
360 else
361 olist = origin;
362 while (act) {
363 data = act->data;
364 /* find corresponding descriptor in the origin list */
365 for (l = olist; l; l = l->next)
366 if (g_strcmp0(data->name, (ordata = l->data)->name) == 0)
367 break;
368 if (l == NULL) {
369 if (action)
370 g_set_error(error, LXKEYS_ERROR, LXKEYS_BAD_ARGS,
371 _("no matching option '%s' found for action '%s'."),
372 data->name, action->name);
373 else
374 g_set_error(error, LXKEYS_ERROR, LXKEYS_BAD_ARGS,
375 _("action '%s' isn't supported by WM %s."),
376 data->name, wm_name);
377 return FALSE;
378 }
379 /* if ordata->values isn't NULL and data->values isn't NULL
380 then it must match, ordata->values==NULL means anything matches */
381 if (data->values != NULL && ordata->values != NULL) {
382 for (l = ordata->values; l; l = l->next)
383 if (g_strcmp0(data->values->data, l->data) == 0 ||
384 /* check for numeric value too */
385 (strtol(data->values->data, &endptr, 10) < LONG_MAX &&
386 ((g_strcmp0(l->data, "#") == 0 && endptr[0] == '\0') ||
387 (g_strcmp0(l->data, "%") == 0 && endptr[0] == '%'))))
388 break;
389 if (l == NULL) {
390 if (action)
391 g_set_error(error, LXKEYS_ERROR, LXKEYS_BAD_ARGS,
392 _("value '%s' is not supported for option '%s'."),
393 (char *)data->values->data, data->name);
394 else
395 g_set_error(error, LXKEYS_ERROR, LXKEYS_BAD_ARGS,
396 _("value '%s' is not supported for action '%s'."),
397 (char *)data->values->data, data->name);
398 return FALSE;
399 }
400 }
401 /* for each data->subopts do recursion against ordata->subopts */
402 if (!data->subopts) ;
403 else if (ordata->has_actions) {
404 /* test against origin actions list, not suboptions */
405 if (!validate_actions(data->subopts, origin, NULL, wm_name, error))
406 return FALSE;
407 } else if (!ordata->subopts) {
408 g_set_error(error, LXKEYS_ERROR, LXKEYS_BAD_ARGS,
409 _("action '%s' does not support options."), data->name);
410 return FALSE;
411 } else if (!validate_actions(data->subopts, origin, ordata, wm_name, error))
412 return FALSE;
413 act = act->next;
414 }
415 return TRUE;
416 }
417
418 /* convert list to a text line */
419 //static char *actions_to_str(const GList *act)
420 //{
421 //}
422
423 static void print_suboptions(GList *sub, int indent)
424 {
425 indent += 3;
426 while (sub) {
427 LXHotkeyAttr *action = sub->data;
428 if (action->values && action->values->data)
429 printf("%*s%s=%s\n", indent, "", action->name,
430 (char *)action->values->data);
431 else
432 printf("%*s%s\n", indent, "", action->name);
433 print_suboptions(action->subopts, indent);
434 sub = sub->next;
435 }
436 }
437
438
439 int main(int argc, char *argv[])
440 {
441 const char *cmd;
442 gchar *wm_name;
443 LXHotkeyPlugin *plugin = NULL;
444 LXHotkeyGUIPlugin *gui_plugin = NULL;
445 int ret = 1; /* failure */
446 gpointer config = NULL;
447 GError *error = NULL;
448 gboolean do_gui = FALSE;
449
450 /* init localizations */
451 #ifdef ENABLE_NLS
452 bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
453 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
454 textdomain(GETTEXT_PACKAGE);
455 #endif
456
457 /* parse args first, show help if "help" "-h" or "--help" */
458 if (argc < 2)
459 cmd = "--gui=gtk";
460 else
461 cmd = argv[1];
462 if (cmd[0] == '-' && cmd[1] == '-') /* skip leading "--" if given */
463 cmd += 2;
464 if (strcmp(cmd, "help") == 0 || (cmd[0] == '-' && cmd[1] == 'h'))
465 return _print_help(argv[0]);
466 if (memcmp(cmd, "gui=", 4) == 0) {
467 do_gui = TRUE;
468 cmd += 4;
469 }
470
471 if (!test_X_is_local()) {
472 fprintf(stderr, _("LXHotkey: sorry, cannot configure keys remotely.\n"));
473 return 1;
474 }
475
476 /* init LibFM and FmModule */
477 fm_init(NULL);
478 fm_modules_add_directory(PACKAGE_PLUGINS_DIR);
479 fm_module_register_lxhotkey();
480 if (do_gui)
481 fm_module_register_lxhotkey_gui();
482
483 LXKEYS_ERROR = g_quark_from_static_string("lxhotkey-error");
484
485 /* detect current WM and find a module for it */
486 wm_name = get_wm_info();
487 if (!wm_name)
488 goto _exit;
489 CHECK_MODULES();
490 for (plugin = plugins; plugin; plugin = plugin->next)
491 if (g_ascii_strcasecmp(plugin->name, wm_name) == 0)
492 break;
493 if (do_gui) /* load GUI plugin if requested */
494 for (gui_plugin = gui_plugins; gui_plugin; gui_plugin = gui_plugin->next)
495 if (g_ascii_strcasecmp(gui_plugin->name, cmd) == 0)
496 break;
497 if (!plugin) {
498 g_set_error(&error, LXKEYS_ERROR, LXKEYS_NOT_SUPPORTED,
499 _("Window manager %s isn't supported now, sorry."), wm_name);
500 goto _exit;
501 }
502
503 /* load the found module */
504 config = plugin->t->load(NULL, &error);
505 if (!config) {
506 g_prefix_error(&error, _("Problems loading configuration: "));
507 goto _exit;
508 }
509
510 if (do_gui) {
511 if (gui_plugin && gui_plugin->t->run)
512 {
513 if (gui_plugin->t->init)
514 gui_plugin->t->init(argc, argv);
515 gui_plugin->t->run(wm_name, plugin->t, &config, &error);
516 }
517 else
518 g_set_error(&error, LXKEYS_ERROR, LXKEYS_NOT_SUPPORTED,
519 _("GUI type %s currently isn't supported."), cmd);
520 goto _exit;
521 }
522
523 /* doing commandline: call module function depending on args */
524 if (strcmp(argv[1], "global") == 0) { /* lxhotkey global ... */
525 if (argc > 3) { /* set */
526 LXHotkeyGlobal data;
527
528 if (plugin->t->set_wm_key == NULL)
529 goto _not_supported;
530 /* parse and validate actions */
531 data.actions = actions_from_str(argv[2], &error);
532 if (error ||
533 (plugin->t->get_wm_actions != NULL &&
534 !validate_actions(data.actions, plugin->t->get_wm_actions(config, &error),
535 NULL, wm_name, &error))) { /* invalid request */
536 g_prefix_error(&error, _("Invalid request: "));
537 goto _exit;
538 }
539 // FIXME: validate key
540 data.accel1 = argv[3];
541 data.accel2 = NULL;
542 if (argc > 4)
543 data.accel2 = argv[4];
544 if (!plugin->t->set_wm_key(config, &data, &error) ||
545 !plugin->t->save(config, &error)) {
546 g_prefix_error(&error, _("Problems saving configuration: "));
547 free_actions(data.actions);
548 goto _exit;
549 }
550 free_actions(data.actions);
551 } else { /* show by mask */
552 const char *mask = NULL;
553 GList *keys, *key;
554 LXHotkeyGlobal *data;
555 GList *act;
556 LXHotkeyAttr *action;
557
558 if (plugin->t->get_wm_keys == NULL)
559 goto _not_supported;
560 if (argc > 2)
561 mask = argv[2]; /* mask given */
562 keys = plugin->t->get_wm_keys(config, mask, NULL);
563 ulc_printf(" %-24s %s\n", _("ACTION(s)"), _("KEY(s)"));
564 for (key = keys; key; key = key->next) {
565 data = key->data;
566 for (act = data->actions; act; act = act->next)
567 {
568 action = act->data;
569 if (act != data->actions)
570 printf("+ %s\n", action->name);
571 else if (data->accel2)
572 ulc_printf("%-24s %s %s\n", action->name, data->accel1,
573 data->accel2);
574 else
575 ulc_printf("%-24s %s\n", action->name, data->accel1);
576 print_suboptions(action->subopts, 0);
577 }
578 }
579 g_list_free(keys);
580 }
581 } else if (strcmp(argv[1], "app") == 0) { /* lxhotkey app ... */
582 if (argc > 3) { /* set */
583 GList *keys = NULL;
584 LXHotkeyApp data;
585
586 if (plugin->t->set_app_key == NULL)
587 goto _not_supported;
588 /* check if exec already has a key */
589 if (plugin->t->get_app_keys != NULL)
590 keys = plugin->t->get_app_keys(config, argv[2], NULL);
591 if (keys && keys->next) /* mask in exec line isn't supported */
592 goto _not_supported;
593 // FIXME: validate key
594 data.accel2 = NULL;
595 if (strcmp(argv[3], "--") == 0) { /* remove all bindings */
596 data.accel1 = NULL;
597 } else if (keys && ((LXHotkeyApp *)keys->data)->accel1) {
598 data.accel1 = ((LXHotkeyApp *)keys->data)->accel1;
599 data.accel2 = argv[3];
600 } else {
601 data.accel1 = argv[3];
602 }
603 g_list_free(keys);
604 cmd = strchr(argv[2], '&');
605 if (cmd) {
606 data.options = actions_from_str(&cmd[1], &error);
607 if (error ||
608 (plugin->t->get_app_options != NULL &&
609 !validate_actions(data.options,
610 plugin->t->get_app_options(config, &error),
611 NULL, wm_name, &error))) { /* invalid request */
612 g_prefix_error(&error, _("Invalid request: "));
613 free_actions(data.options);
614 goto _exit;
615 }
616 data.exec = g_strndup(argv[2], cmd - argv[2]);
617 } else {
618 data.options = NULL;
619 data.exec = g_strdup(argv[2]);
620 }
621 // FIXME: validate exec
622 if (!plugin->t->set_app_key(config, &data, &error) ||
623 !plugin->t->save(config, &error)) {
624 g_prefix_error(&error, _("Problems saving configuration: "));
625 free_actions(data.options);
626 g_free(data.exec);
627 goto _exit;
628 }
629 free_actions(data.options);
630 g_free(data.exec);
631 } else { /* show by mask */
632 const char *mask = NULL;
633 GList *keys, *key;
634 LXHotkeyApp *data;
635
636 if (plugin->t->get_app_keys == NULL)
637 goto _not_supported;
638 if (argc > 2)
639 mask = argv[2]; /* mask given */
640 keys = plugin->t->get_app_keys(config, mask, NULL);
641 ulc_printf(" %-48s %s\n", _("EXEC"), _("KEY(s)"));
642 for (key = keys; key; key = key->next) {
643 data = key->data;
644 if (data->accel2)
645 ulc_printf("%-48s %s %s\n", data->exec, data->accel1,
646 data->accel2);
647 else
648 ulc_printf("%-48s %s\n", data->exec, data->accel1);
649 print_suboptions(data->options, 0);
650 }
651 g_list_free(keys);
652 }
653 } else if (strcmp(argv[1], "show") == 0) { /* lxhotkey show ... */
654 // FIXME: TODO!
655 } else
656 goto _exit;
657 ret = 0; /* success */
658 goto _exit;
659
660 _not_supported:
661 g_set_error_literal(&error, LXKEYS_ERROR, LXKEYS_NOT_SUPPORTED,
662 _("Requested operation isn't supported."));
663
664 /* release resources */
665 _exit:
666 if (config)
667 plugin->t->free(config);
668 if (error) {
669 /* if do_gui then show an alert window instead of stderr */
670 if (gui_plugin && gui_plugin->t->alert)
671 gui_plugin->t->alert(error);
672 else
673 fprintf(stderr, "LXHotkey: %s\n", error->message);
674 g_error_free(error);
675 }
676 fm_module_unregister_type("lxhotkey");
677 if (do_gui)
678 fm_module_unregister_type("lxhotkey_gui");
679 while (plugins) {
680 plugin = plugins;
681 plugins = plugin->next;
682 g_free(plugin->name);
683 g_free(plugin);
684 }
685 while (gui_plugins) {
686 gui_plugin = gui_plugins;
687 gui_plugins = gui_plugin->next;
688 g_free(gui_plugin->name);
689 g_free(gui_plugin);
690 }
691 g_free(wm_name);
692
693 return ret;
694 }