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