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