1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 
3 #include "config.h"
4 
5 #include <string.h>
6 
7 #include <glib/gi18n-lib.h>
8 
9 #include <meta/display.h>
10 #include <meta/meta-workspace-manager.h>
11 #include <meta/meta-x11-display.h>
12 
13 #include "shell-app-private.h"
14 #include "shell-enum-types.h"
15 #include "shell-global.h"
16 #include "shell-util.h"
17 #include "shell-app-system-private.h"
18 #include "shell-window-tracker-private.h"
19 #include "st.h"
20 #include "gtkactionmuxer.h"
21 #include "org-gtk-application.h"
22 #include "switcheroo-control.h"
23 
24 #ifdef HAVE_SYSTEMD
25 #include <systemd/sd-journal.h>
26 #include <errno.h>
27 #include <unistd.h>
28 #endif
29 
30 /* This is mainly a memory usage optimization - the user is going to
31  * be running far fewer of the applications at one time than they have
32  * installed.  But it also just helps keep the code more logically
33  * separated.
34  */
35 typedef struct {
36   guint refcount;
37 
38   /* Signal connection to dirty window sort list on workspace changes */
39   gulong workspace_switch_id;
40 
41   gulong icon_changed_id;
42 
43   GSList *windows;
44 
45   guint interesting_windows;
46 
47   /* Whether or not we need to resort the windows; this is done on demand */
48   guint window_sort_stale : 1;
49 
50   /* See GApplication documentation */
51   GtkActionMuxer   *muxer;
52   char             *unique_bus_name;
53   GDBusConnection  *session;
54 
55   /* GDBus Proxy for getting application busy state */
56   ShellOrgGtkApplication *application_proxy;
57   GCancellable           *cancellable;
58 
59 } ShellAppRunningState;
60 
61 /**
62  * SECTION:shell-app
63  * @short_description: Object representing an application
64  *
65  * This object wraps a #GDesktopAppInfo, providing methods and signals
66  * primarily useful for running applications.
67  */
68 struct _ShellApp
69 {
70   GObject parent;
71 
72   int started_on_workspace;
73 
74   ShellAppState state;
75 
76   GDesktopAppInfo *info; /* If NULL, this app is backed by one or more
77                           * MetaWindow.  For purposes of app title
78                           * etc., we use the first window added,
79                           * because it's most likely to be what we
80                           * want (e.g. it will be of TYPE_NORMAL from
81                           * the way shell-window-tracker.c works).
82                           */
83   GIcon *fallback_icon;
84 
85   ShellAppRunningState *running_state;
86 
87   char *window_id_string;
88   char *name_collation_key;
89 };
90 
91 enum {
92   PROP_0,
93   PROP_STATE,
94   PROP_BUSY,
95   PROP_ID,
96   PROP_DBUS_ID,
97   PROP_ACTION_GROUP,
98   PROP_ICON,
99   PROP_APP_INFO
100 };
101 
102 enum {
103   WINDOWS_CHANGED,
104   LAST_SIGNAL
105 };
106 
107 static guint shell_app_signals[LAST_SIGNAL] = { 0 };
108 
109 static void create_running_state (ShellApp *app);
110 static void unref_running_state (ShellAppRunningState *state);
111 
G_DEFINE_TYPE(ShellApp,shell_app,G_TYPE_OBJECT)112 G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT)
113 
114 static void
115 shell_app_get_property (GObject    *gobject,
116                         guint       prop_id,
117                         GValue     *value,
118                         GParamSpec *pspec)
119 {
120   ShellApp *app = SHELL_APP (gobject);
121 
122   switch (prop_id)
123     {
124     case PROP_STATE:
125       g_value_set_enum (value, app->state);
126       break;
127     case PROP_BUSY:
128       g_value_set_boolean (value, shell_app_get_busy (app));
129       break;
130     case PROP_ID:
131       g_value_set_string (value, shell_app_get_id (app));
132       break;
133     case PROP_ICON:
134       g_value_set_object (value, shell_app_get_icon (app));
135       break;
136     case PROP_ACTION_GROUP:
137       if (app->running_state)
138         g_value_set_object (value, app->running_state->muxer);
139       break;
140     case PROP_APP_INFO:
141       if (app->info)
142         g_value_set_object (value, app->info);
143       break;
144     default:
145       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
146       break;
147     }
148 }
149 
150 static void
shell_app_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * pspec)151 shell_app_set_property (GObject      *gobject,
152                         guint         prop_id,
153                         const GValue *value,
154                         GParamSpec   *pspec)
155 {
156   ShellApp *app = SHELL_APP (gobject);
157 
158   switch (prop_id)
159     {
160     case PROP_APP_INFO:
161       _shell_app_set_app_info (app, g_value_get_object (value));
162       break;
163     default:
164       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
165       break;
166     }
167 }
168 
169 const char *
shell_app_get_id(ShellApp * app)170 shell_app_get_id (ShellApp *app)
171 {
172   if (app->info)
173     return g_app_info_get_id (G_APP_INFO (app->info));
174   return app->window_id_string;
175 }
176 
177 static MetaWindow *
window_backed_app_get_window(ShellApp * app)178 window_backed_app_get_window (ShellApp     *app)
179 {
180   g_assert (app->info == NULL);
181   if (app->running_state)
182     {
183       g_assert (app->running_state->windows);
184       return app->running_state->windows->data;
185     }
186   else
187     return NULL;
188 }
189 
190 static GIcon *
x11_window_create_fallback_gicon(MetaWindow * window)191 x11_window_create_fallback_gicon (MetaWindow *window)
192 {
193   StTextureCache *texture_cache;
194   cairo_surface_t *surface;
195 
196   g_object_get (window, "icon", &surface, NULL);
197 
198   texture_cache = st_texture_cache_get_default ();
199   return st_texture_cache_load_cairo_surface_to_gicon (texture_cache, surface);
200 }
201 
202 static void
on_window_icon_changed(GObject * object,const GParamSpec * pspec,gpointer user_data)203 on_window_icon_changed (GObject          *object,
204                         const GParamSpec *pspec,
205                         gpointer          user_data)
206 {
207   MetaWindow *window = META_WINDOW (object);
208   ShellApp *app = user_data;
209 
210   g_clear_object (&app->fallback_icon);
211   app->fallback_icon = x11_window_create_fallback_gicon (window);
212 
213   g_object_notify (G_OBJECT (app), "icon");
214 }
215 
216 /**
217  * shell_app_get_icon:
218  *
219  * Look up the icon for this application
220  *
221  * Return value: (transfer none): A #GIcon
222  */
223 GIcon *
shell_app_get_icon(ShellApp * app)224 shell_app_get_icon (ShellApp *app)
225 {
226   MetaWindow *window = NULL;
227 
228   g_return_val_if_fail (SHELL_IS_APP (app), NULL);
229 
230   if (app->info)
231     return g_app_info_get_icon (G_APP_INFO (app->info));
232 
233   if (app->fallback_icon)
234     return app->fallback_icon;
235 
236   /* During a state transition from running to not-running for
237    * window-backend apps, it's possible we get a request for the icon.
238    * Avoid asserting here and just return a fallback icon
239    */
240   if (app->running_state != NULL)
241     window = window_backed_app_get_window (app);
242 
243   if (window &&
244       meta_window_get_client_type (window) == META_WINDOW_CLIENT_TYPE_X11)
245     {
246       app->fallback_icon = x11_window_create_fallback_gicon (window);
247       app->running_state->icon_changed_id =
248         g_signal_connect (G_OBJECT (window),
249                          "notify::icon", G_CALLBACK (on_window_icon_changed), app);
250     }
251   else
252     {
253       app->fallback_icon = g_themed_icon_new ("application-x-executable");
254     }
255 
256   return app->fallback_icon;
257 }
258 
259 /**
260  * shell_app_create_icon_texture:
261  *
262  * Look up the icon for this application, and create a #ClutterActor
263  * for it at the given size.
264  *
265  * Return value: (transfer none): A floating #ClutterActor
266  */
267 ClutterActor *
shell_app_create_icon_texture(ShellApp * app,int size)268 shell_app_create_icon_texture (ShellApp   *app,
269                                int         size)
270 {
271   ClutterActor *ret;
272 
273   ret = st_icon_new ();
274   st_icon_set_icon_size (ST_ICON (ret), size);
275   st_icon_set_fallback_icon_name (ST_ICON (ret), "application-x-executable");
276 
277   g_object_bind_property (app, "icon", ret, "gicon", G_BINDING_SYNC_CREATE);
278 
279   if (shell_app_is_window_backed (app))
280     st_widget_add_style_class_name (ST_WIDGET (ret), "fallback-app-icon");
281 
282   return ret;
283 }
284 
285 const char *
shell_app_get_name(ShellApp * app)286 shell_app_get_name (ShellApp *app)
287 {
288   if (app->info)
289     return g_app_info_get_name (G_APP_INFO (app->info));
290   else
291     {
292       MetaWindow *window = window_backed_app_get_window (app);
293       const char *name = NULL;
294 
295       if (window)
296         name = meta_window_get_wm_class (window);
297       if (!name)
298         name = C_("program", "Unknown");
299       return name;
300     }
301 }
302 
303 const char *
shell_app_get_description(ShellApp * app)304 shell_app_get_description (ShellApp *app)
305 {
306   if (app->info)
307     return g_app_info_get_description (G_APP_INFO (app->info));
308   else
309     return NULL;
310 }
311 
312 /**
313  * shell_app_is_window_backed:
314  *
315  * A window backed application is one which represents just an open
316  * window, i.e. there's no .desktop file association, so we don't know
317  * how to launch it again.
318  */
319 gboolean
shell_app_is_window_backed(ShellApp * app)320 shell_app_is_window_backed (ShellApp *app)
321 {
322   return app->info == NULL;
323 }
324 
325 typedef struct {
326   MetaWorkspace *workspace;
327   GSList **transients;
328 } CollectTransientsData;
329 
330 static gboolean
collect_transients_on_workspace(MetaWindow * window,gpointer datap)331 collect_transients_on_workspace (MetaWindow *window,
332                                  gpointer    datap)
333 {
334   CollectTransientsData *data = datap;
335 
336   if (data->workspace && meta_window_get_workspace (window) != data->workspace)
337     return TRUE;
338 
339   *data->transients = g_slist_prepend (*data->transients, window);
340   return TRUE;
341 }
342 
343 /* The basic idea here is that when we're targeting a window,
344  * if it has transients we want to pick the most recent one
345  * the user interacted with.
346  * This function makes raising GEdit with the file chooser
347  * open work correctly.
348  */
349 static MetaWindow *
find_most_recent_transient_on_same_workspace(MetaDisplay * display,MetaWindow * reference)350 find_most_recent_transient_on_same_workspace (MetaDisplay *display,
351                                               MetaWindow  *reference)
352 {
353   GSList *transients, *transients_sorted, *iter;
354   MetaWindow *result;
355   CollectTransientsData data;
356 
357   transients = NULL;
358   data.workspace = meta_window_get_workspace (reference);
359   data.transients = &transients;
360 
361   meta_window_foreach_transient (reference, collect_transients_on_workspace, &data);
362 
363   transients_sorted = meta_display_sort_windows_by_stacking (display, transients);
364   /* Reverse this so we're top-to-bottom (yes, we should probably change the order
365    * returned from the sort_windows_by_stacking function)
366    */
367   transients_sorted = g_slist_reverse (transients_sorted);
368   g_slist_free (transients);
369   transients = NULL;
370 
371   result = NULL;
372   for (iter = transients_sorted; iter; iter = iter->next)
373     {
374       MetaWindow *window = iter->data;
375       MetaWindowType wintype = meta_window_get_window_type (window);
376 
377       /* Don't want to focus UTILITY types, like the Gimp toolbars */
378       if (wintype == META_WINDOW_NORMAL ||
379           wintype == META_WINDOW_DIALOG)
380         {
381           result = window;
382           break;
383         }
384     }
385   g_slist_free (transients_sorted);
386   return result;
387 }
388 
389 static MetaWorkspace *
get_active_workspace(void)390 get_active_workspace (void)
391 {
392   ShellGlobal *global = shell_global_get ();
393   MetaDisplay *display = shell_global_get_display (global);
394   MetaWorkspaceManager *workspace_manager =
395     meta_display_get_workspace_manager (display);
396 
397   return meta_workspace_manager_get_active_workspace (workspace_manager);
398 }
399 
400 /**
401  * shell_app_activate_window:
402  * @app: a #ShellApp
403  * @window: (nullable): Window to be focused
404  * @timestamp: Event timestamp
405  *
406  * Bring all windows for the given app to the foreground,
407  * but ensure that @window is on top.  If @window is %NULL,
408  * the window with the most recent user time for the app
409  * will be used.
410  *
411  * This function has no effect if @app is not currently running.
412  */
413 void
shell_app_activate_window(ShellApp * app,MetaWindow * window,guint32 timestamp)414 shell_app_activate_window (ShellApp     *app,
415                            MetaWindow   *window,
416                            guint32       timestamp)
417 {
418   GSList *windows;
419 
420   if (shell_app_get_state (app) != SHELL_APP_STATE_RUNNING)
421     return;
422 
423   windows = shell_app_get_windows (app);
424   if (window == NULL && windows)
425     window = windows->data;
426 
427   if (!g_slist_find (windows, window))
428     return;
429   else
430     {
431       GSList *windows_reversed, *iter;
432       ShellGlobal *global = shell_global_get ();
433       MetaDisplay *display = shell_global_get_display (global);
434       MetaWorkspace *active = get_active_workspace ();
435       MetaWorkspace *workspace = meta_window_get_workspace (window);
436       guint32 last_user_timestamp = meta_display_get_last_user_time (display);
437       MetaWindow *most_recent_transient;
438 
439       if (meta_display_xserver_time_is_before (display, timestamp, last_user_timestamp))
440         {
441           meta_window_set_demands_attention (window);
442           return;
443         }
444 
445       /* Now raise all the other windows for the app that are on
446        * the same workspace, in reverse order to preserve the stacking.
447        */
448       windows_reversed = g_slist_copy (windows);
449       windows_reversed = g_slist_reverse (windows_reversed);
450       for (iter = windows_reversed; iter; iter = iter->next)
451         {
452           MetaWindow *other_window = iter->data;
453 
454           if (other_window != window && meta_window_get_workspace (other_window) == workspace)
455             meta_window_raise (other_window);
456         }
457       g_slist_free (windows_reversed);
458 
459       /* If we have a transient that the user's interacted with more recently than
460        * the window, pick that.
461        */
462       most_recent_transient = find_most_recent_transient_on_same_workspace (display, window);
463       if (most_recent_transient
464           && meta_display_xserver_time_is_before (display,
465                                                   meta_window_get_user_time (window),
466                                                   meta_window_get_user_time (most_recent_transient)))
467         window = most_recent_transient;
468 
469 
470       if (active != workspace)
471         meta_workspace_activate_with_focus (workspace, window, timestamp);
472       else
473         meta_window_activate (window, timestamp);
474     }
475 }
476 
477 
478 void
shell_app_update_window_actions(ShellApp * app,MetaWindow * window)479 shell_app_update_window_actions (ShellApp *app, MetaWindow *window)
480 {
481   const char *object_path;
482 
483   object_path = meta_window_get_gtk_window_object_path (window);
484   if (object_path != NULL)
485     {
486       GActionGroup *actions;
487 
488       actions = g_object_get_data (G_OBJECT (window), "actions");
489       if (actions == NULL)
490         {
491           actions = G_ACTION_GROUP (g_dbus_action_group_get (app->running_state->session,
492                                                              meta_window_get_gtk_unique_bus_name (window),
493                                                              object_path));
494           g_object_set_data_full (G_OBJECT (window), "actions", actions, g_object_unref);
495         }
496 
497       g_assert (app->running_state->muxer);
498       gtk_action_muxer_insert (app->running_state->muxer, "win", actions);
499       g_object_notify (G_OBJECT (app), "action-group");
500     }
501 }
502 
503 /**
504  * shell_app_activate:
505  * @app: a #ShellApp
506  *
507  * Like shell_app_activate_full(), but using the default workspace and
508  * event timestamp.
509  */
510 void
shell_app_activate(ShellApp * app)511 shell_app_activate (ShellApp      *app)
512 {
513   return shell_app_activate_full (app, -1, 0);
514 }
515 
516 /**
517  * shell_app_activate_full:
518  * @app: a #ShellApp
519  * @workspace: launch on this workspace, or -1 for default. Ignored if
520  *   activating an existing window
521  * @timestamp: Event timestamp
522  *
523  * Perform an appropriate default action for operating on this application,
524  * dependent on its current state.  For example, if the application is not
525  * currently running, launch it.  If it is running, activate the most
526  * recently used NORMAL window (or if that window has a transient, the most
527  * recently used transient for that window).
528  */
529 void
shell_app_activate_full(ShellApp * app,int workspace,guint32 timestamp)530 shell_app_activate_full (ShellApp      *app,
531                          int            workspace,
532                          guint32        timestamp)
533 {
534   ShellGlobal *global;
535 
536   global = shell_global_get ();
537 
538   if (timestamp == 0)
539     timestamp = shell_global_get_current_time (global);
540 
541   switch (app->state)
542     {
543       case SHELL_APP_STATE_STOPPED:
544         {
545           GError *error = NULL;
546           if (!shell_app_launch (app, timestamp, workspace, SHELL_APP_LAUNCH_GPU_APP_PREF, &error))
547             {
548               char *msg;
549               msg = g_strdup_printf (_("Failed to launch “%s”"), shell_app_get_name (app));
550               shell_global_notify_error (global,
551                                          msg,
552                                          error->message);
553               g_free (msg);
554               g_clear_error (&error);
555             }
556         }
557         break;
558       case SHELL_APP_STATE_STARTING:
559         break;
560       case SHELL_APP_STATE_RUNNING:
561         shell_app_activate_window (app, NULL, timestamp);
562         break;
563       default:
564         g_assert_not_reached();
565         break;
566     }
567 }
568 
569 /**
570  * shell_app_open_new_window:
571  * @app: a #ShellApp
572  * @workspace: open on this workspace, or -1 for default
573  *
574  * Request that the application create a new window.
575  */
576 void
shell_app_open_new_window(ShellApp * app,int workspace)577 shell_app_open_new_window (ShellApp      *app,
578                            int            workspace)
579 {
580   GActionGroup *group = NULL;
581   const char * const *actions;
582 
583   g_return_if_fail (app->info != NULL);
584 
585   /* First check whether the application provides a "new-window" desktop
586    * action - it is a safe bet that it will open a new window, and activating
587    * it will trigger startup notification if necessary
588    */
589   actions = g_desktop_app_info_list_actions (G_DESKTOP_APP_INFO (app->info));
590 
591   if (g_strv_contains (actions, "new-window"))
592     {
593       shell_app_launch_action (app, "new-window", 0, workspace);
594       return;
595     }
596 
597   /* Next, check whether the app exports an explicit "new-window" action
598    * that we can activate on the bus - the muxer will add startup notification
599    * information to the platform data, so this should work just as well as
600    * desktop actions.
601    */
602   group = app->running_state ? G_ACTION_GROUP (app->running_state->muxer)
603                              : NULL;
604 
605   if (group &&
606       g_action_group_has_action (group, "app.new-window") &&
607       g_action_group_get_action_parameter_type (group, "app.new-window") == NULL)
608     {
609       g_action_group_activate_action (group, "app.new-window", NULL);
610 
611       return;
612     }
613 
614   /* Lastly, just always launch the application again, even if we know
615    * it was already running.  For most applications this
616    * should have the effect of creating a new window, whether that's
617    * a second process (in the case of Calculator) or IPC to existing
618    * instance (Firefox).  There are a few less-sensical cases such
619    * as say Pidgin.
620    */
621   shell_app_launch (app, 0, workspace, SHELL_APP_LAUNCH_GPU_APP_PREF, NULL);
622 }
623 
624 /**
625  * shell_app_can_open_new_window:
626  * @app: a #ShellApp
627  *
628  * Returns %TRUE if the app supports opening a new window through
629  * shell_app_open_new_window() (ie, if calling that function will
630  * result in actually opening a new window and not something else,
631  * like presenting the most recently active one)
632  */
633 gboolean
shell_app_can_open_new_window(ShellApp * app)634 shell_app_can_open_new_window (ShellApp *app)
635 {
636   ShellAppRunningState *state;
637   MetaWindow *window;
638   GDesktopAppInfo *desktop_info;
639   const char * const *desktop_actions;
640 
641   /* Apps that are stopped can always open new windows, because
642    * activating them would open the first one; if they are starting,
643    * we cannot tell whether they can open additional windows until
644    * they are running */
645   if (app->state != SHELL_APP_STATE_RUNNING)
646     return app->state == SHELL_APP_STATE_STOPPED;
647 
648   state = app->running_state;
649 
650   /* If the app has an explicit new-window action, then it can
651      (or it should be able to) ...
652   */
653   if (g_action_group_has_action (G_ACTION_GROUP (state->muxer), "app.new-window"))
654     return TRUE;
655 
656   /* If the app doesn't have a desktop file, then nothing is possible */
657   if (!app->info)
658     return FALSE;
659 
660   desktop_info = G_DESKTOP_APP_INFO (app->info);
661 
662   /* If the app is explicitly telling us, then we know for sure */
663   if (g_desktop_app_info_has_key (desktop_info, "X-GNOME-SingleWindow"))
664     return !g_desktop_app_info_get_boolean (desktop_info,
665                                             "X-GNOME-SingleWindow");
666 
667   /* If it has a new-window desktop action, it should be able to */
668   desktop_actions = g_desktop_app_info_list_actions (desktop_info);
669   if (desktop_actions && g_strv_contains (desktop_actions, "new-window"))
670     return TRUE;
671 
672   /* If this is a unique GtkApplication, and we don't have a new-window, then
673      probably we can't
674 
675      We don't consider non-unique GtkApplications here to handle cases like
676      evince, which don't export a new-window action because each window is in
677      a different process. In any case, in a non-unique GtkApplication each
678      Activate() knows nothing about the other instances, so it will show a
679      new window.
680   */
681 
682   window = state->windows->data;
683 
684   if (state->unique_bus_name != NULL &&
685       meta_window_get_gtk_application_object_path (window) != NULL)
686     {
687       if (meta_window_get_gtk_application_id (window) != NULL)
688         return FALSE;
689       else
690         return TRUE;
691     }
692 
693   /* In all other cases, we don't have a reliable source of information
694      or a decent heuristic, so we err on the compatibility side and say
695      yes.
696   */
697   return TRUE;
698 }
699 
700 /**
701  * shell_app_get_state:
702  * @app: a #ShellApp
703  *
704  * Returns: State of the application
705  */
706 ShellAppState
shell_app_get_state(ShellApp * app)707 shell_app_get_state (ShellApp *app)
708 {
709   return app->state;
710 }
711 
712 typedef struct {
713   ShellApp *app;
714   MetaWorkspace *active_workspace;
715 } CompareWindowsData;
716 
717 static int
shell_app_compare_windows(gconstpointer a,gconstpointer b,gpointer datap)718 shell_app_compare_windows (gconstpointer   a,
719                            gconstpointer   b,
720                            gpointer        datap)
721 {
722   MetaWindow *win_a = (gpointer)a;
723   MetaWindow *win_b = (gpointer)b;
724   CompareWindowsData *data = datap;
725   gboolean ws_a, ws_b;
726   gboolean vis_a, vis_b;
727 
728   ws_a = meta_window_get_workspace (win_a) == data->active_workspace;
729   ws_b = meta_window_get_workspace (win_b) == data->active_workspace;
730 
731   if (ws_a && !ws_b)
732     return -1;
733   else if (!ws_a && ws_b)
734     return 1;
735 
736   vis_a = meta_window_showing_on_its_workspace (win_a);
737   vis_b = meta_window_showing_on_its_workspace (win_b);
738 
739   if (vis_a && !vis_b)
740     return -1;
741   else if (!vis_a && vis_b)
742     return 1;
743 
744   return meta_window_get_user_time (win_b) - meta_window_get_user_time (win_a);
745 }
746 
747 /**
748  * shell_app_get_windows:
749  * @app:
750  *
751  * Get the windows which are associated with this application. The
752  * returned list will be sorted first by whether they're on the
753  * active workspace, then by whether they're visible, and finally
754  * by the time the user last interacted with them.
755  *
756  * Returns: (transfer none) (element-type MetaWindow): List of windows
757  */
758 GSList *
shell_app_get_windows(ShellApp * app)759 shell_app_get_windows (ShellApp *app)
760 {
761   if (app->running_state == NULL)
762     return NULL;
763 
764   if (app->running_state->window_sort_stale)
765     {
766       CompareWindowsData data;
767       data.app = app;
768       data.active_workspace = get_active_workspace ();
769       app->running_state->windows = g_slist_sort_with_data (app->running_state->windows, shell_app_compare_windows, &data);
770       app->running_state->window_sort_stale = FALSE;
771     }
772 
773   return app->running_state->windows;
774 }
775 
776 guint
shell_app_get_n_windows(ShellApp * app)777 shell_app_get_n_windows (ShellApp *app)
778 {
779   if (app->running_state == NULL)
780     return 0;
781   return g_slist_length (app->running_state->windows);
782 }
783 
784 gboolean
shell_app_is_on_workspace(ShellApp * app,MetaWorkspace * workspace)785 shell_app_is_on_workspace (ShellApp *app,
786                            MetaWorkspace   *workspace)
787 {
788   GSList *iter;
789 
790   if (shell_app_get_state (app) == SHELL_APP_STATE_STARTING)
791     {
792       if (app->started_on_workspace == -1 ||
793           meta_workspace_index (workspace) == app->started_on_workspace)
794         return TRUE;
795       else
796         return FALSE;
797     }
798 
799   if (app->running_state == NULL)
800     return FALSE;
801 
802   for (iter = app->running_state->windows; iter; iter = iter->next)
803     {
804       if (meta_window_get_workspace (iter->data) == workspace)
805         return TRUE;
806     }
807 
808   return FALSE;
809 }
810 
811 static int
shell_app_get_last_user_time(ShellApp * app)812 shell_app_get_last_user_time (ShellApp *app)
813 {
814   GSList *iter;
815   guint32 last_user_time;
816 
817   last_user_time = 0;
818 
819   if (app->running_state != NULL)
820     {
821       for (iter = app->running_state->windows; iter; iter = iter->next)
822         last_user_time = MAX (last_user_time, meta_window_get_user_time (iter->data));
823     }
824 
825   return (int)last_user_time;
826 }
827 
828 static gboolean
shell_app_is_minimized(ShellApp * app)829 shell_app_is_minimized (ShellApp *app)
830 {
831   GSList *iter;
832 
833   if (app->running_state == NULL)
834     return FALSE;
835 
836   for (iter = app->running_state->windows; iter; iter = iter->next)
837     {
838       if (meta_window_showing_on_its_workspace (iter->data))
839         return FALSE;
840     }
841 
842   return TRUE;
843 }
844 
845 /**
846  * shell_app_compare:
847  * @app:
848  * @other: A #ShellApp
849  *
850  * Compare one #ShellApp instance to another, in the following way:
851  *   - Running applications sort before not-running applications.
852  *   - If one of them has non-minimized windows and the other does not,
853  *     the one with visible windows is first.
854  *   - Finally, the application which the user interacted with most recently
855  *     compares earlier.
856  */
857 int
shell_app_compare(ShellApp * app,ShellApp * other)858 shell_app_compare (ShellApp *app,
859                    ShellApp *other)
860 {
861   gboolean min_app, min_other;
862 
863   if (app->state != other->state)
864     {
865       if (app->state == SHELL_APP_STATE_RUNNING)
866         return -1;
867       return 1;
868     }
869 
870   min_app = shell_app_is_minimized (app);
871   min_other = shell_app_is_minimized (other);
872 
873   if (min_app != min_other)
874     {
875       if (min_other)
876         return -1;
877       return 1;
878     }
879 
880   if (app->state == SHELL_APP_STATE_RUNNING)
881     {
882       if (app->running_state->windows && !other->running_state->windows)
883         return -1;
884       else if (!app->running_state->windows && other->running_state->windows)
885         return 1;
886 
887       return shell_app_get_last_user_time (other) - shell_app_get_last_user_time (app);
888     }
889 
890   return 0;
891 }
892 
893 ShellApp *
_shell_app_new_for_window(MetaWindow * window)894 _shell_app_new_for_window (MetaWindow      *window)
895 {
896   ShellApp *app;
897 
898   app = g_object_new (SHELL_TYPE_APP, NULL);
899 
900   app->window_id_string = g_strdup_printf ("window:%d", meta_window_get_stable_sequence (window));
901 
902   _shell_app_add_window (app, window);
903 
904   return app;
905 }
906 
907 ShellApp *
_shell_app_new(GDesktopAppInfo * info)908 _shell_app_new (GDesktopAppInfo *info)
909 {
910   ShellApp *app;
911 
912   app = g_object_new (SHELL_TYPE_APP,
913                       "app-info", info,
914                       NULL);
915 
916   return app;
917 }
918 
919 void
_shell_app_set_app_info(ShellApp * app,GDesktopAppInfo * info)920 _shell_app_set_app_info (ShellApp        *app,
921                          GDesktopAppInfo *info)
922 {
923   g_set_object (&app->info, info);
924 
925   g_clear_pointer (&app->name_collation_key, g_free);
926   if (app->info)
927     app->name_collation_key = g_utf8_collate_key (shell_app_get_name (app), -1);
928 }
929 
930 static void
shell_app_state_transition(ShellApp * app,ShellAppState state)931 shell_app_state_transition (ShellApp      *app,
932                             ShellAppState  state)
933 {
934   if (app->state == state)
935     return;
936   g_return_if_fail (!(app->state == SHELL_APP_STATE_RUNNING &&
937                       state == SHELL_APP_STATE_STARTING));
938   app->state = state;
939 
940   _shell_app_system_notify_app_state_changed (shell_app_system_get_default (), app);
941 
942   g_object_notify (G_OBJECT (app), "state");
943 }
944 
945 static void
shell_app_on_unmanaged(MetaWindow * window,ShellApp * app)946 shell_app_on_unmanaged (MetaWindow      *window,
947                         ShellApp *app)
948 {
949   _shell_app_remove_window (app, window);
950 }
951 
952 static void
shell_app_on_user_time_changed(MetaWindow * window,GParamSpec * pspec,ShellApp * app)953 shell_app_on_user_time_changed (MetaWindow *window,
954                                 GParamSpec *pspec,
955                                 ShellApp   *app)
956 {
957   g_assert (app->running_state != NULL);
958 
959   /* Ideally we don't want to emit windows-changed if the sort order
960    * isn't actually changing. This check catches most of those.
961    */
962   if (window != app->running_state->windows->data)
963     {
964       app->running_state->window_sort_stale = TRUE;
965       g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
966     }
967 }
968 
969 static void
shell_app_sync_running_state(ShellApp * app)970 shell_app_sync_running_state (ShellApp *app)
971 {
972   g_return_if_fail (app->running_state != NULL);
973 
974   if (app->state != SHELL_APP_STATE_STARTING)
975     {
976       if (app->running_state->interesting_windows == 0)
977         shell_app_state_transition (app, SHELL_APP_STATE_STOPPED);
978       else
979         shell_app_state_transition (app, SHELL_APP_STATE_RUNNING);
980     }
981 }
982 
983 
984 static void
shell_app_on_skip_taskbar_changed(MetaWindow * window,GParamSpec * pspec,ShellApp * app)985 shell_app_on_skip_taskbar_changed (MetaWindow *window,
986                                    GParamSpec *pspec,
987                                    ShellApp   *app)
988 {
989   g_assert (app->running_state != NULL);
990 
991   /* we rely on MetaWindow:skip-taskbar only being notified
992    * when it actually changes; when that assumption breaks,
993    * we'll have to track the "interesting" windows themselves
994    */
995   if (meta_window_is_skip_taskbar (window))
996     app->running_state->interesting_windows--;
997   else
998     app->running_state->interesting_windows++;
999 
1000   shell_app_sync_running_state (app);
1001 }
1002 
1003 static void
shell_app_on_ws_switch(MetaWorkspaceManager * workspace_manager,int from,int to,MetaMotionDirection direction,gpointer data)1004 shell_app_on_ws_switch (MetaWorkspaceManager *workspace_manager,
1005                         int                   from,
1006                         int                   to,
1007                         MetaMotionDirection   direction,
1008                         gpointer              data)
1009 {
1010   ShellApp *app = SHELL_APP (data);
1011 
1012   g_assert (app->running_state != NULL);
1013 
1014   app->running_state->window_sort_stale = TRUE;
1015 
1016   g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
1017 }
1018 
1019 gboolean
shell_app_get_busy(ShellApp * app)1020 shell_app_get_busy (ShellApp *app)
1021 {
1022   if (app->running_state != NULL &&
1023       app->running_state->application_proxy != NULL &&
1024       shell_org_gtk_application_get_busy (app->running_state->application_proxy))
1025     return TRUE;
1026 
1027   return FALSE;
1028 }
1029 
1030 static void
busy_changed_cb(GObject * object,GParamSpec * pspec,gpointer user_data)1031 busy_changed_cb (GObject    *object,
1032                  GParamSpec *pspec,
1033                  gpointer    user_data)
1034 {
1035   ShellApp *app = user_data;
1036 
1037   g_assert (SHELL_IS_APP (app));
1038 
1039   g_object_notify (G_OBJECT (app), "busy");
1040 }
1041 
1042 static void
get_application_proxy(GObject * source,GAsyncResult * result,gpointer user_data)1043 get_application_proxy (GObject      *source,
1044                        GAsyncResult *result,
1045                        gpointer      user_data)
1046 {
1047   ShellApp *app = user_data;
1048   ShellOrgGtkApplication *proxy;
1049   g_autoptr (GError) error = NULL;
1050 
1051   g_assert (SHELL_IS_APP (app));
1052 
1053   proxy = shell_org_gtk_application_proxy_new_finish (result, &error);
1054   if (proxy != NULL)
1055     {
1056       app->running_state->application_proxy = proxy;
1057       g_signal_connect (proxy,
1058                         "notify::busy",
1059                         G_CALLBACK (busy_changed_cb),
1060                         app);
1061       if (shell_org_gtk_application_get_busy (proxy))
1062         g_object_notify (G_OBJECT (app), "busy");
1063     }
1064 
1065   if (app->running_state != NULL &&
1066       !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1067     g_clear_object (&app->running_state->cancellable);
1068 
1069   g_object_unref (app);
1070 }
1071 
1072 static void
shell_app_ensure_busy_watch(ShellApp * app)1073 shell_app_ensure_busy_watch (ShellApp *app)
1074 {
1075   ShellAppRunningState *running_state = app->running_state;
1076   MetaWindow *window;
1077   const gchar *object_path;
1078 
1079   if (running_state->application_proxy != NULL ||
1080       running_state->cancellable != NULL)
1081     return;
1082 
1083   if (running_state->unique_bus_name == NULL)
1084     return;
1085 
1086   window = g_slist_nth_data (running_state->windows, 0);
1087   object_path = meta_window_get_gtk_application_object_path (window);
1088 
1089   if (object_path == NULL)
1090     return;
1091 
1092   running_state->cancellable = g_cancellable_new();
1093   /* Take a reference to app to make sure it isn't finalized before
1094      get_application_proxy runs */
1095   shell_org_gtk_application_proxy_new (running_state->session,
1096                                        G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
1097                                        running_state->unique_bus_name,
1098                                        object_path,
1099                                        running_state->cancellable,
1100                                        get_application_proxy,
1101                                        g_object_ref (app));
1102 }
1103 
1104 void
_shell_app_add_window(ShellApp * app,MetaWindow * window)1105 _shell_app_add_window (ShellApp        *app,
1106                        MetaWindow      *window)
1107 {
1108   if (app->running_state && g_slist_find (app->running_state->windows, window))
1109     return;
1110 
1111   g_object_freeze_notify (G_OBJECT (app));
1112 
1113   if (!app->running_state)
1114       create_running_state (app);
1115 
1116   app->running_state->window_sort_stale = TRUE;
1117   app->running_state->windows = g_slist_prepend (app->running_state->windows, g_object_ref (window));
1118   g_signal_connect_object (window, "unmanaged", G_CALLBACK(shell_app_on_unmanaged), app, 0);
1119   g_signal_connect_object (window, "notify::user-time", G_CALLBACK(shell_app_on_user_time_changed), app, 0);
1120   g_signal_connect_object (window, "notify::skip-taskbar", G_CALLBACK(shell_app_on_skip_taskbar_changed), app, 0);
1121 
1122   shell_app_update_app_actions (app, window);
1123   shell_app_ensure_busy_watch (app);
1124 
1125   if (!meta_window_is_skip_taskbar (window))
1126     app->running_state->interesting_windows++;
1127   shell_app_sync_running_state (app);
1128 
1129   if (app->started_on_workspace >= 0 && !meta_window_is_on_all_workspaces (window))
1130     meta_window_change_workspace_by_index (window, app->started_on_workspace, FALSE);
1131   app->started_on_workspace = -1;
1132 
1133   g_object_thaw_notify (G_OBJECT (app));
1134 
1135   g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
1136 }
1137 
1138 void
_shell_app_remove_window(ShellApp * app,MetaWindow * window)1139 _shell_app_remove_window (ShellApp   *app,
1140                           MetaWindow *window)
1141 {
1142   g_assert (app->running_state != NULL);
1143 
1144   if (!g_slist_find (app->running_state->windows, window))
1145     return;
1146 
1147   g_signal_handlers_disconnect_by_func (window, G_CALLBACK(shell_app_on_unmanaged), app);
1148   g_signal_handlers_disconnect_by_func (window, G_CALLBACK(shell_app_on_user_time_changed), app);
1149   g_signal_handlers_disconnect_by_func (window, G_CALLBACK(shell_app_on_skip_taskbar_changed), app);
1150   app->running_state->windows = g_slist_remove (app->running_state->windows, window);
1151 
1152   g_clear_signal_handler (&app->running_state->icon_changed_id, window);
1153 
1154   if (!meta_window_is_skip_taskbar (window))
1155     app->running_state->interesting_windows--;
1156   shell_app_sync_running_state (app);
1157 
1158   g_object_unref (window);
1159 
1160   if (app->running_state->windows == NULL)
1161     g_clear_pointer (&app->running_state, unref_running_state);
1162 
1163   g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
1164 }
1165 
1166 /**
1167  * shell_app_get_pids:
1168  * @app: a #ShellApp
1169  *
1170  * Returns: (transfer container) (element-type int): An unordered list of process identifiers associated with this application.
1171  */
1172 GSList *
shell_app_get_pids(ShellApp * app)1173 shell_app_get_pids (ShellApp *app)
1174 {
1175   GSList *result;
1176   GSList *iter;
1177 
1178   result = NULL;
1179   for (iter = shell_app_get_windows (app); iter; iter = iter->next)
1180     {
1181       MetaWindow *window = iter->data;
1182       pid_t pid = meta_window_get_pid (window);
1183 
1184       if (pid < 1)
1185         continue;
1186 
1187       /* Note in the (by far) common case, app will only have one pid, so
1188        * we'll hit the first element, so don't worry about O(N^2) here.
1189        */
1190       if (!g_slist_find (result, GINT_TO_POINTER (pid)))
1191         result = g_slist_prepend (result, GINT_TO_POINTER (pid));
1192     }
1193   return result;
1194 }
1195 
1196 void
_shell_app_handle_startup_sequence(ShellApp * app,MetaStartupSequence * sequence)1197 _shell_app_handle_startup_sequence (ShellApp            *app,
1198                                     MetaStartupSequence *sequence)
1199 {
1200   gboolean starting = !meta_startup_sequence_get_completed (sequence);
1201 
1202   /* The Shell design calls for on application launch, the app title
1203    * appears at top, and no X window is focused.  So when we get
1204    * a startup-notification for this app, transition it to STARTING
1205    * if it's currently stopped, set it as our application focus,
1206    * but focus the no_focus window.
1207    */
1208   if (starting && shell_app_get_state (app) == SHELL_APP_STATE_STOPPED)
1209     {
1210       MetaDisplay *display = shell_global_get_display (shell_global_get ());
1211 
1212       shell_app_state_transition (app, SHELL_APP_STATE_STARTING);
1213       meta_display_unset_input_focus (display,
1214                                       meta_startup_sequence_get_timestamp (sequence));
1215     }
1216 
1217   if (starting)
1218     app->started_on_workspace = meta_startup_sequence_get_workspace (sequence);
1219   else if (app->running_state && app->running_state->windows)
1220     shell_app_state_transition (app, SHELL_APP_STATE_RUNNING);
1221   else /* application have > 1 .desktop file */
1222     shell_app_state_transition (app, SHELL_APP_STATE_STOPPED);
1223 }
1224 
1225 /**
1226  * shell_app_request_quit:
1227  * @app: A #ShellApp
1228  *
1229  * Initiate an asynchronous request to quit this application.
1230  * The application may interact with the user, and the user
1231  * might cancel the quit request from the application UI.
1232  *
1233  * This operation may not be supported for all applications.
1234  *
1235  * Returns: %TRUE if a quit request is supported for this application
1236  */
1237 gboolean
shell_app_request_quit(ShellApp * app)1238 shell_app_request_quit (ShellApp   *app)
1239 {
1240   GActionGroup *group = NULL;
1241   GSList *iter;
1242 
1243   if (shell_app_get_state (app) != SHELL_APP_STATE_RUNNING)
1244     return FALSE;
1245 
1246   /* First, check whether the app exports an explicit "quit" action
1247    * that we can activate on the bus
1248    */
1249   group = G_ACTION_GROUP (app->running_state->muxer);
1250 
1251   if (g_action_group_has_action (group, "app.quit") &&
1252       g_action_group_get_action_parameter_type (group, "app.quit") == NULL)
1253     {
1254       g_action_group_activate_action (group, "app.quit", NULL);
1255 
1256       return TRUE;
1257     }
1258 
1259   /* Otherwise, fall back to closing all the app's windows */
1260   for (iter = app->running_state->windows; iter; iter = iter->next)
1261     {
1262       MetaWindow *win = iter->data;
1263 
1264       if (!meta_window_can_close (win))
1265         continue;
1266 
1267       meta_window_delete (win, shell_global_get_current_time (shell_global_get ()));
1268     }
1269   return TRUE;
1270 }
1271 
1272 #if !defined(HAVE_GIO_DESKTOP_LAUNCH_URIS_WITH_FDS) && defined(HAVE_SYSTEMD)
1273 /* This sets up the launched application to log to the journal
1274  * using its own identifier, instead of just "gnome-session".
1275  */
1276 static void
app_child_setup(gpointer user_data)1277 app_child_setup (gpointer user_data)
1278 {
1279   const char *appid = user_data;
1280   int res;
1281   int journalfd = sd_journal_stream_fd (appid, LOG_INFO, FALSE);
1282   if (journalfd >= 0)
1283     {
1284       do
1285         res = dup2 (journalfd, 1);
1286       while (G_UNLIKELY (res == -1 && errno == EINTR));
1287       do
1288         res = dup2 (journalfd, 2);
1289       while (G_UNLIKELY (res == -1 && errno == EINTR));
1290       (void) close (journalfd);
1291     }
1292 }
1293 #endif
1294 
1295 static void
wait_pid(GDesktopAppInfo * appinfo,GPid pid,gpointer user_data)1296 wait_pid (GDesktopAppInfo *appinfo,
1297           GPid             pid,
1298           gpointer         user_data)
1299 {
1300   g_child_watch_add (pid, (GChildWatchFunc) g_spawn_close_pid, NULL);
1301 }
1302 
1303 static void
apply_discrete_gpu_env(GAppLaunchContext * context,ShellGlobal * global)1304 apply_discrete_gpu_env (GAppLaunchContext *context,
1305                         ShellGlobal       *global)
1306 {
1307   GDBusProxy *proxy;
1308   GVariant* variant;
1309   guint num_children, i;
1310 
1311   proxy = shell_global_get_switcheroo_control (global);
1312   if (!proxy)
1313     {
1314       g_warning ("Could not apply discrete GPU environment, switcheroo-control not available");
1315       return;
1316     }
1317 
1318   variant = shell_net_hadess_switcheroo_control_get_gpus (SHELL_NET_HADESS_SWITCHEROO_CONTROL (proxy));
1319   if (!variant)
1320     {
1321       g_warning ("Could not apply discrete GPU environment, no GPUs in list");
1322       return;
1323     }
1324 
1325   num_children = g_variant_n_children (variant);
1326   for (i = 0; i < num_children; i++)
1327     {
1328       g_autoptr(GVariant) gpu = NULL;
1329       g_autoptr(GVariant) env = NULL;
1330       g_autoptr(GVariant) default_variant = NULL;
1331       g_autofree const char **env_s = NULL;
1332       guint j;
1333 
1334       gpu = g_variant_get_child_value (variant, i);
1335       if (!gpu ||
1336           !g_variant_is_of_type (gpu, G_VARIANT_TYPE ("a{s*}")))
1337         continue;
1338 
1339       /* Skip over the default GPU */
1340       default_variant = g_variant_lookup_value (gpu, "Default", NULL);
1341       if (!default_variant || g_variant_get_boolean (default_variant))
1342         continue;
1343 
1344       env = g_variant_lookup_value (gpu, "Environment", NULL);
1345       if (!env)
1346         continue;
1347 
1348       env_s = g_variant_get_strv (env, NULL);
1349       for (j = 0; env_s[j] != NULL; j = j + 2)
1350         g_app_launch_context_setenv (context, env_s[j], env_s[j+1]);
1351       return;
1352     }
1353 
1354   g_debug ("Could not find discrete GPU in switcheroo-control, not applying environment");
1355 }
1356 
1357 /**
1358  * shell_app_launch:
1359  * @timestamp: Event timestamp, or 0 for current event timestamp
1360  * @workspace: Start on this workspace, or -1 for default
1361  * @gpu_pref: the GPU to prefer launching on
1362  * @error: A #GError
1363  */
1364 gboolean
shell_app_launch(ShellApp * app,guint timestamp,int workspace,ShellAppLaunchGpu gpu_pref,GError ** error)1365 shell_app_launch (ShellApp           *app,
1366                   guint               timestamp,
1367                   int                 workspace,
1368                   ShellAppLaunchGpu   gpu_pref,
1369                   GError            **error)
1370 {
1371   ShellGlobal *global;
1372   GAppLaunchContext *context;
1373   gboolean ret;
1374   GSpawnFlags flags;
1375   gboolean discrete_gpu = FALSE;
1376 
1377   if (app->info == NULL)
1378     {
1379       MetaWindow *window = window_backed_app_get_window (app);
1380       /* We don't use an error return if there no longer any windows, because the
1381        * user attempting to activate a stale window backed app isn't something
1382        * we would expect the caller to meaningfully handle or display an error
1383        * message to the user.
1384        */
1385       if (window)
1386         meta_window_activate (window, timestamp);
1387       return TRUE;
1388     }
1389 
1390   global = shell_global_get ();
1391   context = shell_global_create_app_launch_context (global, timestamp, workspace);
1392   if (gpu_pref == SHELL_APP_LAUNCH_GPU_APP_PREF)
1393     discrete_gpu = g_desktop_app_info_get_boolean (app->info, "PrefersNonDefaultGPU");
1394   else
1395     discrete_gpu = (gpu_pref == SHELL_APP_LAUNCH_GPU_DISCRETE);
1396 
1397   if (discrete_gpu)
1398     apply_discrete_gpu_env (context, global);
1399 
1400   /* Set LEAVE_DESCRIPTORS_OPEN in order to use an optimized gspawn
1401    * codepath. The shell's open file descriptors should be marked CLOEXEC
1402    * so that they are automatically closed even with this flag set.
1403    */
1404   flags = G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD |
1405           G_SPAWN_LEAVE_DESCRIPTORS_OPEN;
1406 
1407 #ifdef HAVE_GIO_DESKTOP_LAUNCH_URIS_WITH_FDS
1408   /* Optimized spawn path, avoiding a child_setup function */
1409   {
1410     int journalfd = -1;
1411 
1412 #ifdef HAVE_SYSTEMD
1413     journalfd = sd_journal_stream_fd (shell_app_get_id (app), LOG_INFO, FALSE);
1414 #endif /* HAVE_SYSTEMD */
1415 
1416     ret = g_desktop_app_info_launch_uris_as_manager_with_fds (app->info, NULL,
1417                                                               context,
1418                                                               flags,
1419                                                               NULL, NULL,
1420                                                               wait_pid, NULL,
1421                                                               -1,
1422                                                               journalfd,
1423                                                               journalfd,
1424                                                               error);
1425 
1426     if (journalfd >= 0)
1427       (void) close (journalfd);
1428   }
1429 #else /* !HAVE_GIO_DESKTOP_LAUNCH_URIS_WITH_FDS */
1430   ret = g_desktop_app_info_launch_uris_as_manager (app->info, NULL,
1431                                                    context,
1432                                                    flags,
1433 #ifdef HAVE_SYSTEMD
1434                                                    app_child_setup, (gpointer)shell_app_get_id (app),
1435 #else
1436                                                    NULL, NULL,
1437 #endif
1438                                                    wait_pid, NULL,
1439                                                    error);
1440 #endif /* HAVE_GIO_DESKTOP_LAUNCH_URIS_WITH_FDS */
1441   g_object_unref (context);
1442 
1443   return ret;
1444 }
1445 
1446 /**
1447  * shell_app_launch_action:
1448  * @app: the #ShellApp
1449  * @action_name: the name of the action to launch (as obtained by
1450  *               g_desktop_app_info_list_actions())
1451  * @timestamp: Event timestamp, or 0 for current event timestamp
1452  * @workspace: Start on this workspace, or -1 for default
1453  */
1454 void
shell_app_launch_action(ShellApp * app,const char * action_name,guint timestamp,int workspace)1455 shell_app_launch_action (ShellApp        *app,
1456                          const char      *action_name,
1457                          guint            timestamp,
1458                          int              workspace)
1459 {
1460   ShellGlobal *global;
1461   GAppLaunchContext *context;
1462 
1463   global = shell_global_get ();
1464   context = shell_global_create_app_launch_context (global, timestamp, workspace);
1465 
1466   g_desktop_app_info_launch_action (G_DESKTOP_APP_INFO (app->info),
1467                                     action_name, context);
1468 
1469   g_object_unref (context);
1470 }
1471 
1472 /**
1473  * shell_app_get_app_info:
1474  * @app: a #ShellApp
1475  *
1476  * Returns: (transfer none): The #GDesktopAppInfo for this app, or %NULL if backed by a window
1477  */
1478 GDesktopAppInfo *
shell_app_get_app_info(ShellApp * app)1479 shell_app_get_app_info (ShellApp *app)
1480 {
1481   return app->info;
1482 }
1483 
1484 static void
create_running_state(ShellApp * app)1485 create_running_state (ShellApp *app)
1486 {
1487   MetaDisplay *display = shell_global_get_display (shell_global_get ());
1488   MetaWorkspaceManager *workspace_manager =
1489     meta_display_get_workspace_manager (display);
1490 
1491   g_assert (app->running_state == NULL);
1492 
1493   app->running_state = g_new0 (ShellAppRunningState, 1);
1494   app->running_state->refcount = 1;
1495   app->running_state->workspace_switch_id =
1496     g_signal_connect (workspace_manager, "workspace-switched",
1497                       G_CALLBACK (shell_app_on_ws_switch), app);
1498 
1499   app->running_state->session = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
1500   g_assert (app->running_state->session != NULL);
1501   app->running_state->muxer = gtk_action_muxer_new ();
1502 }
1503 
1504 void
shell_app_update_app_actions(ShellApp * app,MetaWindow * window)1505 shell_app_update_app_actions (ShellApp   *app,
1506                               MetaWindow *window)
1507 {
1508   const gchar *unique_bus_name;
1509 
1510   /* We assume that 'gtk-application-object-path' and
1511    * 'gtk-app-menu-object-path' are the same for all windows which
1512    * have it set.
1513    *
1514    * It could be possible, however, that the first window we see
1515    * belonging to the app didn't have them set.  For this reason, we
1516    * take the values from the first window that has them set and ignore
1517    * all the rest (until the app is stopped and restarted).
1518    */
1519 
1520   unique_bus_name = meta_window_get_gtk_unique_bus_name (window);
1521 
1522   if (g_strcmp0 (app->running_state->unique_bus_name, unique_bus_name) != 0)
1523     {
1524       const gchar *application_object_path;
1525       GDBusActionGroup *actions;
1526 
1527       application_object_path = meta_window_get_gtk_application_object_path (window);
1528 
1529       if (application_object_path == NULL || unique_bus_name == NULL)
1530         return;
1531 
1532       g_clear_pointer (&app->running_state->unique_bus_name, g_free);
1533       app->running_state->unique_bus_name = g_strdup (unique_bus_name);
1534       actions = g_dbus_action_group_get (app->running_state->session, unique_bus_name, application_object_path);
1535       gtk_action_muxer_insert (app->running_state->muxer, "app", G_ACTION_GROUP (actions));
1536       g_object_unref (actions);
1537     }
1538 }
1539 
1540 static void
unref_running_state(ShellAppRunningState * state)1541 unref_running_state (ShellAppRunningState *state)
1542 {
1543   MetaDisplay *display = shell_global_get_display (shell_global_get ());
1544   MetaWorkspaceManager *workspace_manager =
1545     meta_display_get_workspace_manager (display);
1546 
1547   g_assert (state->refcount > 0);
1548 
1549   state->refcount--;
1550   if (state->refcount > 0)
1551     return;
1552 
1553   g_clear_signal_handler (&state->workspace_switch_id, workspace_manager);
1554 
1555   g_clear_object (&state->application_proxy);
1556 
1557   if (state->cancellable != NULL)
1558     {
1559       g_cancellable_cancel (state->cancellable);
1560       g_clear_object (&state->cancellable);
1561     }
1562 
1563   g_clear_object (&state->muxer);
1564   g_clear_object (&state->session);
1565   g_clear_pointer (&state->unique_bus_name, g_free);
1566 
1567   g_free (state);
1568 }
1569 
1570 /**
1571  * shell_app_compare_by_name:
1572  * @app: One app
1573  * @other: The other app
1574  *
1575  * Order two applications by name.
1576  *
1577  * Returns: -1, 0, or 1; suitable for use as a comparison function
1578  * for e.g. g_slist_sort()
1579  */
1580 int
shell_app_compare_by_name(ShellApp * app,ShellApp * other)1581 shell_app_compare_by_name (ShellApp *app, ShellApp *other)
1582 {
1583   return strcmp (app->name_collation_key, other->name_collation_key);
1584 }
1585 
1586 static void
shell_app_init(ShellApp * self)1587 shell_app_init (ShellApp *self)
1588 {
1589   self->state = SHELL_APP_STATE_STOPPED;
1590   self->started_on_workspace = -1;
1591 }
1592 
1593 static void
shell_app_dispose(GObject * object)1594 shell_app_dispose (GObject *object)
1595 {
1596   ShellApp *app = SHELL_APP (object);
1597 
1598   g_clear_object (&app->info);
1599   g_clear_object (&app->fallback_icon);
1600 
1601   while (app->running_state)
1602     _shell_app_remove_window (app, app->running_state->windows->data);
1603 
1604   /* We should have been transitioned when we removed all of our windows */
1605   g_assert (app->state == SHELL_APP_STATE_STOPPED);
1606   g_assert (app->running_state == NULL);
1607 
1608   G_OBJECT_CLASS(shell_app_parent_class)->dispose (object);
1609 }
1610 
1611 static void
shell_app_finalize(GObject * object)1612 shell_app_finalize (GObject *object)
1613 {
1614   ShellApp *app = SHELL_APP (object);
1615 
1616   g_free (app->window_id_string);
1617 
1618   g_free (app->name_collation_key);
1619 
1620   G_OBJECT_CLASS(shell_app_parent_class)->finalize (object);
1621 }
1622 
1623 static void
shell_app_class_init(ShellAppClass * klass)1624 shell_app_class_init(ShellAppClass *klass)
1625 {
1626   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1627 
1628   gobject_class->get_property = shell_app_get_property;
1629   gobject_class->set_property = shell_app_set_property;
1630   gobject_class->dispose = shell_app_dispose;
1631   gobject_class->finalize = shell_app_finalize;
1632 
1633   shell_app_signals[WINDOWS_CHANGED] = g_signal_new ("windows-changed",
1634                                      SHELL_TYPE_APP,
1635                                      G_SIGNAL_RUN_LAST,
1636                                      0,
1637                                      NULL, NULL, NULL,
1638                                      G_TYPE_NONE, 0);
1639 
1640   /**
1641    * ShellApp:state:
1642    *
1643    * The high-level state of the application, effectively whether it's
1644    * running or not, or transitioning between those states.
1645    */
1646   g_object_class_install_property (gobject_class,
1647                                    PROP_STATE,
1648                                    g_param_spec_enum ("state",
1649                                                       "State",
1650                                                       "Application state",
1651                                                       SHELL_TYPE_APP_STATE,
1652                                                       SHELL_APP_STATE_STOPPED,
1653                                                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1654 
1655   /**
1656    * ShellApp:busy:
1657    *
1658    * Whether the application has marked itself as busy.
1659    */
1660   g_object_class_install_property (gobject_class,
1661                                    PROP_BUSY,
1662                                    g_param_spec_boolean ("busy",
1663                                                          "Busy",
1664                                                          "Busy state",
1665                                                          FALSE,
1666                                                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1667 
1668   /**
1669    * ShellApp:id:
1670    *
1671    * The id of this application (a desktop filename, or a special string
1672    * like window:0xabcd1234)
1673    */
1674   g_object_class_install_property (gobject_class,
1675                                    PROP_ID,
1676                                    g_param_spec_string ("id",
1677                                                         "Application id",
1678                                                         "The desktop file id of this ShellApp",
1679                                                         NULL,
1680                                                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1681 
1682   /**
1683    * ShellApp:icon:
1684    *
1685    * The #GIcon representing this ShellApp
1686    */
1687   g_object_class_install_property (gobject_class,
1688                                    PROP_ICON,
1689                                    g_param_spec_object ("icon",
1690                                                         "GIcon",
1691                                                         "The GIcon representing this app",
1692                                                         G_TYPE_ICON,
1693                                                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1694 
1695   /**
1696    * ShellApp:action-group:
1697    *
1698    * The #GDBusActionGroup associated with this ShellApp, if any. See the
1699    * documentation of #GApplication and #GActionGroup for details.
1700    */
1701   g_object_class_install_property (gobject_class,
1702                                    PROP_ACTION_GROUP,
1703                                    g_param_spec_object ("action-group",
1704                                                         "Application Action Group",
1705                                                         "The action group exported by the remote application",
1706                                                         G_TYPE_ACTION_GROUP,
1707                                                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1708   /**
1709    * ShellApp:app-info:
1710    *
1711    * The #GDesktopAppInfo associated with this ShellApp, if any.
1712    */
1713   g_object_class_install_property (gobject_class,
1714                                    PROP_APP_INFO,
1715                                    g_param_spec_object ("app-info",
1716                                                         "DesktopAppInfo",
1717                                                         "The DesktopAppInfo associated with this app",
1718                                                         G_TYPE_DESKTOP_APP_INFO,
1719                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1720 
1721 }
1722