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