1 /*
2 * desktop-app-info: A GDesktopAppInfo like object for garcon menu
3 * items implementing and supporting GAppInfo
4 *
5 * Copyright 2012-2020 Stephan Haller <nomad@froevel.de>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301, USA.
21 *
22 *
23 */
24
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28
29 #include <glib/gi18n-lib.h>
30
31 #include <libxfdashboard/desktop-app-info.h>
32 #include <libxfdashboard/application-database.h>
33 #include <libxfdashboard/compat.h>
34 #include <libxfdashboard/debug.h>
35
36 #include <libxfce4util/libxfce4util.h>
37
38
39 /* Define this class in GObject system */
40 static void _xfdashboard_desktop_app_info_gappinfo_iface_init(GAppInfoIface *iface);
41
42 struct _XfdashboardDesktopAppInfoPrivate
43 {
44 /* Properties related */
45 gchar *desktopID;
46 GFile *file;
47
48 /* Instance related */
49 gboolean inited;
50 gboolean isValid;
51
52 GarconMenuItem *item;
53 guint itemChangedID;
54
55 GKeyFile *secondarySource;
56
57 gchar *binaryExecutable;
58
59 gboolean needActions;
60 GList *actions;
61
62 gboolean needKeywords;
63 GList *keywords;
64 };
65
66 G_DEFINE_TYPE_WITH_CODE(XfdashboardDesktopAppInfo,
67 xfdashboard_desktop_app_info,
68 G_TYPE_OBJECT,
69 G_ADD_PRIVATE(XfdashboardDesktopAppInfo)
70 G_IMPLEMENT_INTERFACE(G_TYPE_APP_INFO, _xfdashboard_desktop_app_info_gappinfo_iface_init))
71
72 /* Properties */
73 enum
74 {
75 PROP_0,
76
77 PROP_VALID,
78 PROP_DESKTOP_ID,
79 PROP_FILE,
80
81 PROP_LAST
82 };
83
84 static GParamSpec* XfdashboardDesktopAppInfoProperties[PROP_LAST]={ 0, };
85
86 /* Signals */
87 enum
88 {
89 SIGNAL_CHANGED,
90 SIGNAL_RELOAD,
91
92 SIGNAL_LAST
93 };
94
95 static guint XfdashboardDesktopAppInfoSignals[SIGNAL_LAST]={ 0, };
96
97 /* IMPLEMENTATION: Private variables and methods */
98 typedef struct
99 {
100 gchar *display;
101 gchar *startupNotificationID;
102 gchar *desktopFile;
103 } XfdashboardDesktopAppInfoChildSetupData;
104
105 /* Load secondary source file if not already done.
106 * Note: It is called secondary source although it is the same file as used
107 * for GarconMenuItem. But it is not the same source because the file is loaded
108 * via a GKeyFile object to get access to entries not provided by garcon or
109 * implemented in an unusable way for xfdashboard.
110 */
_xfdashboard_desktop_app_info_load_secondary_source(XfdashboardDesktopAppInfo * self)111 static gboolean _xfdashboard_desktop_app_info_load_secondary_source(XfdashboardDesktopAppInfo *self)
112 {
113 XfdashboardDesktopAppInfoPrivate *priv;
114
115 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self), FALSE);
116
117 priv=self->priv;
118
119 /* Only load secondary source file if not already done and a file is set */
120 if(!priv->secondarySource &&
121 priv->file)
122 {
123 GKeyFile *keyfile;
124 gchar *secondarySourceFilename;
125 GError *error;
126
127 error=NULL;
128
129 /* Get path to secondary source file */
130 secondarySourceFilename=g_file_get_path(priv->file);
131
132 /* Load secondary source file */
133 keyfile=g_key_file_new();
134 if(!g_key_file_load_from_file(keyfile,
135 secondarySourceFilename,
136 G_KEY_FILE_KEEP_TRANSLATIONS,
137 &error))
138 {
139 /* Show warning */
140 g_warning("Could not load secondary source %s for desktop ID '%s': %s",
141 secondarySourceFilename,
142 priv->desktopID,
143 error ? error->message : "Unknown error");
144
145 /* Release allocated resources */
146 if(error) g_error_free(error);
147 if(secondarySourceFilename) g_free(secondarySourceFilename);
148 if(keyfile) g_key_file_unref(keyfile);
149
150 /* Return FALSE to indicate error */
151 return(FALSE);
152 }
153
154 /* Use secondary source */
155 priv->secondarySource=g_key_file_ref(keyfile);
156
157 /* Release allocated resources */
158 if(secondarySourceFilename) g_free(secondarySourceFilename);
159 if(keyfile) g_key_file_unref(keyfile);
160 }
161
162 /* If we get here and we have no keyfile for secondary source, return FALSE
163 * to indicate that secondary source is not available.
164 */
165 if(G_UNLIKELY(!priv->secondarySource)) return(FALSE);
166
167 /* Return TRUE for success */
168 return(TRUE);
169 }
170
171 /* Get or update path to executable file for this application */
_xfdashboard_desktop_app_info_update_binary_executable(XfdashboardDesktopAppInfo * self)172 static void _xfdashboard_desktop_app_info_update_binary_executable(XfdashboardDesktopAppInfo *self)
173 {
174 XfdashboardDesktopAppInfoPrivate *priv;
175
176 g_return_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self));
177
178 priv=self->priv;
179
180 /* Get path to executable file for this application by striping white-space from
181 * the beginning of the command to execute when launching up to first white-space
182 * after the first command-line argument (which is the command).
183 */
184 if(priv->binaryExecutable)
185 {
186 g_free(priv->binaryExecutable);
187 priv->binaryExecutable=NULL;
188 }
189
190 if(priv->item)
191 {
192 const gchar *command;
193 const gchar *commandStart;
194 const gchar *commandEnd;
195
196 command=garcon_menu_item_get_command(priv->item);
197
198 while(*command==' ') command++;
199 commandStart=command;
200
201 while(*command && *command!=' ') command++;
202 commandEnd=command;
203
204 priv->binaryExecutable=g_strndup(commandStart, commandEnd-commandStart);
205 }
206 }
207
208 /* (Re-)Load application actions */
_xfdashboard_desktop_app_info_update_actions(XfdashboardDesktopAppInfo * self)209 static void _xfdashboard_desktop_app_info_update_actions(XfdashboardDesktopAppInfo *self)
210 {
211 XfdashboardDesktopAppInfoPrivate *priv;
212
213 g_return_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self));
214
215 priv=self->priv;
216
217 /* Reload only if needed */
218 if(!priv->needActions) return;
219
220 /* Remove old actions loaded */
221 if(priv->actions)
222 {
223 g_list_free_full(priv->actions, g_object_unref);
224 priv->actions=NULL;
225 }
226
227 /* Get application actions for menu item (desktop entry) */
228 #if 0 /*GARCON_CHECK_VERSION(0, 6, 3)*/
229 if(priv->item)
230 {
231 GList *itemActions;
232 GList *iter;
233 const gchar *itemActionName;
234 GarconMenuItemAction *itemAction;
235 XfdashboardDesktopAppInfoAction *action;
236
237 /* Get action from garcon menu item and create desktop info action object
238 * for each action iterated.
239 */
240 itemActions=garcon_menu_item_get_actions(priv->item);
241 for(iter=itemActions; iter; iter=g_list_next(iter))
242 {
243 /* Get action currently iterated from garcon menu item */
244 itemActionName=(const gchar*)(iter->data);
245 if(!itemActionName)
246 {
247 g_warning("Cannot create application action because of empty action name for desktop ID '%s'",
248 priv->desktopID);
249 continue;
250 }
251
252 itemAction=garcon_menu_item_get_action(priv->item, itemActionName);
253 if(!itemAction)
254 {
255 g_warning("Cannot create application action for desktop ID '%s'",
256 priv->desktopID);
257 continue;
258 }
259
260 /* Create desktop info action object and add to list */
261 action=XFDASHBOARD_DESKTOP_APP_INFO_ACTION
262 (
263 g_object_new(XFDASHBOARD_TYPE_DESKTOP_APP_INFO_ACTION,
264 "name", garcon_menu_item_action_get_name(itemAction),
265 "icon-name", garcon_menu_item_action_get_icon_name(itemAction),
266 "command", garcon_menu_item_action_get_command(itemAction),
267 NULL)
268 );
269 priv->actions=g_list_prepend(priv->actions, action);
270
271 XFDASHBOARD_DEBUG(self, APPLICATIONS,
272 "Created application action '%s' for desktop ID '%s'",
273 xfdashboard_desktop_app_info_action_get_name(action),
274 priv->desktopID);
275 }
276 priv->actions=g_list_reverse(priv->actions);
277
278 /* Release allocated resources */
279 g_list_free(itemActions);
280 }
281 #else
282 /* Garcon prior to version 0.6.0 does not provide accessor function for
283 * application actions of a desktop entry and the first version providing
284 * these function return the action in an unpredictable order but not the
285 * order as listed in "Action" keyword of desktop entry. So we need to
286 * load the secondary source and grab them ourselve.
287 */
288 if(_xfdashboard_desktop_app_info_load_secondary_source(self))
289 {
290 gchar **itemActions;
291 gchar **iter;
292 gchar *itemActionGroup;
293 gchar *itemActionName;
294 gchar *itemActionIcon;
295 gchar *itemActionExec;
296 XfdashboardDesktopAppInfoAction *action;
297 GError *error;
298
299 error=NULL;
300
301 /* Get list of actions and their order from "Action" keyword */
302 itemActions=g_key_file_get_string_list(priv->secondarySource,
303 G_KEY_FILE_DESKTOP_GROUP,
304 G_KEY_FILE_DESKTOP_KEY_ACTIONS,
305 NULL,
306 &error);
307 if(!itemActions)
308 {
309 XFDASHBOARD_DEBUG(self, APPLICATIONS,
310 "Could not fetch list of actions from secondary source for desktop ID '%s': %s",
311 priv->desktopID,
312 error ? error->message : "Unknown error");
313
314 /* Release allocated resources */
315 if(error) g_error_free(error);
316
317 /* Return from here as we cannot collect any list of actions */
318 return;
319 }
320
321 /* Get action currently iterated from string list of secondary source */
322 for(iter=itemActions; *iter; iter++)
323 {
324 /* Determine group name in desktop file to retrieve data about
325 * application action currently iterated.
326 */
327 itemActionGroup=g_strdup_printf("Desktop Action %s", *iter);
328
329 /* Get display name of application action. According to the specification,
330 * it says only the "Name" keyword is required. So do not create a
331 * desktop info action object if it is missing, but fail silenty and
332 * continue with next action in list.
333 */
334 itemActionName=g_key_file_get_locale_string(priv->secondarySource,
335 itemActionGroup,
336 G_KEY_FILE_DESKTOP_KEY_NAME,
337 NULL,
338 &error);
339 if(!itemActionName)
340 {
341 XFDASHBOARD_DEBUG(self, APPLICATIONS,
342 "Could not get name of action '%s' from secondary source for desktop ID '%s': %s",
343 *iter,
344 priv->desktopID,
345 error ? error->message : "Unknown error");
346
347 /* Release allocated resources */
348 if(itemActionGroup) g_free(itemActionGroup);
349 if(error)
350 {
351 g_error_free(error);
352 error=NULL;
353 }
354
355 /* Continue with next action */
356 continue;
357 }
358
359 /* Get optional icon name of application action */
360 itemActionIcon=g_key_file_get_string(priv->secondarySource,
361 itemActionGroup,
362 G_KEY_FILE_DESKTOP_KEY_ICON,
363 NULL);
364
365 /* Get optional command of application action */
366 itemActionExec=g_key_file_get_string(priv->secondarySource,
367 itemActionGroup,
368 G_KEY_FILE_DESKTOP_KEY_EXEC,
369 NULL);
370
371 /* Create desktop info action object and add to list */
372 action=XFDASHBOARD_DESKTOP_APP_INFO_ACTION
373 (
374 g_object_new(XFDASHBOARD_TYPE_DESKTOP_APP_INFO_ACTION,
375 "name", itemActionName,
376 "icon-name", itemActionIcon,
377 "command", itemActionExec,
378 NULL)
379 );
380 priv->actions=g_list_prepend(priv->actions, action);
381
382 XFDASHBOARD_DEBUG(self, APPLICATIONS,
383 "Created application action '%s' for desktop ID '%s' from secondary source",
384 xfdashboard_desktop_app_info_action_get_name(action),
385 priv->desktopID);
386
387 /* Release allocated resources */
388 if(itemActionExec) g_free(itemActionExec);
389 if(itemActionIcon) g_free(itemActionIcon);
390 if(itemActionName) g_free(itemActionName);
391 if(itemActionGroup) g_free(itemActionGroup);
392 }
393 priv->actions=g_list_reverse(priv->actions);
394
395 /* Release allocated resources */
396 if(itemActions) g_strfreev(itemActions);
397 }
398 #endif
399
400 /* Set flag that application actions are loaded and do not need futher updates */
401 priv->needActions=FALSE;
402 }
403
404 /* (Re-)Load keywords */
_xfdashboard_desktop_app_info_update_keywords(XfdashboardDesktopAppInfo * self)405 static void _xfdashboard_desktop_app_info_update_keywords(XfdashboardDesktopAppInfo *self)
406 {
407 XfdashboardDesktopAppInfoPrivate *priv;
408
409 g_return_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self));
410
411 priv=self->priv;
412
413 /* Reload only if needed */
414 if(!priv->needKeywords) return;
415
416 /* Remove old actions loaded */
417 if(priv->keywords)
418 {
419 g_list_free_full(priv->keywords, g_free);
420 priv->keywords=NULL;
421 }
422
423 /* Get application actions for menu item (desktop entry) */
424 #if 0 /*GARCON_CHECK_VERSION(0, 6, 3)*/
425 if(priv->item)
426 {
427 const GList *keywords;
428
429 /* Get keywords from garcon menu item and create a deep copy of list */
430 keywords=garcon_menu_item_get_keywords(priv->item);
431 for(iter=keywords; iter; iter=g_list_next(iter))
432 {
433 /* Create copy of list entry and prepend to new list */
434 priv->keywords=g_list_prepend(priv->keywords, g_strdup((const gchar*)(iter->data)));
435
436 XFDASHBOARD_DEBUG(self, APPLICATIONS,
437 "Added keyword '%s' for desktop ID '%s'",
438 (const gchar*)(iter->data),
439 priv->desktopID);
440 }
441 priv->keywords=g_list_reverse(priv->keywords);
442 }
443 #else
444 /* Garcon does not provide an accessor function to get keywords for desktop
445 * entries in any official released version yet. So load them ourselve from
446 * secondary source.
447 */
448 if(_xfdashboard_desktop_app_info_load_secondary_source(self))
449 {
450 gchar **keywords;
451 gchar **iter;
452 GError *error;
453
454 error=NULL;
455
456 /* Get keywords from secondary source */
457 keywords=g_key_file_get_string_list(priv->secondarySource,
458 G_KEY_FILE_DESKTOP_GROUP,
459 "Keywords",
460 NULL,
461 &error);
462 if(!keywords)
463 {
464 XFDASHBOARD_DEBUG(self, APPLICATIONS,
465 "Could not fetch list of keywords from secondary source for desktop ID '%s': %s",
466 priv->desktopID,
467 error ? error->message : "Unknown error");
468
469 /* Release allocated resources */
470 if(error) g_error_free(error);
471
472 /* Return from here as we cannot collect any list of actions */
473 return;
474 }
475
476 /* Get action currently iterated from string list of secondary source */
477 for(iter=keywords; *iter; iter++)
478 {
479 /* Create copy a currently iterated keyword and prepend to list */
480 priv->keywords=g_list_prepend(priv->keywords, g_strdup(*iter));
481
482 XFDASHBOARD_DEBUG(self, APPLICATIONS,
483 "Added keyword '%s' for desktop ID '%s' from secondary source",
484 *iter,
485 priv->desktopID);
486 }
487 priv->keywords=g_list_reverse(priv->keywords);
488
489 /* Release allocated resources */
490 if(keywords) g_strfreev(keywords);
491 }
492 #endif
493
494 /* Set flag that keywords are loaded and do not need futher updates */
495 priv->needKeywords=FALSE;
496 }
497
498 /* Menu item has changed */
_xfdashboard_desktop_app_info_on_item_changed(XfdashboardDesktopAppInfo * self,gpointer inUserData)499 static void _xfdashboard_desktop_app_info_on_item_changed(XfdashboardDesktopAppInfo *self,
500 gpointer inUserData)
501 {
502 g_return_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self));
503
504 /* Emit 'changed' signal for this desktop app info */
505 g_signal_emit(self, XfdashboardDesktopAppInfoSignals[SIGNAL_CHANGED], 0);
506 }
507
508 /* Set desktop ID */
_xfdashboard_desktop_app_info_set_desktop_id(XfdashboardDesktopAppInfo * self,const gchar * inDesktopID)509 static void _xfdashboard_desktop_app_info_set_desktop_id(XfdashboardDesktopAppInfo *self,
510 const gchar *inDesktopID)
511 {
512 XfdashboardDesktopAppInfoPrivate *priv;
513
514 g_return_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self));
515
516 priv=self->priv;
517
518 /* Set value if changed */
519 if(g_strcmp0(priv->desktopID, inDesktopID))
520 {
521 /* Set value */
522 if(priv->desktopID)
523 {
524 g_free(priv->desktopID);
525 priv->desktopID=NULL;
526 }
527
528 if(inDesktopID) priv->desktopID=g_strdup(inDesktopID);
529
530 /* Notify about property change */
531 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardDesktopAppInfoProperties[PROP_DESKTOP_ID]);
532 }
533 }
534
535 /* Set desktop file */
_xfdashboard_desktop_app_info_set_file(XfdashboardDesktopAppInfo * self,GFile * inFile)536 static void _xfdashboard_desktop_app_info_set_file(XfdashboardDesktopAppInfo *self,
537 GFile *inFile)
538 {
539 XfdashboardDesktopAppInfoPrivate *priv;
540
541 g_return_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self));
542 g_return_if_fail(!inFile || G_IS_FILE(inFile));
543
544 priv=self->priv;
545
546 /* Set value if changed */
547 if(!(priv->file && inFile && g_file_equal(priv->file, inFile)))
548 {
549 gboolean valid;
550
551 /* Freeze notification */
552 g_object_freeze_notify(G_OBJECT(self));
553
554 /* Release secondard source */
555 if(priv->secondarySource)
556 {
557 g_key_file_unref(priv->secondarySource);
558 priv->secondarySource=NULL;
559 }
560
561 /* Replace current file to menu item with new one */
562 if(priv->file)
563 {
564 g_object_unref(priv->file);
565 priv->file=NULL;
566 }
567 if(inFile) priv->file=g_object_ref(inFile);
568
569 /* Replace current menu item with new one */
570 if(priv->item)
571 {
572 if(priv->itemChangedID)
573 {
574 g_signal_handler_disconnect(priv->item, priv->itemChangedID);
575 priv->itemChangedID=0;
576 }
577
578 g_object_unref(priv->item);
579 priv->item=NULL;
580 }
581
582 if(priv->file)
583 {
584 priv->item=garcon_menu_item_new(priv->file);
585 }
586
587 /* Connect signal to get notified about changes of menu item */
588 if(priv->item)
589 {
590 priv->itemChangedID=g_signal_connect_swapped(priv->item,
591 "changed",
592 G_CALLBACK(_xfdashboard_desktop_app_info_on_item_changed),
593 self);
594 }
595
596 /* Get path to executable file for this application */
597 _xfdashboard_desktop_app_info_update_binary_executable(self);
598
599 /* Set flag to reload application actions and keywords. They will be
600 * cleared and (re-)loaded on-demand.
601 */
602 priv->needActions=TRUE;
603 priv->needKeywords=TRUE;
604
605 /* Notify about property change */
606 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardDesktopAppInfoProperties[PROP_FILE]);
607
608 /* Emit 'changed' signal if was inited before */
609 if(priv->inited)
610 {
611 g_signal_emit(self, XfdashboardDesktopAppInfoSignals[SIGNAL_CHANGED], 0);
612 }
613
614 /* Desktop file is set, menu item loaded - this desktop app info is inited now */
615 priv->inited=TRUE;
616
617 /* Check if this app info is valid (either file is not set or file and menu item is set */
618 valid=FALSE;
619 if(!priv->file ||
620 (priv->file && priv->item))
621 {
622 valid=TRUE;
623 }
624
625 /* Set value if changed */
626 if(priv->isValid!=valid)
627 {
628 /* Set value */
629 priv->isValid=valid;
630
631 /* Notify about property change */
632 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardDesktopAppInfoProperties[PROP_VALID]);
633 }
634
635 /* Thaw notification */
636 g_object_thaw_notify(G_OBJECT(self));
637 }
638 /* If both files (the file already set in this desktop app info and the file to set
639 * are equal then reload desktop file which forces the 'changed' signal to be emitted.
640 */
641 else if(priv->inited && priv->file && inFile && g_file_equal(priv->file, inFile))
642 {
643 gboolean valid;
644
645 /* Reload desktop file */
646 valid=xfdashboard_desktop_app_info_reload(self);
647
648 /* Set value depending on reload success if changed */
649 if(priv->isValid!=valid)
650 {
651 /* Set value */
652 priv->isValid=valid;
653
654 /* Notify about property change */
655 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardDesktopAppInfoProperties[PROP_VALID]);
656 }
657 }
658 }
659
660 #if !LIBXFCE4UTIL_CHECK_VERSION(4, 15, 2)
661
662 /* Launch application with URIs in given context */
_xfdashboard_desktop_app_info_expand_macros_add_file(const gchar * inURI,GString * ioExpanded)663 static void _xfdashboard_desktop_app_info_expand_macros_add_file(const gchar *inURI, GString *ioExpanded)
664 {
665 GFile *file;
666 gchar *path;
667 gchar *quotedPath;
668
669 g_return_if_fail(inURI && *inURI);
670 g_return_if_fail(ioExpanded);
671
672 file=g_file_new_for_uri(inURI);
673 path=g_file_get_path(file);
674 quotedPath=g_shell_quote(path);
675
676 g_string_append(ioExpanded, quotedPath);
677 g_string_append_c(ioExpanded, ' ');
678
679 g_free(quotedPath);
680 g_free(path);
681 g_object_unref(file);
682 }
683
_xfdashboard_desktop_app_info_expand_macros_add_uri(const gchar * inURI,GString * ioExpanded)684 static void _xfdashboard_desktop_app_info_expand_macros_add_uri(const gchar *inURI, GString *ioExpanded)
685 {
686 gchar *quotedURI;
687
688 g_return_if_fail(inURI && *inURI);
689 g_return_if_fail(ioExpanded);
690
691 quotedURI=g_shell_quote(inURI);
692
693 g_string_append(ioExpanded, quotedURI);
694 g_string_append_c(ioExpanded, ' ');
695
696 g_free(quotedURI);
697 }
698
_xfdashboard_desktop_app_info_expand_macros(XfdashboardDesktopAppInfo * self,const gchar * inCommand,GList * inURIs,GString * ioExpanded)699 static gboolean _xfdashboard_desktop_app_info_expand_macros(XfdashboardDesktopAppInfo *self,
700 const gchar *inCommand,
701 GList *inURIs,
702 GString *ioExpanded)
703 {
704 XfdashboardDesktopAppInfoPrivate *priv;
705 gboolean filesOrUriAdded;
706
707 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self), FALSE);
708 g_return_val_if_fail(inCommand && *inCommand, FALSE);
709 g_return_val_if_fail(ioExpanded, FALSE);
710
711 priv=self->priv;
712
713 /* Iterate through command-line char by char and expand known macros */
714 filesOrUriAdded=FALSE;
715
716 while(*inCommand)
717 {
718 /* Check if character is '%' indicating that a macro could follow ... */
719 if(*inCommand=='%')
720 {
721 /* Move to next character to determin which macro to expand
722 * but check also that we have not reached end of inCommand-line.
723 */
724 inCommand++;
725 if(!*inCommand) break;
726
727 /* Expand macro */
728 switch(*inCommand)
729 {
730 case 'f':
731 if(inURIs) _xfdashboard_desktop_app_info_expand_macros_add_file(inURIs->data, ioExpanded);
732 filesOrUriAdded=TRUE;
733 break;
734
735 case 'F':
736 g_list_foreach(inURIs, (GFunc)_xfdashboard_desktop_app_info_expand_macros_add_file, ioExpanded);
737 filesOrUriAdded=TRUE;
738 break;
739
740 case 'u':
741 if(inURIs) _xfdashboard_desktop_app_info_expand_macros_add_uri(inURIs->data, ioExpanded);
742 filesOrUriAdded=TRUE;
743 break;
744
745 case 'U':
746 g_list_foreach(inURIs, (GFunc)_xfdashboard_desktop_app_info_expand_macros_add_uri, ioExpanded);
747 filesOrUriAdded=TRUE;
748 break;
749
750 case '%':
751 g_string_append_c(ioExpanded, '%');
752 break;
753
754 case 'i':
755 {
756 const gchar *iconName;
757 gchar *quotedIconName;
758
759 iconName=garcon_menu_item_get_icon_name(priv->item);
760 if(iconName)
761 {
762 quotedIconName=g_shell_quote(iconName);
763
764 g_string_append(ioExpanded, "--icon ");
765 g_string_append(ioExpanded, quotedIconName);
766
767 g_free(quotedIconName);
768 }
769 break;
770 }
771
772 case 'c':
773 {
774 const gchar *name;
775 gchar *quotedName;
776
777 name=garcon_menu_item_get_name(priv->item);
778 if(name)
779 {
780 quotedName=g_shell_quote(name);
781 g_string_append(ioExpanded, quotedName);
782 g_free(quotedName);
783 }
784 break;
785 }
786
787 case 'k':
788 {
789 GFile *desktopFile;
790 gchar *filename;
791 gchar *quotedFilename;
792
793 desktopFile=garcon_menu_item_get_file(priv->item);
794 if(desktopFile)
795 {
796 filename=g_file_get_path(desktopFile);
797 if(filename)
798 {
799 quotedFilename=g_shell_quote(filename);
800 g_string_append(ioExpanded, quotedFilename);
801 g_free(quotedFilename);
802
803 g_free(filename);
804 }
805
806 g_object_unref(desktopFile);
807 }
808
809 break;
810 }
811
812 default:
813 break;
814 }
815 }
816 /* ... otherwise just add the character */
817 else g_string_append_c(ioExpanded, *inCommand);
818
819 /* Continue with next character in inCommand-line */
820 inCommand++;
821 }
822
823 /* If URIs was provided but not used (exec key does not contain %f, %F, %u, %U)
824 * append first URI to expanded inCommand-line.
825 */
826 if(inURIs && !filesOrUriAdded)
827 {
828 g_string_append_c(ioExpanded, ' ');
829 _xfdashboard_desktop_app_info_expand_macros_add_file(inURIs->data, ioExpanded);
830 }
831
832 /* If we get here we could expand macros in command-line successfully */
833 return(TRUE);
834 }
835
836 #endif
837
838 /* Child process for launching application was spawned but application
839 * was not executed yet so we can set up environment etc. now.
840 *
841 * Note: Do not use any kind of dynamically allocated memory like
842 * GObject instances or memory allocation functions like g_new,
843 * malloc etc., and also do not ref or unref any GObject instances
844 * because we cannot be sure that memory is cleaned up and references
845 * are incremented/decremented in spawned (forked) child process.
846 */
_xfdashboard_desktop_app_info_on_child_spawned(gpointer inUserData)847 static void _xfdashboard_desktop_app_info_on_child_spawned(gpointer inUserData)
848 {
849 XfdashboardDesktopAppInfoChildSetupData *data=(XfdashboardDesktopAppInfoChildSetupData*)inUserData;
850
851 g_return_if_fail(data);
852
853 /* Set up environment */
854 if(data->display)
855 {
856 g_setenv("DISPLAY", data->display, TRUE);
857 }
858
859 if(data->startupNotificationID)
860 {
861 g_setenv("DESKTOP_STARTUP_ID", data->startupNotificationID, TRUE);
862 }
863
864 if(data->desktopFile)
865 {
866 gchar pid[20];
867
868 g_setenv("GIO_LAUNCHED_DESKTOP_FILE", data->desktopFile, TRUE);
869
870 g_snprintf(pid, 20, "%ld", (long)getpid());
871 g_setenv("GIO_LAUNCHED_DESKTOP_FILE_PID", pid, TRUE);
872 }
873 }
874
875 #if !LIBXFCE4UTIL_CHECK_VERSION(4, 15, 2)
876
_xfdashboard_desktop_app_info_launch_appinfo_internal(XfdashboardDesktopAppInfo * self,const gchar * inCommand,GList * inURIs,GAppLaunchContext * inContext,GError ** outError)877 static gboolean _xfdashboard_desktop_app_info_launch_appinfo_internal(XfdashboardDesktopAppInfo *self,
878 const gchar *inCommand,
879 GList *inURIs,
880 GAppLaunchContext *inContext,
881 GError **outError)
882 {
883 XfdashboardDesktopAppInfoPrivate *priv;
884 GString *expanded;
885 gchar *display;
886 gchar *startupNotificationID;
887 gchar *desktopFile;
888 const gchar *workingDirectory;
889 gboolean success;
890 GPid launchedPID;
891 gint argc;
892 gchar **argv;
893 GError *error;
894 XfdashboardDesktopAppInfoChildSetupData childSetup;
895
896 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self), FALSE);
897 g_return_val_if_fail(inCommand && *inCommand, FALSE);
898 g_return_val_if_fail(!inContext || G_IS_APP_LAUNCH_CONTEXT(inContext), FALSE);
899 g_return_val_if_fail(outError && *outError==NULL, FALSE);
900
901 priv=self->priv;
902 display=NULL;
903 startupNotificationID=NULL;
904 desktopFile=NULL;
905 success=FALSE;
906 argc=0;
907 argv=NULL;
908 error=NULL;
909
910 /* Get command-line with expanded macros */
911 expanded=g_string_new(NULL);
912 if(!expanded ||
913 !_xfdashboard_desktop_app_info_expand_macros(self, inCommand, inURIs, expanded))
914 {
915 /* Set error */
916 g_set_error_literal(outError,
917 G_IO_ERROR,
918 G_IO_ERROR_FAILED,
919 "Unable to expand macros at command-line.");
920
921 /* Release allocated resources */
922 if(expanded) g_string_free(expanded, TRUE);
923
924 /* Return error state */
925 return(FALSE);
926 }
927
928 /* If a terminal is required, prepend "exo-open" command.
929 * NOTE: The space at end of command is important to separate
930 * the command we prepend from command-line of application.
931 */
932 if(garcon_menu_item_requires_terminal(priv->item))
933 {
934 g_string_prepend(expanded, "exo-open --launch TerminalEmulator ");
935 }
936
937 /* Get command-line arguments as string list */
938 if(!g_shell_parse_argv(expanded->str, &argc, &argv, &error))
939 {
940 /* Propagate error */
941 g_propagate_error(outError, error);
942
943 /* Release allocated resources */
944 if(argv) g_strfreev(argv);
945 if(expanded) g_string_free(expanded, TRUE);
946
947 /* Return error state */
948 return(FALSE);
949 }
950
951 /* Set up launch context, e.g. display and startup notification */
952 if(inContext)
953 {
954 GList *filesToLaunch;
955 GList *iter;
956
957 /* Create GFile objects for URIs */
958 filesToLaunch=NULL;
959 for(iter=inURIs; iter; iter=g_list_next(iter))
960 {
961 GFile *file;
962
963 file=g_file_new_for_uri((const gchar*)iter->data);
964 filesToLaunch=g_list_prepend(filesToLaunch, file);
965 }
966 filesToLaunch=g_list_reverse(filesToLaunch);
967
968 /* Get display where to launch application at */
969 display=g_app_launch_context_get_display(inContext,
970 G_APP_INFO(self),
971 filesToLaunch);
972
973 /* Get startup notification ID if it is supported by application */
974 if(garcon_menu_item_supports_startup_notification(priv->item))
975 {
976 startupNotificationID=g_app_launch_context_get_startup_notify_id(inContext,
977 G_APP_INFO(self),
978 filesToLaunch);
979 }
980
981 /* Release allocated resources */
982 g_list_free_full(filesToLaunch, g_object_unref);
983 }
984
985 /* Get working directory and test if directory exists */
986 workingDirectory=garcon_menu_item_get_path(priv->item);
987 if(!workingDirectory || !*workingDirectory)
988 {
989 /* Working directory was either NULL or is an empty string,
990 * so do not set working directory.
991 */
992 workingDirectory=NULL;
993 }
994 else if(!g_file_test(workingDirectory, G_FILE_TEST_IS_DIR))
995 {
996 /* Working directory does not exist or is not a directory */
997 g_warning("Working directory '%s' does not exist. It won't be used when launching '%s'.",
998 workingDirectory,
999 *argv);
1000
1001 /* Do not set working directory */
1002 workingDirectory=NULL;
1003 }
1004
1005 /* Get desktop file of application to launch */
1006 desktopFile=g_file_get_path(priv->file);
1007
1008 /* Launch application */
1009 childSetup.display=display;
1010 childSetup.startupNotificationID=startupNotificationID;
1011 childSetup.desktopFile=desktopFile;
1012 success=g_spawn_async(workingDirectory,
1013 argv,
1014 NULL,
1015 G_SPAWN_SEARCH_PATH,
1016 _xfdashboard_desktop_app_info_on_child_spawned,
1017 &childSetup,
1018 &launchedPID,
1019 &error);
1020 if(success)
1021 {
1022 GDBusConnection *sessionBus;
1023
1024 XFDASHBOARD_DEBUG(self, APPLICATIONS,
1025 "Launching %s succeeded with PID %ld.",
1026 garcon_menu_item_get_name(priv->item),
1027 (long)launchedPID);
1028
1029 /* Open connection to DBUS session bus and send notification about
1030 * successful launch of application. Then flush and close DBUS
1031 * session bus connection.
1032 */
1033 sessionBus=g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
1034 if(sessionBus)
1035 {
1036 GDBusMessage *message;
1037 GVariantBuilder uris;
1038 GVariantBuilder extras;
1039 GList *iter;
1040 const gchar *desktopFileID;
1041 const gchar *gioDesktopFile;
1042 const gchar *programName;
1043
1044 /* Build list of URIs */
1045 g_variant_builder_init(&uris, G_VARIANT_TYPE("as"));
1046 for(iter=inURIs; iter; iter=g_list_next(iter))
1047 {
1048 g_variant_builder_add(&uris, "s", (const gchar*)iter->data);
1049 }
1050
1051 /* Build list of extra information */
1052 g_variant_builder_init(&extras, G_VARIANT_TYPE("a{sv}"));
1053 if(startupNotificationID &&
1054 g_utf8_validate(startupNotificationID, -1, NULL))
1055 {
1056 g_variant_builder_add(&extras,
1057 "{sv}",
1058 "startup-id",
1059 g_variant_new("s", startupNotificationID));
1060 }
1061
1062 gioDesktopFile=g_getenv("GIO_LAUNCHED_DESKTOP_FILE");
1063 if(gioDesktopFile)
1064 {
1065 g_variant_builder_add(&extras,
1066 "{sv}",
1067 "origin-desktop-file",
1068 g_variant_new_bytestring(gioDesktopFile));
1069 }
1070
1071 programName=g_get_prgname();
1072 if(programName)
1073 {
1074 g_variant_builder_add(&extras,
1075 "{sv}",
1076 "origin-prgname",
1077 g_variant_new_bytestring(programName));
1078 }
1079
1080 g_variant_builder_add(&extras,
1081 "{sv}",
1082 "origin-pid",
1083 g_variant_new("x", (gint64)getpid()));
1084
1085 if(priv->desktopID) desktopFileID=priv->desktopID;
1086 else if(priv->file) desktopFileID=desktopFile;
1087 else desktopFileID="";
1088
1089 message=g_dbus_message_new_signal("/org/gtk/gio/DesktopAppInfo",
1090 "org.gtk.gio.DesktopAppInfo",
1091 "Launched");
1092 g_dbus_message_set_body(message,
1093 g_variant_new
1094 (
1095 "(@aysxasa{sv})",
1096 g_variant_new_bytestring(desktopFileID),
1097 display ? display : "",
1098 (gint64)launchedPID,
1099 &uris,
1100 &extras
1101 ));
1102 g_dbus_connection_send_message(sessionBus,
1103 message,
1104 0,
1105 NULL,
1106 NULL);
1107
1108 g_object_unref(message);
1109
1110 /* It is safe to unreference DBUS session bus object after
1111 * calling flush function even if the flush function is
1112 * a asynchronous function because it takes its own reference
1113 * on the session bus to keep it alive until flush is complete.
1114 */
1115 g_dbus_connection_flush(sessionBus, NULL, NULL, NULL);
1116 g_object_unref(sessionBus);
1117 }
1118 }
1119 else
1120 {
1121 g_warning("Launching %s failed!", garcon_menu_item_get_name(priv->item));
1122
1123 /* Propagate error */
1124 g_propagate_error(outError, error);
1125
1126 /* Tell context about failed application launch */
1127 if(startupNotificationID)
1128 {
1129 g_app_launch_context_launch_failed(inContext, startupNotificationID);
1130 }
1131 }
1132
1133 /* Release allocated resources */
1134 if(expanded) g_string_free(expanded, TRUE);
1135 if(argv) g_strfreev(argv);
1136 if(desktopFile) g_free(desktopFile);
1137 if(startupNotificationID) g_free(startupNotificationID);
1138 if(display) g_free(display);
1139
1140 return(success);
1141 }
1142
1143 #else
1144
_xfdashboard_desktop_app_info_launch_appinfo_internal(XfdashboardDesktopAppInfo * self,const gchar * inCommand,GList * inURIs,GAppLaunchContext * inContext,GError ** outError)1145 static gboolean _xfdashboard_desktop_app_info_launch_appinfo_internal(XfdashboardDesktopAppInfo *self,
1146 const gchar *inCommand,
1147 GList *inURIs,
1148 GAppLaunchContext *inContext,
1149 GError **outError)
1150 {
1151 XfdashboardDesktopAppInfoPrivate *priv;
1152 GString *string;
1153 gchar *expanded;
1154 gchar *uri;
1155 gchar *filename;
1156 gchar *display;
1157 gchar *startupNotificationID;
1158 gchar *desktopFile;
1159 const gchar *workingDirectory;
1160 const gchar *name;
1161 gboolean success;
1162 GPid launchedPID;
1163 gint argc;
1164 gchar **argv;
1165 GError *error;
1166 XfdashboardDesktopAppInfoChildSetupData childSetup;
1167
1168 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self), FALSE);
1169 g_return_val_if_fail(inCommand && *inCommand, FALSE);
1170 g_return_val_if_fail(!inContext || G_IS_APP_LAUNCH_CONTEXT(inContext), FALSE);
1171 g_return_val_if_fail(outError && *outError==NULL, FALSE);
1172
1173 priv=self->priv;
1174 display=NULL;
1175 startupNotificationID=NULL;
1176 desktopFile=NULL;
1177 success=FALSE;
1178 argc=0;
1179 argv=NULL;
1180 error=NULL;
1181
1182 /* Get command-line with expanded macros */
1183 name=garcon_menu_item_get_name(priv->item);
1184 uri=garcon_menu_item_get_uri(priv->item);
1185 expanded=xfce_expand_desktop_entry_field_codes(inCommand, (GSList*)inURIs,
1186 garcon_menu_item_get_icon_name(priv->item),
1187 name, uri,
1188 garcon_menu_item_requires_terminal(priv->item));
1189 g_free(uri);
1190
1191 if(!expanded)
1192 {
1193 /* Set error */
1194 g_set_error_literal(outError,
1195 G_IO_ERROR,
1196 G_IO_ERROR_FAILED,
1197 "Unable to expand macros at command-line.");
1198
1199 /* Return error state */
1200 return(FALSE);
1201 }
1202
1203 /* If URIs was provided but not used (exec key does not contain %f, %F, %u, %U)
1204 * append first URI to expanded inCommand-line.
1205 */
1206 if(inURIs && !g_regex_match_simple("%[fu]", inCommand, G_REGEX_CASELESS, 0))
1207 {
1208 string=g_string_new(expanded);
1209 g_free(expanded);
1210 g_string_append_c(string, ' ');
1211 filename=g_filename_from_uri(inURIs->data, NULL, NULL);
1212 xfce_append_quoted(string, filename);
1213 g_free(filename);
1214 expanded=g_string_free(string, FALSE);
1215 }
1216
1217 /* Get command-line arguments as string list */
1218 if(!g_shell_parse_argv(expanded, &argc, &argv, &error))
1219 {
1220 /* Propagate error */
1221 g_propagate_error(outError, error);
1222
1223 /* Release allocated resources */
1224 if(argv) g_strfreev(argv);
1225 if(expanded) g_free(expanded);
1226
1227 /* Return error state */
1228 return(FALSE);
1229 }
1230
1231 /* Set up launch context, e.g. display and startup notification */
1232 if(inContext)
1233 {
1234 GList *filesToLaunch;
1235 GList *iter;
1236
1237 /* Create GFile objects for URIs */
1238 filesToLaunch=NULL;
1239 for(iter=inURIs; iter; iter=g_list_next(iter))
1240 {
1241 GFile *file;
1242
1243 file=g_file_new_for_uri((const gchar*)iter->data);
1244 filesToLaunch=g_list_prepend(filesToLaunch, file);
1245 }
1246 filesToLaunch=g_list_reverse(filesToLaunch);
1247
1248 /* Get display where to launch application at */
1249 display=g_app_launch_context_get_display(inContext,
1250 G_APP_INFO(self),
1251 filesToLaunch);
1252
1253 /* Get startup notification ID if it is supported by application */
1254 if(garcon_menu_item_supports_startup_notification(priv->item))
1255 {
1256 startupNotificationID=g_app_launch_context_get_startup_notify_id(inContext,
1257 G_APP_INFO(self),
1258 filesToLaunch);
1259 }
1260
1261 /* Release allocated resources */
1262 g_list_free_full(filesToLaunch, g_object_unref);
1263 }
1264
1265 /* Get working directory and test if directory exists */
1266 workingDirectory=garcon_menu_item_get_path(priv->item);
1267 if(!workingDirectory || !*workingDirectory)
1268 {
1269 /* Working directory was either NULL or is an empty string,
1270 * so do not set working directory.
1271 */
1272 workingDirectory=NULL;
1273 }
1274 else if(!g_file_test(workingDirectory, G_FILE_TEST_IS_DIR))
1275 {
1276 /* Working directory does not exist or is not a directory */
1277 g_warning("Working directory '%s' does not exist. It won't be used when launching '%s'.",
1278 workingDirectory,
1279 *argv);
1280
1281 /* Do not set working directory */
1282 workingDirectory=NULL;
1283 }
1284
1285 /* Get desktop file of application to launch */
1286 desktopFile=g_file_get_path(priv->file);
1287
1288 /* Launch application */
1289 childSetup.display=display;
1290 childSetup.startupNotificationID=startupNotificationID;
1291 childSetup.desktopFile=desktopFile;
1292 success=g_spawn_async(workingDirectory,
1293 argv,
1294 NULL,
1295 G_SPAWN_SEARCH_PATH,
1296 _xfdashboard_desktop_app_info_on_child_spawned,
1297 &childSetup,
1298 &launchedPID,
1299 &error);
1300 if(success)
1301 {
1302 GDBusConnection *sessionBus;
1303
1304 XFDASHBOARD_DEBUG(self, APPLICATIONS,
1305 "Launching %s succeeded with PID %ld.",
1306 name, (long)launchedPID);
1307
1308 /* Open connection to DBUS session bus and send notification about
1309 * successful launch of application. Then flush and close DBUS
1310 * session bus connection.
1311 */
1312 sessionBus=g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
1313 if(sessionBus)
1314 {
1315 GDBusMessage *message;
1316 GVariantBuilder uris;
1317 GVariantBuilder extras;
1318 GList *iter;
1319 const gchar *desktopFileID;
1320 const gchar *gioDesktopFile;
1321 const gchar *programName;
1322
1323 /* Build list of URIs */
1324 g_variant_builder_init(&uris, G_VARIANT_TYPE("as"));
1325 for(iter=inURIs; iter; iter=g_list_next(iter))
1326 {
1327 g_variant_builder_add(&uris, "s", (const gchar*)iter->data);
1328 }
1329
1330 /* Build list of extra information */
1331 g_variant_builder_init(&extras, G_VARIANT_TYPE("a{sv}"));
1332 if(startupNotificationID &&
1333 g_utf8_validate(startupNotificationID, -1, NULL))
1334 {
1335 g_variant_builder_add(&extras,
1336 "{sv}",
1337 "startup-id",
1338 g_variant_new("s", startupNotificationID));
1339 }
1340
1341 gioDesktopFile=g_getenv("GIO_LAUNCHED_DESKTOP_FILE");
1342 if(gioDesktopFile)
1343 {
1344 g_variant_builder_add(&extras,
1345 "{sv}",
1346 "origin-desktop-file",
1347 g_variant_new_bytestring(gioDesktopFile));
1348 }
1349
1350 programName=g_get_prgname();
1351 if(programName)
1352 {
1353 g_variant_builder_add(&extras,
1354 "{sv}",
1355 "origin-prgname",
1356 g_variant_new_bytestring(programName));
1357 }
1358
1359 g_variant_builder_add(&extras,
1360 "{sv}",
1361 "origin-pid",
1362 g_variant_new("x", (gint64)getpid()));
1363
1364 if(priv->desktopID) desktopFileID=priv->desktopID;
1365 else if(priv->file) desktopFileID=desktopFile;
1366 else desktopFileID="";
1367
1368 message=g_dbus_message_new_signal("/org/gtk/gio/DesktopAppInfo",
1369 "org.gtk.gio.DesktopAppInfo",
1370 "Launched");
1371 g_dbus_message_set_body(message,
1372 g_variant_new
1373 (
1374 "(@aysxasa{sv})",
1375 g_variant_new_bytestring(desktopFileID),
1376 display ? display : "",
1377 (gint64)launchedPID,
1378 &uris,
1379 &extras
1380 ));
1381 g_dbus_connection_send_message(sessionBus,
1382 message,
1383 0,
1384 NULL,
1385 NULL);
1386
1387 g_object_unref(message);
1388
1389 /* It is safe to unreference DBUS session bus object after
1390 * calling flush function even if the flush function is
1391 * a asynchronous function because it takes its own reference
1392 * on the session bus to keep it alive until flush is complete.
1393 */
1394 g_dbus_connection_flush(sessionBus, NULL, NULL, NULL);
1395 g_object_unref(sessionBus);
1396 }
1397 }
1398 else
1399 {
1400 g_warning("Launching %s failed!", name);
1401
1402 /* Propagate error */
1403 g_propagate_error(outError, error);
1404
1405 /* Tell context about failed application launch */
1406 if(startupNotificationID)
1407 {
1408 g_app_launch_context_launch_failed(inContext, startupNotificationID);
1409 }
1410 }
1411
1412 /* Release allocated resources */
1413 if(expanded) g_free(expanded);
1414 if(argv) g_strfreev(argv);
1415 if(desktopFile) g_free(desktopFile);
1416 if(startupNotificationID) g_free(startupNotificationID);
1417 if(display) g_free(display);
1418
1419 return(success);
1420 }
1421
1422 #endif
1423
1424 /* IMPLEMENTATION: Interface GAppInfo */
1425
1426 /* Create a duplicate of a GAppInfo */
_xfdashboard_desktop_app_info_gappinfo_dup(GAppInfo * inAppInfo)1427 static GAppInfo* _xfdashboard_desktop_app_info_gappinfo_dup(GAppInfo *inAppInfo)
1428 {
1429 XfdashboardDesktopAppInfo *self;
1430 XfdashboardDesktopAppInfoPrivate *priv;
1431
1432 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(inAppInfo), NULL);
1433
1434 self=XFDASHBOARD_DESKTOP_APP_INFO(inAppInfo);
1435 priv=self->priv;
1436
1437 /* Create and return a newly allocated copy */
1438 return(G_APP_INFO(g_object_new(XFDASHBOARD_TYPE_DESKTOP_APP_INFO,
1439 "desktop-id", priv->desktopID,
1440 "file", priv->file,
1441 NULL)));
1442 }
1443
1444 /* Check if two GAppInfos are equal */
_xfdashboard_desktop_app_info_gappinfo_equal(GAppInfo * inLeft,GAppInfo * inRight)1445 static gboolean _xfdashboard_desktop_app_info_gappinfo_equal(GAppInfo *inLeft, GAppInfo *inRight)
1446 {
1447 XfdashboardDesktopAppInfo *left;
1448 XfdashboardDesktopAppInfo *right;
1449
1450 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(inLeft), FALSE);
1451 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(inRight), FALSE);
1452
1453 /* Get object instance for this class of both GAppInfos */
1454 left=XFDASHBOARD_DESKTOP_APP_INFO(inLeft);
1455 right=XFDASHBOARD_DESKTOP_APP_INFO(inRight);
1456
1457 /* If one of both instance do not have a menu item return FALSE */
1458 if(!left->priv->item || !right->priv->item) return(FALSE);
1459
1460 /* Return result of check if menu item of both GAppInfos are equal */
1461 return(garcon_menu_element_equal(GARCON_MENU_ELEMENT(left->priv->item),
1462 GARCON_MENU_ELEMENT(right->priv->item)));
1463 }
1464
1465 /* Get ID of GAppInfo */
_xfdashboard_desktop_app_info_gappinfo_get_id(GAppInfo * inAppInfo)1466 static const gchar* _xfdashboard_desktop_app_info_gappinfo_get_id(GAppInfo *inAppInfo)
1467 {
1468 XfdashboardDesktopAppInfo *self;
1469 XfdashboardDesktopAppInfoPrivate *priv;
1470
1471 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(inAppInfo), NULL);
1472
1473 self=XFDASHBOARD_DESKTOP_APP_INFO(inAppInfo);
1474 priv=self->priv;
1475
1476 /* Return ID of menu item */
1477 return(priv->desktopID);
1478 }
1479
1480 /* Get name of GAppInfo */
_xfdashboard_desktop_app_info_gappinfo_get_name(GAppInfo * inAppInfo)1481 static const gchar* _xfdashboard_desktop_app_info_gappinfo_get_name(GAppInfo *inAppInfo)
1482 {
1483 XfdashboardDesktopAppInfo *self;
1484 XfdashboardDesktopAppInfoPrivate *priv;
1485
1486 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(inAppInfo), NULL);
1487
1488 self=XFDASHBOARD_DESKTOP_APP_INFO(inAppInfo);
1489 priv=self->priv;
1490
1491 /* If desktop app info has no item return NULL here */
1492 if(!priv->item) return(NULL);
1493
1494 /* Return name of menu item */
1495 return(garcon_menu_item_get_name(priv->item));
1496 }
1497
1498 /* Get description of GAppInfo */
_xfdashboard_desktop_app_info_gappinfo_get_description(GAppInfo * inAppInfo)1499 static const gchar* _xfdashboard_desktop_app_info_gappinfo_get_description(GAppInfo *inAppInfo)
1500 {
1501 XfdashboardDesktopAppInfo *self;
1502 XfdashboardDesktopAppInfoPrivate *priv;
1503
1504 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(inAppInfo), NULL);
1505
1506 self=XFDASHBOARD_DESKTOP_APP_INFO(inAppInfo);
1507 priv=self->priv;
1508
1509 /* If desktop app info has no item return NULL here */
1510 if(!priv->item) return(NULL);
1511
1512 /* Return comment of menu item as description */
1513 return(garcon_menu_item_get_comment(priv->item));
1514 }
1515
1516 /* Get path to executable binary of GAppInfo */
_xfdashboard_desktop_app_info_gappinfo_get_executable(GAppInfo * inAppInfo)1517 static const gchar* _xfdashboard_desktop_app_info_gappinfo_get_executable(GAppInfo *inAppInfo)
1518 {
1519 XfdashboardDesktopAppInfo *self;
1520 XfdashboardDesktopAppInfoPrivate *priv;
1521
1522 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(inAppInfo), NULL);
1523
1524 self=XFDASHBOARD_DESKTOP_APP_INFO(inAppInfo);
1525 priv=self->priv;
1526
1527 /* Return comment of menu item as description */
1528 return(priv->binaryExecutable);
1529 }
1530
1531 /* Get icon of GAppInfo */
_xfdashboard_desktop_app_info_gappinfo_get_icon(GAppInfo * inAppInfo)1532 static GIcon* _xfdashboard_desktop_app_info_gappinfo_get_icon(GAppInfo *inAppInfo)
1533 {
1534 XfdashboardDesktopAppInfo *self;
1535 XfdashboardDesktopAppInfoPrivate *priv;
1536 GIcon *icon;
1537 const gchar *iconFilename;
1538
1539 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(inAppInfo), NULL);
1540
1541 self=XFDASHBOARD_DESKTOP_APP_INFO(inAppInfo);
1542 priv=self->priv;
1543 icon=NULL;
1544
1545 /* Create icon from path of menu item */
1546 if(priv->item)
1547 {
1548 iconFilename=garcon_menu_item_get_icon_name(priv->item);
1549 if(iconFilename)
1550 {
1551 if(!g_path_is_absolute(iconFilename)) icon=g_themed_icon_new(iconFilename);
1552 else
1553 {
1554 GFile *file;
1555
1556 file=g_file_new_for_path(iconFilename);
1557 icon=g_file_icon_new(file);
1558 g_object_unref(file);
1559 }
1560 }
1561 }
1562
1563 /* Return icon created */
1564 return(icon);
1565 }
1566
1567 /* Check if GAppInfo supports URIs as command-line parameters */
_xfdashboard_desktop_app_info_gappinfo_supports_uris(GAppInfo * inAppInfo)1568 static gboolean _xfdashboard_desktop_app_info_gappinfo_supports_uris(GAppInfo *inAppInfo)
1569 {
1570 XfdashboardDesktopAppInfo *self;
1571 XfdashboardDesktopAppInfoPrivate *priv;
1572 gboolean result;
1573 const gchar *command;
1574
1575 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(inAppInfo), FALSE);
1576
1577 self=XFDASHBOARD_DESKTOP_APP_INFO(inAppInfo);
1578 priv=self->priv;
1579 result=FALSE;
1580
1581 /* Check if command at menu item contains "%u" or "%U"
1582 * indicating URIs as command-line parameters.
1583 */
1584 if(priv->item)
1585 {
1586 command=garcon_menu_item_get_command(priv->item);
1587 if(command)
1588 {
1589 if(!result && strstr(command, "%u")) result=TRUE;
1590 if(!result && strstr(command, "%U")) result=TRUE;
1591 }
1592 }
1593
1594 /* Return result of check */
1595 return(result);
1596 }
1597
1598 /* Check if GAppInfo supports file paths as command-line parameters */
_xfdashboard_desktop_app_info_gappinfo_supports_files(GAppInfo * inAppInfo)1599 static gboolean _xfdashboard_desktop_app_info_gappinfo_supports_files(GAppInfo *inAppInfo)
1600 {
1601 XfdashboardDesktopAppInfo *self;
1602 XfdashboardDesktopAppInfoPrivate *priv;
1603 gboolean result;
1604 const gchar *command;
1605
1606 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(inAppInfo), FALSE);
1607
1608 self=XFDASHBOARD_DESKTOP_APP_INFO(inAppInfo);
1609 priv=self->priv;
1610 result=FALSE;
1611
1612 /* Check if command at menu item contains "%f" or "%F"
1613 * indicating file paths as command-line parameters.
1614 */
1615 if(priv->item)
1616 {
1617 command=garcon_menu_item_get_command(priv->item);
1618 if(command)
1619 {
1620 if(!result && strstr(command, "%f")) result=TRUE;
1621 if(!result && strstr(command, "%F")) result=TRUE;
1622 }
1623 }
1624
1625 /* Return result of check */
1626 return(result);
1627 }
1628
1629 /* Launch application of GAppInfo with file paths */
_xfdashboard_desktop_app_info_gappinfo_launch(GAppInfo * inAppInfo,GList * inFiles,GAppLaunchContext * inContext,GError ** outError)1630 static gboolean _xfdashboard_desktop_app_info_gappinfo_launch(GAppInfo *inAppInfo,
1631 GList *inFiles,
1632 GAppLaunchContext *inContext,
1633 GError **outError)
1634 {
1635 XfdashboardDesktopAppInfo *self;
1636 XfdashboardDesktopAppInfoPrivate *priv;
1637 GList *iter;
1638 GList *uris;
1639 gchar *uri;
1640 gboolean result;
1641
1642 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(inAppInfo), FALSE);
1643 g_return_val_if_fail(!inContext || G_IS_APP_LAUNCH_CONTEXT(inContext), FALSE);
1644 g_return_val_if_fail(outError && *outError==NULL, FALSE);
1645
1646 self=XFDASHBOARD_DESKTOP_APP_INFO(inAppInfo);
1647 priv=self->priv;
1648 uris=NULL;
1649
1650 /* Create list of URIs for files */
1651 for(iter=inFiles; iter; iter=g_list_next(iter))
1652 {
1653 uri=g_file_get_uri(iter->data);
1654 uris=g_list_prepend(uris, uri);
1655 }
1656 uris=g_list_reverse(uris);
1657
1658 /* Call function to launch application of XfdashboardDesktopAppInfo with URIs */
1659 result=_xfdashboard_desktop_app_info_launch_appinfo_internal(self,
1660 garcon_menu_item_get_command(priv->item),
1661 uris,
1662 inContext,
1663 outError);
1664
1665 /* Release allocated resources */
1666 g_list_free_full(uris, g_free);
1667
1668 return(result);
1669 }
1670
1671 /* Launch application of GAppInfo with URIs */
_xfdashboard_desktop_app_info_gappinfo_launch_uris(GAppInfo * inAppInfo,GList * inURIs,GAppLaunchContext * inContext,GError ** outError)1672 static gboolean _xfdashboard_desktop_app_info_gappinfo_launch_uris(GAppInfo *inAppInfo,
1673 GList *inURIs,
1674 GAppLaunchContext *inContext,
1675 GError **outError)
1676 {
1677 XfdashboardDesktopAppInfo *self;
1678 XfdashboardDesktopAppInfoPrivate *priv;
1679 gboolean result;
1680
1681 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(inAppInfo), FALSE);
1682 g_return_val_if_fail(!inContext || G_IS_APP_LAUNCH_CONTEXT(inContext), FALSE);
1683 g_return_val_if_fail(outError && *outError==NULL, FALSE);
1684
1685 self=XFDASHBOARD_DESKTOP_APP_INFO(inAppInfo);
1686 priv=self->priv;
1687
1688 /* Call function to launch application of XfdashboardDesktopAppInfo with URIs */
1689 result=_xfdashboard_desktop_app_info_launch_appinfo_internal(self,
1690 garcon_menu_item_get_command(priv->item),
1691 inURIs,
1692 inContext,
1693 outError);
1694
1695 return(result);
1696 }
1697
1698 /* Check if the application info should be shown */
_xfdashboard_desktop_app_info_gappinfo_should_show(GAppInfo * inAppInfo)1699 static gboolean _xfdashboard_desktop_app_info_gappinfo_should_show(GAppInfo *inAppInfo)
1700 {
1701 XfdashboardDesktopAppInfo *self;
1702 XfdashboardDesktopAppInfoPrivate *priv;
1703
1704 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(inAppInfo), FALSE);
1705
1706 self=XFDASHBOARD_DESKTOP_APP_INFO(inAppInfo);
1707 priv=self->priv;
1708
1709 /* If desktop app info has no item return FALSE here */
1710 if(!priv->item) return(FALSE);
1711
1712 /* Check if menu item is visible and therefore can be shown */
1713 return(garcon_menu_element_get_visible(GARCON_MENU_ELEMENT(priv->item)));
1714 }
1715
1716 /* Get command-line of GAppInfo with which the application will be started */
_xfdashboard_desktop_app_info_gappinfo_get_commandline(GAppInfo * inAppInfo)1717 static const gchar* _xfdashboard_desktop_app_info_gappinfo_get_commandline(GAppInfo *inAppInfo)
1718 {
1719 XfdashboardDesktopAppInfo *self;
1720 XfdashboardDesktopAppInfoPrivate *priv;
1721
1722 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(inAppInfo), NULL);
1723
1724 self=XFDASHBOARD_DESKTOP_APP_INFO(inAppInfo);
1725 priv=self->priv;
1726
1727 /* If desktop app info has no item return NULL here */
1728 if(!priv->item) return(NULL);
1729
1730 /* Return command of menu item */
1731 return(garcon_menu_item_get_command(priv->item));
1732 }
1733
1734 /* Get display name of GAppInfo */
_xfdashboard_desktop_app_info_gappinfo_get_display_name(GAppInfo * inAppInfo)1735 static const gchar* _xfdashboard_desktop_app_info_gappinfo_get_display_name(GAppInfo *inAppInfo)
1736 {
1737 XfdashboardDesktopAppInfo *self;
1738 XfdashboardDesktopAppInfoPrivate *priv;
1739
1740 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(inAppInfo), NULL);
1741
1742 self=XFDASHBOARD_DESKTOP_APP_INFO(inAppInfo);
1743 priv=self->priv;
1744
1745 /* If desktop app info has no item return NULL here */
1746 if(!priv->item) return(NULL);
1747
1748 /* Return name of menu item */
1749 return(garcon_menu_item_get_name(priv->item));
1750 }
1751
1752 /* Interface initialization
1753 * Set up default functions
1754 */
_xfdashboard_desktop_app_info_gappinfo_iface_init(GAppInfoIface * iface)1755 static void _xfdashboard_desktop_app_info_gappinfo_iface_init(GAppInfoIface *iface)
1756 {
1757 iface->dup=_xfdashboard_desktop_app_info_gappinfo_dup;
1758 iface->equal=_xfdashboard_desktop_app_info_gappinfo_equal;
1759 iface->get_id=_xfdashboard_desktop_app_info_gappinfo_get_id;
1760 iface->get_name=_xfdashboard_desktop_app_info_gappinfo_get_name;
1761 iface->get_description=_xfdashboard_desktop_app_info_gappinfo_get_description;
1762 iface->get_executable=_xfdashboard_desktop_app_info_gappinfo_get_executable;
1763 iface->get_icon=_xfdashboard_desktop_app_info_gappinfo_get_icon;
1764 iface->launch=_xfdashboard_desktop_app_info_gappinfo_launch;
1765 iface->supports_uris=_xfdashboard_desktop_app_info_gappinfo_supports_uris;
1766 iface->supports_files=_xfdashboard_desktop_app_info_gappinfo_supports_files;
1767 iface->launch_uris=_xfdashboard_desktop_app_info_gappinfo_launch_uris;
1768 iface->should_show=_xfdashboard_desktop_app_info_gappinfo_should_show;
1769 iface->get_commandline=_xfdashboard_desktop_app_info_gappinfo_get_commandline;
1770 iface->get_display_name=_xfdashboard_desktop_app_info_gappinfo_get_display_name;
1771 }
1772
1773 /* IMPLEMENTATION: GObject */
1774
1775 /* Dispose this object */
_xfdashboard_desktop_app_info_dispose(GObject * inObject)1776 static void _xfdashboard_desktop_app_info_dispose(GObject *inObject)
1777 {
1778 XfdashboardDesktopAppInfo *self=XFDASHBOARD_DESKTOP_APP_INFO(inObject);
1779 XfdashboardDesktopAppInfoPrivate *priv=self->priv;
1780
1781 /* Release allocated variables */
1782 if(priv->keywords)
1783 {
1784 g_list_free_full(priv->keywords, g_free);
1785 priv->keywords=NULL;
1786 }
1787 priv->needKeywords=TRUE;
1788
1789 if(priv->actions)
1790 {
1791 g_list_free_full(priv->actions, g_object_unref);
1792 priv->actions=NULL;
1793 }
1794 priv->needActions=TRUE;
1795
1796 if(priv->binaryExecutable)
1797 {
1798 g_free(priv->binaryExecutable);
1799 priv->binaryExecutable=NULL;
1800 }
1801
1802 if(priv->item)
1803 {
1804 if(priv->itemChangedID)
1805 {
1806 g_signal_handler_disconnect(priv->item, priv->itemChangedID);
1807 priv->itemChangedID=0;
1808 }
1809
1810 g_object_unref(priv->item);
1811 priv->item=NULL;
1812 }
1813
1814 if(priv->secondarySource)
1815 {
1816 g_key_file_unref(priv->secondarySource);
1817 priv->secondarySource=NULL;
1818 }
1819
1820 if(priv->file)
1821 {
1822 g_object_unref(priv->file);
1823 priv->file=NULL;
1824 }
1825
1826 if(priv->desktopID)
1827 {
1828 g_free(priv->desktopID);
1829 priv->desktopID=NULL;
1830 }
1831
1832 /* Call parent's class dispose method */
1833 G_OBJECT_CLASS(xfdashboard_desktop_app_info_parent_class)->dispose(inObject);
1834 }
1835
1836 /* Set/get properties */
_xfdashboard_desktop_app_info_set_property(GObject * inObject,guint inPropID,const GValue * inValue,GParamSpec * inSpec)1837 static void _xfdashboard_desktop_app_info_set_property(GObject *inObject,
1838 guint inPropID,
1839 const GValue *inValue,
1840 GParamSpec *inSpec)
1841 {
1842 XfdashboardDesktopAppInfo *self=XFDASHBOARD_DESKTOP_APP_INFO(inObject);
1843
1844 switch(inPropID)
1845 {
1846 case PROP_DESKTOP_ID:
1847 _xfdashboard_desktop_app_info_set_desktop_id(self, g_value_get_string(inValue));
1848 break;
1849
1850 case PROP_FILE:
1851 _xfdashboard_desktop_app_info_set_file(self, G_FILE(g_value_get_object(inValue)));
1852 break;
1853
1854 default:
1855 G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
1856 break;
1857 }
1858 }
1859
_xfdashboard_desktop_app_info_get_property(GObject * inObject,guint inPropID,GValue * outValue,GParamSpec * inSpec)1860 static void _xfdashboard_desktop_app_info_get_property(GObject *inObject,
1861 guint inPropID,
1862 GValue *outValue,
1863 GParamSpec *inSpec)
1864 {
1865 XfdashboardDesktopAppInfo *self=XFDASHBOARD_DESKTOP_APP_INFO(inObject);
1866 XfdashboardDesktopAppInfoPrivate *priv=self->priv;
1867
1868 switch(inPropID)
1869 {
1870 case PROP_VALID:
1871 g_value_set_boolean(outValue, priv->isValid);
1872 break;
1873
1874 case PROP_DESKTOP_ID:
1875 g_value_set_string(outValue, priv->desktopID);
1876 break;
1877
1878 case PROP_FILE:
1879 g_value_set_object(outValue, priv->file);
1880 break;
1881
1882 default:
1883 G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
1884 break;
1885 }
1886 }
1887
1888 /* Class initialization
1889 * Override functions in parent classes and define properties
1890 * and signals
1891 */
xfdashboard_desktop_app_info_class_init(XfdashboardDesktopAppInfoClass * klass)1892 static void xfdashboard_desktop_app_info_class_init(XfdashboardDesktopAppInfoClass *klass)
1893 {
1894 GObjectClass *gobjectClass=G_OBJECT_CLASS(klass);
1895
1896 /* Override functions */
1897 gobjectClass->dispose=_xfdashboard_desktop_app_info_dispose;
1898 gobjectClass->set_property=_xfdashboard_desktop_app_info_set_property;
1899 gobjectClass->get_property=_xfdashboard_desktop_app_info_get_property;
1900
1901 /* Define properties */
1902 XfdashboardDesktopAppInfoProperties[PROP_VALID]=
1903 g_param_spec_boolean("valid",
1904 "Valid",
1905 "Flag indicating whether this desktop application information is valid or not",
1906 FALSE,
1907 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1908
1909 XfdashboardDesktopAppInfoProperties[PROP_DESKTOP_ID]=
1910 g_param_spec_string("desktop-id",
1911 "Desktop ID",
1912 "Name of desktop ID",
1913 NULL,
1914 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1915
1916 XfdashboardDesktopAppInfoProperties[PROP_FILE]=
1917 g_param_spec_object("file",
1918 "File",
1919 "The desktop file",
1920 G_TYPE_FILE,
1921 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1922
1923 g_object_class_install_properties(gobjectClass, PROP_LAST, XfdashboardDesktopAppInfoProperties);
1924
1925 /* Define signals */
1926 XfdashboardDesktopAppInfoSignals[SIGNAL_CHANGED]=
1927 g_signal_new("changed",
1928 G_TYPE_FROM_CLASS(klass),
1929 G_SIGNAL_RUN_FIRST,
1930 G_STRUCT_OFFSET(XfdashboardDesktopAppInfoClass, changed),
1931 NULL,
1932 NULL,
1933 g_cclosure_marshal_VOID__VOID,
1934 G_TYPE_NONE,
1935 0);
1936 }
1937
1938 /* Object initialization
1939 * Create private structure and set up default values
1940 */
xfdashboard_desktop_app_info_init(XfdashboardDesktopAppInfo * self)1941 static void xfdashboard_desktop_app_info_init(XfdashboardDesktopAppInfo *self)
1942 {
1943 XfdashboardDesktopAppInfoPrivate *priv;
1944
1945 priv=self->priv=xfdashboard_desktop_app_info_get_instance_private(self);
1946
1947 /* Set up default values */
1948 priv->inited=FALSE;
1949 priv->isValid=FALSE;
1950 priv->desktopID=NULL;
1951 priv->file=NULL;
1952 priv->item=NULL;
1953 priv->itemChangedID=0;
1954 priv->binaryExecutable=NULL;
1955 priv->actions=NULL;
1956 priv->needActions=TRUE;
1957 priv->keywords=NULL;
1958 priv->needKeywords=TRUE;
1959 }
1960
1961 /* IMPLEMENTATION: Public API */
1962
1963 /* Create new instance */
xfdashboard_desktop_app_info_new_from_desktop_id(const gchar * inDesktopID)1964 GAppInfo* xfdashboard_desktop_app_info_new_from_desktop_id(const gchar *inDesktopID)
1965 {
1966 XfdashboardDesktopAppInfo *instance;
1967 gchar *desktopFilename;
1968 GFile *file;
1969
1970 g_return_val_if_fail(inDesktopID && *inDesktopID, NULL);
1971
1972 instance=NULL;
1973
1974 /* Find desktop file for desktop ID */
1975 desktopFilename=xfdashboard_application_database_get_file_from_desktop_id(inDesktopID);
1976 if(!desktopFilename)
1977 {
1978 g_warning("Desktop ID '%s' not found", inDesktopID);
1979 return(NULL);
1980 }
1981
1982 /* Create this class instance for desktop file found */
1983 file=g_file_new_for_path(desktopFilename);
1984 instance=XFDASHBOARD_DESKTOP_APP_INFO(g_object_new(XFDASHBOARD_TYPE_DESKTOP_APP_INFO,
1985 "desktop-id", inDesktopID,
1986 "file", file,
1987 NULL));
1988 XFDASHBOARD_DEBUG(instance, APPLICATIONS,
1989 "Created %s desktop file '%s' for desktop ID '%s'",
1990 G_OBJECT_TYPE_NAME(instance),
1991 desktopFilename,
1992 inDesktopID);
1993 if(file) g_object_unref(file);
1994
1995 /* Release allocated resources */
1996 if(desktopFilename) g_free(desktopFilename);
1997
1998 /* Return created instance */
1999 return(G_APP_INFO(instance));
2000 }
2001
xfdashboard_desktop_app_info_new_from_path(const gchar * inPath)2002 GAppInfo* xfdashboard_desktop_app_info_new_from_path(const gchar *inPath)
2003 {
2004 XfdashboardDesktopAppInfo *instance;
2005 GFile *file;
2006
2007 g_return_val_if_fail(inPath && *inPath, NULL);
2008
2009 /* Create this class instance for given URI */
2010 file=g_file_new_for_path(inPath);
2011 instance=XFDASHBOARD_DESKTOP_APP_INFO(g_object_new(XFDASHBOARD_TYPE_DESKTOP_APP_INFO,
2012 "file", file,
2013 NULL));
2014 if(file) g_object_unref(file);
2015
2016 /* Return created instance */
2017 return(G_APP_INFO(instance));
2018 }
2019
xfdashboard_desktop_app_info_new_from_file(GFile * inFile)2020 GAppInfo* xfdashboard_desktop_app_info_new_from_file(GFile *inFile)
2021 {
2022 g_return_val_if_fail(G_IS_FILE(inFile), NULL);
2023
2024 /* Return created instance for given file object */
2025 return(G_APP_INFO(g_object_new(XFDASHBOARD_TYPE_DESKTOP_APP_INFO,
2026 "file", inFile,
2027 NULL)));
2028 }
2029
xfdashboard_desktop_app_info_new_from_menu_item(GarconMenuItem * inMenuItem)2030 GAppInfo* xfdashboard_desktop_app_info_new_from_menu_item(GarconMenuItem *inMenuItem)
2031 {
2032 XfdashboardDesktopAppInfo *instance;
2033 const gchar *desktopID;
2034 GFile *file;
2035
2036 g_return_val_if_fail(GARCON_IS_MENU_ITEM(inMenuItem), NULL);
2037
2038 /* Create this class instance from menu item loaded from given URI */
2039 instance=XFDASHBOARD_DESKTOP_APP_INFO(g_object_new(XFDASHBOARD_TYPE_DESKTOP_APP_INFO, NULL));
2040
2041 /* Set menu item but increase reference counter */
2042 instance->priv->item=GARCON_MENU_ITEM(g_object_ref(inMenuItem));
2043
2044 /* Copy desktop ID from menu item if available */
2045 desktopID=garcon_menu_item_get_desktop_id(inMenuItem);
2046 if(desktopID) g_object_set(instance, "desktop-id", desktopID, NULL);
2047
2048 /* Copy file object and do not use g_object_set to set it
2049 * in created instance to prevent the property setter function
2050 * _xfdashboard_desktop_app_info_set_file to be called which
2051 * would unreference the just referenced menu item.
2052 */
2053 file=garcon_menu_item_get_file(inMenuItem);
2054 instance->priv->file=G_FILE(g_object_ref(file));
2055 g_object_unref(file);
2056
2057 /* Desktop app info is inited now */
2058 instance->priv->inited=TRUE;
2059
2060 /* Return created instance */
2061 return(G_APP_INFO(instance));
2062 }
2063
2064 /* Determine if desktop app info is valid */
xfdashboard_desktop_app_info_is_valid(XfdashboardDesktopAppInfo * self)2065 gboolean xfdashboard_desktop_app_info_is_valid(XfdashboardDesktopAppInfo *self)
2066 {
2067 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self), FALSE);
2068
2069 return(self->priv->isValid);
2070 }
2071
2072 /* Get file of desktop app info */
xfdashboard_desktop_app_info_get_file(XfdashboardDesktopAppInfo * self)2073 GFile* xfdashboard_desktop_app_info_get_file(XfdashboardDesktopAppInfo *self)
2074 {
2075 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self), NULL);
2076
2077 return(self->priv->file);
2078 }
2079
2080 /* Reload desktop app info */
xfdashboard_desktop_app_info_reload(XfdashboardDesktopAppInfo * self)2081 gboolean xfdashboard_desktop_app_info_reload(XfdashboardDesktopAppInfo *self)
2082 {
2083 XfdashboardDesktopAppInfoPrivate *priv;
2084 gboolean success;
2085
2086 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self), FALSE);
2087
2088 priv=self->priv;
2089 success=FALSE;
2090
2091 /* Release secondary source if available to enforce reload when updating
2092 * data depending secondard source.
2093 */
2094 if(priv->secondarySource)
2095 {
2096 g_key_file_unref(priv->secondarySource);
2097 priv->secondarySource=NULL;
2098 }
2099
2100 /* Reload menu item */
2101 if(priv->item)
2102 {
2103 GError *error;
2104
2105 error=NULL;
2106 success=garcon_menu_item_reload(priv->item, NULL, &error);
2107 if(!success)
2108 {
2109 g_warning("Could not reload desktop application information for '%s': %s",
2110 garcon_menu_item_get_name(priv->item),
2111 error ? error->message : "Unknown error");
2112 if(error) g_error_free(error);
2113 }
2114
2115 /* Update path to executable file for this application */
2116 _xfdashboard_desktop_app_info_update_binary_executable(self);
2117
2118 /* Set flag to reload application actions and keywords. They will be
2119 * cleared and (re-)loaded on-demand.
2120 */
2121 priv->needActions=TRUE;
2122 priv->needKeywords=TRUE;
2123 }
2124
2125 /* If reload was successful emit changed signal */
2126 if(success)
2127 {
2128 g_signal_emit(self, XfdashboardDesktopAppInfoSignals[SIGNAL_CHANGED], 0);
2129 }
2130
2131 /* Set valid flag depending on reload success but only if changed */
2132 if(priv->isValid!=success)
2133 {
2134 /* Set value */
2135 priv->isValid=success;
2136
2137 /* Notify about property change */
2138 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardDesktopAppInfoProperties[PROP_VALID]);
2139 }
2140
2141 /* Return success result */
2142 return(success);
2143 }
2144
2145 /* Get list of application actions of desktop app info */
xfdashboard_desktop_app_info_get_actions(XfdashboardDesktopAppInfo * self)2146 GList* xfdashboard_desktop_app_info_get_actions(XfdashboardDesktopAppInfo *self)
2147 {
2148 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self), FALSE);
2149
2150 /* Update list of application actions */
2151 _xfdashboard_desktop_app_info_update_actions(self);
2152
2153 /* Return the list of application actions */
2154 return(self->priv->actions);
2155 }
2156
2157 /* Launch application action of desktop app info */
xfdashboard_desktop_app_info_launch_action(XfdashboardDesktopAppInfo * self,XfdashboardDesktopAppInfoAction * inAction,GAppLaunchContext * inContext,GError ** outError)2158 gboolean xfdashboard_desktop_app_info_launch_action(XfdashboardDesktopAppInfo *self,
2159 XfdashboardDesktopAppInfoAction *inAction,
2160 GAppLaunchContext *inContext,
2161 GError **outError)
2162 {
2163 const gchar *actionName;
2164 gboolean success;
2165
2166 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self), FALSE);
2167 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO_ACTION(inAction), FALSE);
2168 g_return_val_if_fail(!inContext || G_IS_APP_LAUNCH_CONTEXT(inContext), FALSE);
2169 g_return_val_if_fail(outError && *outError==NULL, FALSE);
2170
2171 /* Launch by application action's name as it will lookup a maybe updated
2172 * action, e.g. when reloaded in the meantime.
2173 */
2174 actionName=xfdashboard_desktop_app_info_action_get_name(inAction);
2175 success=xfdashboard_desktop_app_info_launch_action_by_name(self,
2176 actionName,
2177 inContext,
2178 outError);
2179
2180 /* Return success result */
2181 return(success);
2182 }
2183
xfdashboard_desktop_app_info_launch_action_by_name(XfdashboardDesktopAppInfo * self,const gchar * inActionName,GAppLaunchContext * inContext,GError ** outError)2184 gboolean xfdashboard_desktop_app_info_launch_action_by_name(XfdashboardDesktopAppInfo *self,
2185 const gchar *inActionName,
2186 GAppLaunchContext *inContext,
2187 GError **outError)
2188 {
2189 XfdashboardDesktopAppInfoPrivate *priv;
2190 XfdashboardDesktopAppInfoAction *action;
2191 GList *iter;
2192 gboolean success;
2193
2194 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self), FALSE);
2195 g_return_val_if_fail(inActionName && *inActionName, FALSE);
2196 g_return_val_if_fail(!inContext || G_IS_APP_LAUNCH_CONTEXT(inContext), FALSE);
2197 g_return_val_if_fail(outError && *outError==NULL, FALSE);
2198
2199 priv=self->priv;
2200
2201 /* Find application action data by name */
2202 action=NULL;
2203 for(iter=priv->actions; iter && !action; iter=g_list_next(iter))
2204 {
2205 XfdashboardDesktopAppInfoAction *iterAction;
2206
2207 iterAction=XFDASHBOARD_DESKTOP_APP_INFO_ACTION(iter->data);
2208 if(!iterAction) continue;
2209
2210 if(g_strcmp0(xfdashboard_desktop_app_info_action_get_name(iterAction), inActionName)==0)
2211 {
2212 action=iterAction;
2213 }
2214 }
2215
2216 if(!action)
2217 {
2218 /* Set error */
2219 g_set_error(outError,
2220 G_IO_ERROR,
2221 G_IO_ERROR_NOT_FOUND,
2222 "Invalid application action '%s' to execute for desktop ID '%s'",
2223 inActionName,
2224 priv->desktopID);
2225
2226 /* Return fail status */
2227 return(FALSE);
2228 }
2229
2230 /* Launch application action found */
2231 success=_xfdashboard_desktop_app_info_launch_appinfo_internal(self,
2232 xfdashboard_desktop_app_info_action_get_command(action),
2233 NULL,
2234 inContext,
2235 outError);
2236 if(!success)
2237 {
2238 g_warning("Could launch action '%s' for desktop ID '%s': %s",
2239 xfdashboard_desktop_app_info_action_get_name(action),
2240 self->priv->desktopID,
2241 (outError && *outError) ? (*outError)->message : "Unknown error");
2242 }
2243
2244 /* Return success result of launching action */
2245 return(success);
2246 }
2247
2248 /* Get list of keywords of desktop app info */
xfdashboard_desktop_app_info_get_keywords(XfdashboardDesktopAppInfo * self)2249 GList* xfdashboard_desktop_app_info_get_keywords(XfdashboardDesktopAppInfo *self)
2250 {
2251 g_return_val_if_fail(XFDASHBOARD_IS_DESKTOP_APP_INFO(self), FALSE);
2252
2253 /* Update list of keywords */
2254 _xfdashboard_desktop_app_info_update_keywords(self);
2255
2256 /* Return the list of keywords */
2257 return(self->priv->keywords);
2258 }
2259