1 /*
2 * Copyright (c) 2009-2012 Mike Massonnet <mmassonnet@xfce.org>
3 *
4 * XML parsing based on Xfce4 Panel:
5 * Copyright (c) 2005 Jasper Huijsmans <jasper@xfce.org>
6 *
7 * Internationalization of the XML file based on Thunar User Custom Actions:
8 * Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.org>
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 2
13 * of the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #ifdef HAVE_LOCALE_H
30 #include <locale.h>
31 #endif
32
33 #ifdef HAVE_STRING_H
34 #include <string.h>
35 #endif
36
37 #include <gio/gio.h>
38 #include <gtk/gtk.h>
39 #include <libxfce4ui/libxfce4ui.h>
40 #include <libxfce4util/libxfce4util.h>
41
42 #include "common.h"
43
44 #include "actions.h"
45
46 /*
47 * GObject declarations
48 */
49
50 struct _ClipmanActionsPrivate
51 {
52 GFile *file;
53 GFileMonitor *file_monitor;
54 GSList *entries;
55 GtkWidget *menu;
56 gboolean skip_action_on_key_down;
57 };
58
59 G_DEFINE_TYPE_WITH_PRIVATE (ClipmanActions, clipman_actions, G_TYPE_OBJECT)
60
61 enum
62 {
63 SKIP_ACTION_ON_KEY_DOWN = 1,
64 };
65
66 static void clipman_actions_finalize (GObject *object);
67 static void clipman_actions_set_property (GObject *object,
68 guint property_id,
69 const GValue *value,
70 GParamSpec *pspec);
71 static void clipman_actions_get_property (GObject *object,
72 guint property_id,
73 GValue *value,
74 GParamSpec *pspec);
75
76 /*
77 * Misc functions declarations
78 */
79
80 static void _clipman_actions_free_list (ClipmanActions *actions);
81 static gint __clipman_actions_entry_compare (gpointer a,
82 gpointer b);
83 static gint __clipman_actions_entry_compare_name (gpointer a,
84 gpointer b);
85 static void __clipman_actions_entry_free (ClipmanActionsEntry *entry);
86
87 /*
88 * Callbacks declarations
89 */
90
91 static void cb_entry_activated (GtkMenuItem *mi,
92 gpointer user_data);
93 static void cb_file_changed (ClipmanActions *actions,
94 GFile *file,
95 GFile *other_file,
96 GFileMonitorEvent event_type);
97 /*
98 * XML Parser declarations
99 */
100
101 static void start_element_handler (GMarkupParseContext *context,
102 const gchar *element_name,
103 const gchar **attribute_names,
104 const gchar **attribute_values,
105 gpointer user_data,
106 GError **error);
107 static void end_element_handler (GMarkupParseContext *context,
108 const gchar *element_name,
109 gpointer user_data,
110 GError **error);
111 static void text_handler (GMarkupParseContext *context,
112 const gchar *text,
113 gsize text_len,
114 gpointer user_data,
115 GError **error);
116
117 static GMarkupParser markup_parser =
118 {
119 start_element_handler,
120 end_element_handler,
121 text_handler,
122 NULL,
123 NULL,
124 };
125
126 typedef enum
127 {
128 START,
129 ACTIONS,
130 ACTION,
131 ACTION_NAME,
132 REGEX,
133 GROUP,
134 COMMANDS,
135 COMMAND,
136 COMMAND_NAME,
137 EXEC,
138 } ParserState;
139
140 typedef struct _EntryParser EntryParser;
141 struct _EntryParser
142 {
143 ClipmanActions *actions;
144 ParserState state;
145
146 gchar *locale;
147 gboolean name_use;
148 gint name_match;
149
150 gchar *action_name;
151 gchar *regex;
152 gint group;
153 gchar *command_name;
154 gchar *command;
155 };
156
157
158
159 /*
160 * XML Parser
161 */
162
163 static void
start_element_handler(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)164 start_element_handler (GMarkupParseContext *context,
165 const gchar *element_name,
166 const gchar **attribute_names,
167 const gchar **attribute_values,
168 gpointer user_data,
169 GError **error)
170 {
171 EntryParser *parser = user_data;
172 gint n;
173 gint match;
174
175 switch (parser->state)
176 {
177 case START:
178 if (!g_ascii_strcasecmp (element_name, "actions"))
179 parser->state = ACTIONS;
180 break;
181
182 case ACTIONS:
183 parser->name_use = FALSE;
184 parser->name_match = XFCE_LOCALE_NO_MATCH;
185
186 if (!g_ascii_strcasecmp (element_name, "action"))
187 parser->state = ACTION;
188 break;
189
190 case COMMANDS:
191 parser->name_use = FALSE;
192 parser->name_match = XFCE_LOCALE_NO_MATCH;
193
194 if (!g_ascii_strcasecmp (element_name, "command"))
195 parser->state = COMMAND;
196 break;
197
198 case ACTION:
199 case COMMAND:
200 if (!g_ascii_strcasecmp (element_name, "name"))
201 {
202 for (n = 0; attribute_names[n] != NULL; n++)
203 {
204 if (!g_ascii_strcasecmp (attribute_names[n], "xml:lang"))
205 break;
206 }
207
208 if (attribute_names[n] == NULL)
209 {
210 parser->name_use = (parser->name_match <= XFCE_LOCALE_NO_MATCH);
211 }
212 else
213 {
214 match = xfce_locale_match (parser->locale, attribute_values[n]);
215 if (parser->name_match < match)
216 {
217 parser->name_match = match;
218 parser->name_use = TRUE;
219 }
220 else
221 parser->name_use = FALSE;
222 }
223
224 parser->state = (parser->state == ACTION) ? ACTION_NAME : COMMAND_NAME;
225 }
226 else if (!g_ascii_strcasecmp (element_name, "regex"))
227 parser->state = REGEX;
228 else if (!g_ascii_strcasecmp (element_name, "group"))
229 parser->state = GROUP;
230 else if (!g_ascii_strcasecmp (element_name, "commands"))
231 parser->state = COMMANDS;
232 else if (!g_ascii_strcasecmp (element_name, "exec"))
233 parser->state = EXEC;
234 break;
235
236 default:
237 break;
238 }
239 }
240
241 static void
end_element_handler(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)242 end_element_handler (GMarkupParseContext *context,
243 const gchar *element_name,
244 gpointer user_data,
245 GError **error)
246 {
247 EntryParser *parser = user_data;
248
249 switch (parser->state)
250 {
251 case ACTION:
252 g_free (parser->action_name);
253 g_free (parser->regex);
254 parser->action_name = NULL;
255 parser->regex = NULL;
256 parser->group = 0;
257
258 parser->state = ACTIONS;
259 break;
260
261 case ACTION_NAME:
262 case REGEX:
263 case GROUP:
264 case COMMANDS:
265 parser->state = ACTION;
266 break;
267
268 case COMMAND:
269 if (parser->action_name == NULL || parser->regex == NULL)
270 {
271 g_warning ("Closing a command but no action name nor regex set");
272 }
273 else
274 {
275 clipman_actions_add (parser->actions, parser->action_name, parser->regex,
276 parser->command_name, parser->command);
277 clipman_actions_set_group (parser->actions, parser->action_name, parser->group);
278 }
279
280 g_free (parser->command_name);
281 g_free (parser->command);
282 parser->command_name = NULL;
283 parser->command = NULL;
284
285 parser->state = COMMANDS;
286 break;
287
288 case COMMAND_NAME:
289 case EXEC:
290 parser->state = COMMAND;
291 break;
292
293 default:
294 break;
295 }
296 }
297
298 static void
text_handler(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)299 text_handler (GMarkupParseContext *context,
300 const gchar *text,
301 gsize text_len,
302 gpointer user_data,
303 GError **error)
304 {
305 EntryParser *parser = user_data;
306
307 switch (parser->state)
308 {
309 case ACTION_NAME:
310 if (parser->name_use)
311 {
312 g_free (parser->action_name);
313 parser->action_name = g_strdup (text);
314 }
315 break;
316
317 case REGEX:
318 parser->regex = g_strdup (text);
319 break;
320
321 case GROUP:
322 parser->group = (gint)g_strtod (text, NULL);
323 break;
324
325 case COMMAND_NAME:
326 if (parser->name_use)
327 {
328 g_free (parser->command_name);
329 parser->command_name = g_strdup (text);
330 }
331 break;
332
333 case EXEC:
334 parser->command = g_strdup (text);
335 break;
336
337 default:
338 break;
339 }
340 }
341
342 /*
343 * Callbacks
344 */
345
346 static void
cb_entry_activated(GtkMenuItem * mi,gpointer user_data)347 cb_entry_activated (GtkMenuItem *mi,
348 gpointer user_data)
349 {
350 gchar *real_command;
351 const gchar *text;
352 const gchar *command;
353 const GRegex *regex;
354 GError *error = NULL;
355
356 text = g_object_get_data (G_OBJECT (mi), "text");
357 command = g_object_get_data (G_OBJECT (mi), "command");
358 regex = g_object_get_data (G_OBJECT (mi), "regex");
359
360 real_command = g_regex_replace (regex, text, -1, 0, command, 0, NULL);
361
362 DBG ("Execute command `%s'", real_command);
363
364 g_spawn_command_line_async (real_command, &error);
365 if (error != NULL)
366 {
367 xfce_dialog_show_error (NULL, error, _("Unable to execute the command \"%s\"\n\n%s"), real_command, error->message);
368 g_error_free (error);
369 }
370 g_free (real_command);
371 }
372
373 static gboolean
timeout_file_changed(gpointer user_data)374 timeout_file_changed (gpointer user_data)
375 {
376 ClipmanActions *actions = user_data;
377 _clipman_actions_free_list (actions);
378 clipman_actions_load (actions);
379 return FALSE;
380 }
381
382 static void
cb_file_changed(ClipmanActions * actions,GFile * file,GFile * other_file,GFileMonitorEvent event_type)383 cb_file_changed (ClipmanActions *actions,
384 GFile *file,
385 GFile *other_file,
386 GFileMonitorEvent event_type)
387 {
388 static guint timeout = 0;
389 if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
390 {
391 if (timeout > 0)
392 g_source_remove (timeout);
393 timeout = g_timeout_add_seconds (1, timeout_file_changed, actions);
394 }
395 }
396
397 /*
398 * Misc functions
399 */
400
401 static void
_clipman_actions_free_list(ClipmanActions * actions)402 _clipman_actions_free_list (ClipmanActions *actions)
403 {
404 GSList *l;
405 for (l = actions->priv->entries; l != NULL; l = l->next)
406 __clipman_actions_entry_free (l->data);
407 g_slist_free (actions->priv->entries);
408 actions->priv->entries = NULL;
409 }
410
411 static gint
__clipman_actions_entry_compare(gpointer a,gpointer b)412 __clipman_actions_entry_compare (gpointer a,
413 gpointer b)
414 {
415 const ClipmanActionsEntry *entrya = a;
416 const ClipmanActionsEntry *entryb = b;
417 return g_ascii_strcasecmp (entrya->action_name, entryb->action_name);
418 }
419
420 static gint
__clipman_actions_entry_compare_name(gpointer a,gpointer b)421 __clipman_actions_entry_compare_name (gpointer a,
422 gpointer b)
423 {
424 const ClipmanActionsEntry *entry = a;
425 const char *name = b;
426 return g_ascii_strcasecmp (entry->action_name, name);
427 }
428
429 static void
__clipman_actions_entry_free(ClipmanActionsEntry * entry)430 __clipman_actions_entry_free (ClipmanActionsEntry *entry)
431 {
432 g_free (entry->action_name);
433 g_free (entry->pattern);
434 g_regex_unref (entry->regex);
435 g_hash_table_destroy (entry->commands);
436 g_slice_free (ClipmanActionsEntry, entry);
437 }
438
439 /*
440 * Public methods
441 */
442
443 /**
444 * clipman_actions_add:
445 * @actions: a #ClipmanActions
446 * @action_name: human readable name of @regex
447 * @regex: a valid regex or NULL if @name already exists in the list
448 * @command_name: human readable name of @command
449 * @command: the command to execute
450 *
451 * Adds a new entry to the list of actions. The same name and regex can be
452 * passed several times for each new command.
453 * If @action_name already exists, the regex is ignored.
454 * If @command_name already exists, the new command will replace the old one.
455 *
456 * The command can contain the parameter '%s' that will be replaced by the
457 * matching regex text.
458 *
459 * The action is created with the default group 0. To change it use the
460 * function clipman_actions_set_group().
461 *
462 * Returns: FALSE if the regex was invalid
463 */
464 gboolean
clipman_actions_add(ClipmanActions * actions,const gchar * action_name,const gchar * regex,const gchar * command_name,const gchar * command)465 clipman_actions_add (ClipmanActions *actions,
466 const gchar *action_name,
467 const gchar *regex,
468 const gchar *command_name,
469 const gchar *command)
470 {
471 ClipmanActionsEntry *entry;
472 GSList *l;
473 GRegex *_regex;
474 gchar *regex_anchored;
475
476 g_return_val_if_fail (G_LIKELY (action_name != NULL), FALSE);
477 g_return_val_if_fail (G_LIKELY (command_name != NULL), FALSE);
478 g_return_val_if_fail (G_LIKELY (command != NULL), FALSE);
479
480 l = g_slist_find_custom (actions->priv->entries, action_name, (GCompareFunc)__clipman_actions_entry_compare_name);
481
482 /* Add a new entry to the list */
483 if (l == NULL)
484 {
485 /* Validate the regex */
486 regex_anchored = g_strdup_printf ("%s$", regex);
487 _regex = g_regex_new (regex_anchored, G_REGEX_CASELESS|G_REGEX_ANCHORED, 0, NULL);
488 g_free (regex_anchored);
489 if (_regex == NULL)
490 return FALSE;
491
492 DBG ("New entry `%s' with command `%s'", action_name, command_name);
493
494 entry = g_slice_new0 (ClipmanActionsEntry);
495 entry->action_name = g_strdup (action_name);
496 entry->pattern = g_strdup (regex);
497 entry->regex = _regex;
498 entry->group = 0;
499 entry->commands = g_hash_table_new_full ((GHashFunc)g_str_hash, (GEqualFunc)g_str_equal,
500 (GDestroyNotify)g_free, (GDestroyNotify)g_free);
501 g_hash_table_insert (entry->commands, g_strdup (command_name), g_strdup (command));
502
503 actions->priv->entries = g_slist_insert_sorted (actions->priv->entries, entry, (GCompareFunc)__clipman_actions_entry_compare);
504 return TRUE;
505 }
506
507 /* Add command to the existing entry */
508 DBG ("Add to entry `%s' the command `%s'", action_name, command_name);
509
510 entry = l->data;
511 g_hash_table_insert (entry->commands, g_strdup (command_name), g_strdup (command));
512 return TRUE;
513 }
514
515 /**
516 * clipman_actions_remove:
517 * @actions: a #ClipmanActions
518 * @action_name: the human readable name for the regex
519 *
520 * Removes a #ClipmanActionsEntry from the list of actions.
521 *
522 * Returns: FALSE if no action could be removed
523 */
524 gboolean
clipman_actions_remove(ClipmanActions * actions,const gchar * action_name)525 clipman_actions_remove (ClipmanActions *actions,
526 const gchar *action_name)
527 {
528 ClipmanActionsEntry *entry;
529 GSList *l;
530
531 l = g_slist_find_custom (actions->priv->entries, action_name, (GCompareFunc)__clipman_actions_entry_compare_name);
532 if (l == NULL)
533 {
534 g_warning ("No corresponding entry `%s'", action_name);
535 return FALSE;
536 }
537
538 DBG ("Drop the entry `%s'", action_name);
539
540 entry = l->data;
541 __clipman_actions_entry_free (entry);
542 actions->priv->entries = g_slist_delete_link (actions->priv->entries, l);
543
544 return TRUE;
545 }
546
547 /**
548 * clipman_actions_remove_command:
549 * @actions: a #ClipmanActions
550 * @action_name: the human readable name for the regex
551 * @command_name: the command to remove
552 *
553 * Removes a command from the list of actions. If the command is the last
554 * command for the corresponding action, than the action will be completely
555 * dropped from the list.
556 *
557 * Returns: FALSE if no command could be removed
558 */
559 gboolean
clipman_actions_remove_command(ClipmanActions * actions,const gchar * action_name,const gchar * command_name)560 clipman_actions_remove_command (ClipmanActions *actions,
561 const gchar *action_name,
562 const gchar *command_name)
563 {
564 ClipmanActionsEntry *entry;
565 GSList *l;
566 gboolean found;
567
568 l = g_slist_find_custom (actions->priv->entries, action_name, (GCompareFunc)__clipman_actions_entry_compare_name);
569 if (l == NULL)
570 {
571 g_warning ("No corresponding entry `%s'", action_name);
572 return FALSE;
573 }
574
575 entry = l->data;
576 found = g_hash_table_remove (entry->commands, command_name);
577
578 if (!found)
579 g_warning ("No corresponding command `%s' inside entry `%s'", command_name, action_name);
580 else
581 {
582 DBG ("Drop from entry `%s' the command `%s'", action_name, command_name);
583 if (g_hash_table_size (entry->commands) == 0)
584 {
585 DBG ("Clean up the entry");
586 __clipman_actions_entry_free (entry);
587 actions->priv->entries = g_slist_delete_link (actions->priv->entries, l);
588 }
589 }
590
591 return found;
592 }
593
594 /**
595 * clipman_actions_set_group:
596 * @actions: a #ClipmanActions
597 * @action_name: the human readable name for the regex
598 * @group: the group identifier
599 *
600 * Changes the group of @action_name to @group.
601 */
602 void
clipman_actions_set_group(ClipmanActions * actions,const gchar * action_name,gint group)603 clipman_actions_set_group (ClipmanActions *actions,
604 const gchar *action_name,
605 gint group)
606 {
607 ClipmanActionsEntry *entry;
608 GSList *l;
609
610 l = g_slist_find_custom (actions->priv->entries, action_name, (GCompareFunc)__clipman_actions_entry_compare_name);
611 if (l == NULL)
612 {
613 g_warning ("No corresponding entry `%s'", action_name);
614 return;
615 }
616
617 entry = l->data;
618 entry->group = group;
619 }
620
621 /**
622 * clipman_actions_get_entries:
623 * @actions: a #ClipmanActions
624 *
625 * Returns: a #const #GSList owned by #ClipmanActions
626 */
627 const GSList *
clipman_actions_get_entries(ClipmanActions * actions)628 clipman_actions_get_entries (ClipmanActions *actions)
629 {
630 return actions->priv->entries;
631 }
632
633 /**
634 * clipman_actions_match:
635 * @actions: a #ClipmanActions
636 * @group: the group identifier
637 * @text: the text to match against the existing regex's
638 *
639 * Searches a regex match for @text and returns a newly allocated #GSList which
640 * must be freed with g_slist_free() that contains a list of
641 * #ClipmanActionsEntry for each matched action.
642 * Note that the data inside the list is owned by #ClipmanActions and must not
643 * be modified.
644 * The @group identifier can be -1 to get a match from all groups.
645 *
646 * Returns: a newly allocated #GSList
647 */
648 GSList *
clipman_actions_match(ClipmanActions * actions,gint group,const gchar * text)649 clipman_actions_match (ClipmanActions *actions,
650 gint group,
651 const gchar *text)
652 {
653 ClipmanActionsEntry *entry;
654 GSList *l;
655 GSList *entries = NULL;
656
657 for (l = actions->priv->entries; l != NULL; l = l->next)
658 {
659 entry = l->data;
660 if (group == -1 || group == entry->group)
661 {
662 if (g_regex_match (entry->regex, text, 0, NULL))
663 entries = g_slist_prepend (entries, entry);
664 }
665 }
666
667 return entries;
668 }
669
670 /**
671 * clipman_actions_match_with_menu:
672 * @actions: a #ClipmanActions
673 * @group: the group identifier
674 * @text: the text to match against the existing regex's
675 *
676 * Builds and displays a menu with matching actions for @text.
677 *
678 * Read clipman_actions_match() for more information.
679 */
680 void
clipman_actions_match_with_menu(ClipmanActions * actions,gint group,const gchar * text)681 clipman_actions_match_with_menu (ClipmanActions *actions,
682 gint group,
683 const gchar *text)
684 {
685 ClipmanActionsEntry *entry;
686 GtkWidget *mi;
687 GSList *l, *entries;
688 GdkModifierType state = 0;
689 GdkDisplay* display = gdk_display_get_default ();
690 GdkSeat *seat = gdk_display_get_default_seat (display);
691 GdkDevice *device = gdk_seat_get_pointer (seat);
692 GdkScreen* screen = gdk_screen_get_default ();
693 GdkWindow * root_win = gdk_screen_get_root_window (screen);
694
695 if (group == ACTION_GROUP_SELECTION)
696 {
697 gint ctrl_mask = 0;
698
699 gdk_window_get_device_position (root_win, device, NULL, NULL, &state);
700 ctrl_mask = state & GDK_CONTROL_MASK;
701 if (ctrl_mask && actions->priv->skip_action_on_key_down)
702 {
703 return;
704 }
705 else if (!ctrl_mask && !actions->priv->skip_action_on_key_down)
706 {
707 return;
708 }
709 }
710
711 entries = clipman_actions_match (actions, group, text);
712
713 if (entries == NULL)
714 return;
715
716 DBG ("Build the menu with actions");
717
718 if (GTK_IS_MENU (actions->priv->menu))
719 {
720 gtk_widget_destroy (actions->priv->menu);
721 actions->priv->menu = NULL;
722 }
723
724 actions->priv->menu = gtk_menu_new ();
725 g_object_set_data_full (G_OBJECT (actions->priv->menu), "text", g_strdup (text), (GDestroyNotify)g_free);
726
727 for (l = entries; l != NULL; l = l->next)
728 {
729 entry = l->data;
730
731 mi = gtk_menu_item_new_with_label (entry->action_name);
732 gtk_widget_set_sensitive (mi, FALSE);
733 gtk_container_add (GTK_CONTAINER (actions->priv->menu), mi);
734
735 mi = gtk_separator_menu_item_new ();
736 gtk_container_add (GTK_CONTAINER (actions->priv->menu), mi);
737
738 {
739 GHashTableIter iter;
740 gpointer key, value;
741 g_hash_table_iter_init (&iter, entry->commands);
742 while (g_hash_table_iter_next (&iter, &key, &value))
743 {
744 mi = gtk_menu_item_new_with_label ((const gchar *)key);
745 g_object_set_data (G_OBJECT (mi), "text", g_object_get_data (G_OBJECT (actions->priv->menu), "text"));
746 g_object_set_data (G_OBJECT (mi), "command", value);
747 g_object_set_data (G_OBJECT (mi), "regex", entry->regex);
748 gtk_container_add (GTK_CONTAINER (actions->priv->menu), mi);
749 g_signal_connect (mi, "activate", G_CALLBACK (cb_entry_activated), NULL);
750 }
751 }
752
753 mi = gtk_separator_menu_item_new ();
754 gtk_container_add (GTK_CONTAINER (actions->priv->menu), mi);
755 }
756
757 mi = gtk_menu_item_new_with_label ("Cancel");
758 gtk_container_add (GTK_CONTAINER (actions->priv->menu), mi);
759
760 gtk_widget_show_all (actions->priv->menu);
761
762 if(!gtk_widget_has_grab(actions->priv->menu))
763 {
764 gtk_grab_add(actions->priv->menu);
765 }
766
767 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
768 gtk_menu_popup (GTK_MENU (actions->priv->menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time ());
769 G_GNUC_END_IGNORE_DEPRECATIONS
770
771 g_slist_free (entries);
772 }
773
774 /**
775 * clipman_actions_load:
776 * @actions:
777 *
778 */
779 void
clipman_actions_load(ClipmanActions * actions)780 clipman_actions_load (ClipmanActions *actions)
781 {
782 gchar *filename;
783 gchar *data;
784 gssize size;
785 gboolean load;
786 GMarkupParseContext *context;
787 EntryParser *parser;
788
789 load = g_file_load_contents (actions->priv->file, NULL, &data, (gsize*)&size, NULL, NULL);
790
791 if (!load)
792 {
793 /* Create user directory early to be sure it exists for next actions */
794 GFile *dir = g_file_get_parent (actions->priv->file);
795 g_file_make_directory_with_parents (dir, NULL, NULL);
796 g_object_unref (dir);
797 dir = NULL;
798
799 /* Load from system wide file */
800 filename = g_strdup (SYSCONFDIR"/xdg/xfce4/panel/xfce4-clipman-actions.xml");
801 load = g_file_get_contents (filename, &data, (gsize*)&size, NULL);
802 g_free (filename);
803 }
804
805 if (!load)
806 {
807 g_warning ("Unable to load actions from an XML file");
808 return;
809 }
810
811 DBG ("Load actions from file");
812
813 parser = g_slice_new0 (EntryParser);
814 parser->actions = actions;
815 parser->locale = setlocale (LC_MESSAGES, NULL);
816 context = g_markup_parse_context_new (&markup_parser, 0, parser, NULL);
817 g_markup_parse_context_parse (context, data, size, NULL);
818 if (!g_markup_parse_context_end_parse (context, NULL))
819 g_warning ("Error parsing the XML file");
820 g_markup_parse_context_free (context);
821 g_slice_free (EntryParser, parser);
822
823 g_free (data);
824 }
825
826 /**
827 * clipman_actions_save:
828 * @actions:
829 *
830 */
831 void
clipman_actions_save(ClipmanActions * actions)832 clipman_actions_save (ClipmanActions *actions)
833 {
834 ClipmanActionsEntry *entry;
835 gchar *data;
836 GString *output;
837 gchar *tmp;
838 GSList *l;
839
840 /* Generate the XML output format */
841 output = g_string_new ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
842 "<actions>\n");
843
844 for (l = actions->priv->entries; l != NULL; l = l->next)
845 {
846 entry = l->data;
847
848 g_string_append (output, "\t<action>\n");
849
850 tmp = g_markup_escape_text (entry->action_name, -1);
851 g_string_append_printf (output, "\t\t<name>%s</name>\n", tmp);
852 g_free (tmp);
853
854 tmp = g_markup_escape_text (entry->pattern, -1);
855 g_string_append_printf (output, "\t\t<regex>%s</regex>\n", tmp);
856 g_free (tmp);
857
858 g_string_append_printf (output, "\t\t<group>%d</group>\n", entry->group);
859
860 g_string_append (output, "\t\t<commands>\n");
861
862 {
863 GHashTableIter iter;
864 gpointer key, value;
865 g_hash_table_iter_init (&iter, entry->commands);
866 while (g_hash_table_iter_next (&iter, &key, &value))
867 {
868 g_string_append (output, "\t\t\t<command>\n");
869
870 tmp = g_markup_escape_text (key, -1);
871 g_string_append_printf (output, "\t\t\t\t<name>%s</name>\n", tmp);
872 g_free (tmp);
873
874 tmp = g_markup_escape_text (value, -1);
875 g_string_append_printf (output, "\t\t\t\t<exec>%s</exec>\n", tmp);
876 g_free (tmp);
877
878 g_string_append (output, "\t\t\t</command>\n");
879 }
880 }
881
882 g_string_append (output, "\t\t</commands>\n");
883
884 g_string_append (output, "\t</action>\n");
885 }
886
887 g_string_append (output, "</actions>");
888
889 /* And now write output to the xml file */
890 DBG ("Save actions to file");
891 data = g_string_free (output, FALSE);
892 if (!g_file_replace_contents (actions->priv->file, data, strlen (data), NULL, FALSE,
893 G_FILE_CREATE_NONE, NULL, NULL, NULL))
894 g_warning ("Unable to write the actions to the XML file");
895
896 g_free (data);
897 }
898
899 /**
900 * clipman_actions_get:
901 *
902 */
903 ClipmanActions *
clipman_actions_get(void)904 clipman_actions_get (void)
905 {
906 static ClipmanActions *singleton = NULL;
907
908 if (singleton == NULL)
909 {
910 singleton = g_object_new (CLIPMAN_TYPE_ACTIONS, NULL);
911 g_object_add_weak_pointer (G_OBJECT (singleton), (gpointer)&singleton);
912 }
913 else
914 g_object_ref (G_OBJECT (singleton));
915
916 return singleton;
917 }
918
919 /*
920 * GObject
921 */
922
923 static void
clipman_actions_class_init(ClipmanActionsClass * klass)924 clipman_actions_class_init (ClipmanActionsClass *klass)
925 {
926 GObjectClass *object_class;
927
928 clipman_actions_parent_class = g_type_class_peek_parent (klass);
929
930 object_class = G_OBJECT_CLASS (klass);
931 object_class->finalize = clipman_actions_finalize;
932 object_class->set_property = clipman_actions_set_property;
933 object_class->get_property = clipman_actions_get_property;
934
935 g_object_class_install_property (object_class, SKIP_ACTION_ON_KEY_DOWN,
936 g_param_spec_boolean ("skip-action-on-key-down",
937 "SkipActionOnKeyDown",
938 "Skip the action if the Control key is pressed down",
939 DEFAULT_SKIP_ACTION_ON_KEY_DOWN,
940 G_PARAM_CONSTRUCT|G_PARAM_READWRITE));
941 }
942
943 static void
clipman_actions_init(ClipmanActions * actions)944 clipman_actions_init (ClipmanActions *actions)
945 {
946 gchar *filename;
947
948 actions->priv = clipman_actions_get_instance_private (actions);
949
950 /* Actions file */
951 filename = g_strdup_printf ("%s/xfce4/panel/xfce4-clipman-actions.xml", g_get_user_config_dir ());
952 actions->priv->file = g_file_new_for_path (filename);
953 g_free (filename);
954
955 /* Load initial actions */
956 clipman_actions_load (actions);
957
958 /* Listen on xml file changes */
959 actions->priv->file_monitor = g_file_monitor_file (actions->priv->file, G_FILE_MONITOR_NONE, NULL, NULL);
960 g_signal_connect_swapped (actions->priv->file_monitor, "changed", G_CALLBACK (cb_file_changed), actions);
961 }
962
963 static void
clipman_actions_finalize(GObject * object)964 clipman_actions_finalize (GObject *object)
965 {
966 ClipmanActions *actions = CLIPMAN_ACTIONS (object);
967 _clipman_actions_free_list (actions);
968 g_object_unref (actions->priv->file_monitor);
969 g_object_unref (actions->priv->file);
970 }
971
972 static void
clipman_actions_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)973 clipman_actions_set_property (GObject *object,
974 guint property_id,
975 const GValue *value,
976 GParamSpec *pspec)
977 {
978 ClipmanActionsPrivate *priv = CLIPMAN_ACTIONS (object)->priv;
979
980 switch (property_id)
981 {
982 case SKIP_ACTION_ON_KEY_DOWN:
983 priv->skip_action_on_key_down = g_value_get_boolean (value);
984 break;
985
986 default:
987 break;
988 }
989 }
990
991 static void
clipman_actions_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)992 clipman_actions_get_property (GObject *object,
993 guint property_id,
994 GValue *value,
995 GParamSpec *pspec)
996 {
997 ClipmanActionsPrivate *priv = CLIPMAN_ACTIONS (object)->priv;
998
999 switch (property_id)
1000 {
1001 case SKIP_ACTION_ON_KEY_DOWN:
1002 g_value_set_boolean (value, priv->skip_action_on_key_down);
1003 break;
1004
1005 default:
1006 break;
1007 }
1008 }
1009