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
11 #include "shell-app-private.h"
12 #include "shell-enum-types.h"
13 #include "shell-global.h"
14 #include "shell-util.h"
15 #include "shell-app-system-private.h"
16 #include "shell-window-tracker-private.h"
17 #include "st.h"
18 #include "gactionmuxer.h"
19
20 typedef enum {
21 MATCH_NONE,
22 MATCH_SUBSTRING, /* Not prefix, substring */
23 MATCH_PREFIX, /* Strict prefix */
24 } ShellAppSearchMatch;
25
26 /* This is mainly a memory usage optimization - the user is going to
27 * be running far fewer of the applications at one time than they have
28 * installed. But it also just helps keep the code more logically
29 * separated.
30 */
31 typedef struct {
32 guint refcount;
33
34 /* Signal connection to dirty window sort list on workspace changes */
35 guint workspace_switch_id;
36
37 GSList *windows;
38
39 /* Whether or not we need to resort the windows; this is done on demand */
40 gboolean window_sort_stale : 1;
41
42 /* See GApplication documentation */
43 GDBusMenuModel *remote_menu;
44 GActionMuxer *muxer;
45 char * unique_bus_name;
46 } ShellAppRunningState;
47
48 /**
49 * SECTION:shell-app
50 * @short_description: Object representing an application
51 *
52 * This object wraps a #GMenuTreeEntry, providing methods and signals
53 * primarily useful for running applications.
54 */
55 struct _ShellApp
56 {
57 GObject parent;
58
59 int started_on_workspace;
60
61 ShellAppState state;
62
63 GMenuTreeEntry *entry; /* If NULL, this app is backed by one or more
64 * MetaWindow. For purposes of app title
65 * etc., we use the first window added,
66 * because it's most likely to be what we
67 * want (e.g. it will be of TYPE_NORMAL from
68 * the way shell-window-tracker.c works).
69 */
70
71 ShellAppRunningState *running_state;
72
73 char *window_id_string;
74
75 char *casefolded_name;
76 char *casefolded_generic_name;
77 char *name_collation_key;
78 char *casefolded_exec;
79 char **casefolded_keywords;
80 };
81
82 enum {
83 PROP_0,
84 PROP_STATE,
85 PROP_ID,
86 PROP_DBUS_ID,
87 PROP_ACTION_GROUP,
88 PROP_MENU
89 };
90
91 enum {
92 WINDOWS_CHANGED,
93 LAST_SIGNAL
94 };
95
96 static guint shell_app_signals[LAST_SIGNAL] = { 0 };
97
98 static void create_running_state (ShellApp *app);
99 static void unref_running_state (ShellAppRunningState *state);
100
101 G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT)
102
103 static void
104 shell_app_get_property (GObject *gobject,
105 guint prop_id,
106 GValue *value,
107 GParamSpec *pspec)
108 {
109 ShellApp *app = SHELL_APP (gobject);
110
111 switch (prop_id)
112 {
113 case PROP_STATE:
114 g_value_set_enum (value, app->state);
115 break;
116 case PROP_ID:
117 g_value_set_string (value, shell_app_get_id (app));
118 break;
119 case PROP_ACTION_GROUP:
120 if (app->running_state)
121 g_value_set_object (value, app->running_state->muxer);
122 break;
123 case PROP_MENU:
124 if (app->running_state)
125 g_value_set_object (value, app->running_state->remote_menu);
126 break;
127 default:
128 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
129 break;
130 }
131 }
132
133 const char *
134 shell_app_get_id (ShellApp *app)
135 {
136 if (app->entry)
137 return gmenu_tree_entry_get_desktop_file_id (app->entry);
138 return app->window_id_string;
139 }
140
141 static MetaWindow *
142 window_backed_app_get_window (ShellApp *app)
143 {
144 g_assert (app->entry == NULL);
145 g_assert (app->running_state);
146 g_assert (app->running_state->windows);
147 return app->running_state->windows->data;
148 }
149
150 static ClutterActor *
151 window_backed_app_get_icon (ShellApp *app,
152 int size)
153 {
154 MetaWindow *window;
155 ClutterActor *actor;
156
157 /* During a state transition from running to not-running for
158 * window-backend apps, it's possible we get a request for the icon.
159 * Avoid asserting here and just return an empty image.
160 */
161 if (app->running_state == NULL)
162 {
163 actor = clutter_texture_new ();
'clutter_texture_new' is deprecated (declared at /usr/include/clutter-1.0/clutter/deprecated/clutter-texture.h:36): Use 'clutter_image_new' instead
(emitted by gcc)
164 g_object_set (actor, "opacity", 0, "width", (float) size, "height", (float) size, NULL);
165 return actor;
166 }
167
168 window = window_backed_app_get_window (app);
169 actor = st_texture_cache_bind_pixbuf_property (st_texture_cache_get_default (),
170 G_OBJECT (window),
171 "icon");
172 g_object_set (actor, "width", (float) size, "height", (float) size, NULL);
173 return actor;
174 }
175
176 /**
177 * shell_app_create_icon_texture:
178 *
179 * Look up the icon for this application, and create a #ClutterTexture
180 * for it at the given size.
181 *
182 * Return value: (transfer none): A floating #ClutterActor
183 */
184 ClutterActor *
185 shell_app_create_icon_texture (ShellApp *app,
186 int size)
187 {
188 GIcon *icon;
189 ClutterActor *ret;
190
191 ret = NULL;
192
193 if (app->entry == NULL)
194 return window_backed_app_get_icon (app, size);
195
196 icon = g_app_info_get_icon (G_APP_INFO (gmenu_tree_entry_get_app_info (app->entry)));
197 if (icon != NULL)
198 ret = st_texture_cache_load_gicon (st_texture_cache_get_default (), NULL, icon, size);
199
200 if (ret == NULL)
201 {
202 icon = g_themed_icon_new ("application-x-executable");
203 ret = st_texture_cache_load_gicon (st_texture_cache_get_default (), NULL, icon, size);
204 g_object_unref (icon);
205 }
206
207 return ret;
208 }
209
210 typedef struct {
211 ShellApp *app;
212 int size;
213 } CreateFadedIconData;
214
215 static CoglHandle
216 shell_app_create_faded_icon_cpu (StTextureCache *cache,
217 const char *key,
218 void *datap,
219 GError **error)
220 {
221 CreateFadedIconData *data = datap;
222 ShellApp *app;
223 GdkPixbuf *pixbuf;
224 int size;
225 CoglHandle texture;
226 gint width, height, rowstride;
227 guint8 n_channels;
228 gboolean have_alpha;
229 gint fade_start;
230 gint fade_range;
231 guint i, j;
232 guint pixbuf_byte_size;
233 guint8 *orig_pixels;
234 guint8 *pixels;
235 GIcon *icon;
236 GtkIconInfo *info;
237
238 app = data->app;
239 size = data->size;
240
241 info = NULL;
242
243 icon = g_app_info_get_icon (G_APP_INFO (gmenu_tree_entry_get_app_info (app->entry)));
244 if (icon != NULL)
245 {
246 info = gtk_icon_theme_lookup_by_gicon (gtk_icon_theme_get_default (),
247 icon, size,
248 GTK_ICON_LOOKUP_FORCE_SIZE);
249 }
250
251 if (info == NULL)
252 {
253 icon = g_themed_icon_new ("application-x-executable");
254 info = gtk_icon_theme_lookup_by_gicon (gtk_icon_theme_get_default (),
255 icon, size,
256 GTK_ICON_LOOKUP_FORCE_SIZE);
257 g_object_unref (icon);
258 }
259
260 if (info == NULL)
261 return COGL_INVALID_HANDLE;
262
263 pixbuf = gtk_icon_info_load_icon (info, NULL);
264 gtk_icon_info_free (info);
265
266 if (pixbuf == NULL)
267 return COGL_INVALID_HANDLE;
268
269 width = gdk_pixbuf_get_width (pixbuf);
270 height = gdk_pixbuf_get_height (pixbuf);
271 rowstride = gdk_pixbuf_get_rowstride (pixbuf);
272 n_channels = gdk_pixbuf_get_n_channels (pixbuf);
273 orig_pixels = gdk_pixbuf_get_pixels (pixbuf);
274 have_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
275
276 pixbuf_byte_size = (height - 1) * rowstride +
277 + width * ((n_channels * gdk_pixbuf_get_bits_per_sample (pixbuf) + 7) / 8);
278
279 pixels = g_malloc0 (rowstride * height);
280 memcpy (pixels, orig_pixels, pixbuf_byte_size);
281
282 fade_start = width / 2;
283 fade_range = width - fade_start;
284 for (i = fade_start; i < width; i++)
285 {
286 for (j = 0; j < height; j++)
287 {
288 guchar *pixel = &pixels[j * rowstride + i * n_channels];
289 float fade = 1.0 - ((float) i - fade_start) / fade_range;
290 pixel[0] = 0.5 + pixel[0] * fade;
291 pixel[1] = 0.5 + pixel[1] * fade;
292 pixel[2] = 0.5 + pixel[2] * fade;
293 if (have_alpha)
294 pixel[3] = 0.5 + pixel[3] * fade;
295 }
296 }
297
298 texture = cogl_texture_new_from_data (width,
299 height,
300 COGL_TEXTURE_NONE,
301 have_alpha ? COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888,
302 COGL_PIXEL_FORMAT_ANY,
303 rowstride,
304 pixels);
305 g_free (pixels);
306 g_object_unref (pixbuf);
307
308 return texture;
309 }
310
311 /**
312 * shell_app_get_faded_icon:
313 * @app: A #ShellApp
314 * @size: Size in pixels
315 *
316 * Return an actor with a horizontally faded look.
317 *
318 * Return value: (transfer none): A floating #ClutterActor, or %NULL if no icon
319 */
320 ClutterActor *
321 shell_app_get_faded_icon (ShellApp *app, int size)
322 {
323 CoglHandle texture;
324 ClutterActor *result;
325 char *cache_key;
326 CreateFadedIconData data;
327
328 /* Don't fade for window backed apps for now...easier to reuse the
329 * property tracking bits, and this helps us visually distinguish
330 * app-tracked from not.
331 */
332 if (!app->entry)
333 return window_backed_app_get_icon (app, size);
334
335 /* Use icon: prefix so that we get evicted from the cache on
336 * icon theme changes. */
337 cache_key = g_strdup_printf ("icon:%s,size=%d,faded", shell_app_get_id (app), size);
338 data.app = app;
339 data.size = size;
340 texture = st_texture_cache_load (st_texture_cache_get_default (),
341 cache_key,
342 ST_TEXTURE_CACHE_POLICY_FOREVER,
343 shell_app_create_faded_icon_cpu,
344 &data,
345 NULL);
346 g_free (cache_key);
347
348 if (texture != COGL_INVALID_HANDLE)
349 {
350 result = clutter_texture_new ();
'clutter_texture_new' is deprecated (declared at /usr/include/clutter-1.0/clutter/deprecated/clutter-texture.h:36): Use 'clutter_image_new' instead
(emitted by gcc)
351 clutter_texture_set_cogl_texture (CLUTTER_TEXTURE (result), texture);
'clutter_texture_set_cogl_texture' is deprecated (declared at /usr/include/clutter-1.0/clutter/deprecated/clutter-texture.h:80)
(emitted by gcc)
352 }
353 else
354 {
355 result = clutter_texture_new ();
'clutter_texture_new' is deprecated (declared at /usr/include/clutter-1.0/clutter/deprecated/clutter-texture.h:36): Use 'clutter_image_new' instead
(emitted by gcc)
356 g_object_set (result, "opacity", 0, "width", (float) size, "height", (float) size, NULL);
357
358 }
359 return result;
360 }
361
362 const char *
363 shell_app_get_name (ShellApp *app)
364 {
365 if (app->entry)
366 return g_app_info_get_name (G_APP_INFO (gmenu_tree_entry_get_app_info (app->entry)));
367 else
368 {
369 MetaWindow *window = window_backed_app_get_window (app);
370 const char *name;
371
372 name = meta_window_get_wm_class (window);
373 if (!name)
374 name = C_("program", "Unknown");
375 return name;
376 }
377 }
378
379 const char *
380 shell_app_get_description (ShellApp *app)
381 {
382 if (app->entry)
383 return g_app_info_get_description (G_APP_INFO (gmenu_tree_entry_get_app_info (app->entry)));
384 else
385 return NULL;
386 }
387
388 /**
389 * shell_app_is_window_backed:
390 *
391 * A window backed application is one which represents just an open
392 * window, i.e. there's no .desktop file assocation, so we don't know
393 * how to launch it again.
394 */
395 gboolean
396 shell_app_is_window_backed (ShellApp *app)
397 {
398 return app->entry == NULL;
399 }
400
401 typedef struct {
402 MetaWorkspace *workspace;
403 GSList **transients;
404 } CollectTransientsData;
405
406 static gboolean
407 collect_transients_on_workspace (MetaWindow *window,
408 gpointer datap)
409 {
410 CollectTransientsData *data = datap;
411
412 if (data->workspace && meta_window_get_workspace (window) != data->workspace)
413 return TRUE;
414
415 *data->transients = g_slist_prepend (*data->transients, window);
416 return TRUE;
417 }
418
419 /* The basic idea here is that when we're targeting a window,
420 * if it has transients we want to pick the most recent one
421 * the user interacted with.
422 * This function makes raising GEdit with the file chooser
423 * open work correctly.
424 */
425 static MetaWindow *
426 find_most_recent_transient_on_same_workspace (MetaDisplay *display,
427 MetaWindow *reference)
428 {
429 GSList *transients, *transients_sorted, *iter;
430 MetaWindow *result;
431 CollectTransientsData data;
432
433 transients = NULL;
434 data.workspace = meta_window_get_workspace (reference);
435 data.transients = &transients;
436
437 meta_window_foreach_transient (reference, collect_transients_on_workspace, &data);
438
439 transients_sorted = meta_display_sort_windows_by_stacking (display, transients);
440 /* Reverse this so we're top-to-bottom (yes, we should probably change the order
441 * returned from the sort_windows_by_stacking function)
442 */
443 transients_sorted = g_slist_reverse (transients_sorted);
444 g_slist_free (transients);
445 transients = NULL;
446
447 result = NULL;
448 for (iter = transients_sorted; iter; iter = iter->next)
449 {
450 MetaWindow *window = iter->data;
451 MetaWindowType wintype = meta_window_get_window_type (window);
452
453 /* Don't want to focus UTILITY types, like the Gimp toolbars */
454 if (wintype == META_WINDOW_NORMAL ||
455 wintype == META_WINDOW_DIALOG)
456 {
457 result = window;
458 break;
459 }
460 }
461 g_slist_free (transients_sorted);
462 return result;
463 }
464
465 /**
466 * shell_app_activate_window:
467 * @app: a #ShellApp
468 * @window: (allow-none): Window to be focused
469 * @timestamp: Event timestamp
470 *
471 * Bring all windows for the given app to the foreground,
472 * but ensure that @window is on top. If @window is %NULL,
473 * the window with the most recent user time for the app
474 * will be used.
475 *
476 * This function has no effect if @app is not currently running.
477 */
478 void
479 shell_app_activate_window (ShellApp *app,
480 MetaWindow *window,
481 guint32 timestamp)
482 {
483 GSList *windows;
484
485 if (shell_app_get_state (app) != SHELL_APP_STATE_RUNNING)
486 return;
487
488 windows = shell_app_get_windows (app);
489 if (window == NULL && windows)
490 window = windows->data;
491
492 if (!g_slist_find (windows, window))
493 return;
494 else
495 {
496 GSList *windows_reversed, *iter;
497 ShellGlobal *global = shell_global_get ();
498 MetaScreen *screen = shell_global_get_screen (global);
499 MetaDisplay *display = meta_screen_get_display (screen);
500 MetaWorkspace *active = meta_screen_get_active_workspace (screen);
501 MetaWorkspace *workspace = meta_window_get_workspace (window);
502 guint32 last_user_timestamp = meta_display_get_last_user_time (display);
503 MetaWindow *most_recent_transient;
504
505 if (meta_display_xserver_time_is_before (display, timestamp, last_user_timestamp))
506 {
507 meta_window_set_demands_attention (window);
508 return;
509 }
510
511 /* Now raise all the other windows for the app that are on
512 * the same workspace, in reverse order to preserve the stacking.
513 */
514 windows_reversed = g_slist_copy (windows);
515 windows_reversed = g_slist_reverse (windows_reversed);
516 for (iter = windows_reversed; iter; iter = iter->next)
517 {
518 MetaWindow *other_window = iter->data;
519
520 if (other_window != window)
521 meta_window_raise (other_window);
522 }
523 g_slist_free (windows_reversed);
524
525 /* If we have a transient that the user's interacted with more recently than
526 * the window, pick that.
527 */
528 most_recent_transient = find_most_recent_transient_on_same_workspace (display, window);
529 if (most_recent_transient
530 && meta_display_xserver_time_is_before (display,
531 meta_window_get_user_time (window),
532 meta_window_get_user_time (most_recent_transient)))
533 window = most_recent_transient;
534
535
536 if (active != workspace)
537 meta_workspace_activate_with_focus (workspace, window, timestamp);
538 else
539 meta_window_activate (window, timestamp);
540 }
541 }
542
543
544 void
545 shell_app_update_window_actions (ShellApp *app, MetaWindow *window)
546 {
547 const char *object_path;
548
549 object_path = meta_window_get_gtk_window_object_path (window);
550 if (object_path != NULL)
551 {
552 GActionGroup *actions;
553
554 actions = g_object_get_data (G_OBJECT (window), "actions");
555 if (actions == NULL)
556 {
557 actions = G_ACTION_GROUP (g_dbus_action_group_get (g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL),
558 meta_window_get_gtk_unique_bus_name (window),
559 object_path));
560 g_object_set_data_full (G_OBJECT (window), "actions", actions, g_object_unref);
561 }
562
563 if (!app->running_state->muxer)
564 app->running_state->muxer = g_action_muxer_new ();
565
566 g_action_muxer_insert (app->running_state->muxer, "win", actions);
567 g_object_notify (G_OBJECT (app), "action-group");
568 }
569 }
570
571 /**
572 * shell_app_activate:
573 * @app: a #ShellApp
574 *
575 * Like shell_app_activate_full(), but using the default workspace and
576 * event timestamp.
577 */
578 void
579 shell_app_activate (ShellApp *app)
580 {
581 return shell_app_activate_full (app, -1, 0);
582 }
583
584 /**
585 * shell_app_activate_full:
586 * @app: a #ShellApp
587 * @workspace: launch on this workspace, or -1 for default. Ignored if
588 * activating an existing window
589 * @timestamp: Event timestamp
590 *
591 * Perform an appropriate default action for operating on this application,
592 * dependent on its current state. For example, if the application is not
593 * currently running, launch it. If it is running, activate the most
594 * recently used NORMAL window (or if that window has a transient, the most
595 * recently used transient for that window).
596 */
597 void
598 shell_app_activate_full (ShellApp *app,
599 int workspace,
600 guint32 timestamp)
601 {
602 ShellGlobal *global;
603
604 global = shell_global_get ();
605
606 if (timestamp == 0)
607 timestamp = shell_global_get_current_time (global);
608
609 switch (app->state)
610 {
611 case SHELL_APP_STATE_STOPPED:
612 {
613 GError *error = NULL;
614 if (!shell_app_launch (app,
615 timestamp,
616 NULL,
617 workspace,
618 NULL,
619 &error))
620 {
621 char *msg;
622 msg = g_strdup_printf (_("Failed to launch '%s'"), shell_app_get_name (app));
623 shell_global_notify_error (global,
624 msg,
625 error->message);
626 g_free (msg);
627 g_clear_error (&error);
628 }
629 }
630 break;
631 case SHELL_APP_STATE_STARTING:
632 break;
633 case SHELL_APP_STATE_RUNNING:
634 shell_app_activate_window (app, NULL, timestamp);
635 break;
636 }
637 }
638
639 /**
640 * shell_app_open_new_window:
641 * @app: a #ShellApp
642 * @workspace: open on this workspace, or -1 for default
643 *
644 * Request that the application create a new window.
645 */
646 void
647 shell_app_open_new_window (ShellApp *app,
648 int workspace)
649 {
650 g_return_if_fail (app->entry != NULL);
651
652 /* Here we just always launch the application again, even if we know
653 * it was already running. For most applications this
654 * should have the effect of creating a new window, whether that's
655 * a second process (in the case of Calculator) or IPC to existing
656 * instance (Firefox). There are a few less-sensical cases such
657 * as say Pidgin. Ideally, we have the application express to us
658 * that it supports an explicit new-window action.
659 */
660 shell_app_launch (app,
661 0,
662 NULL,
663 workspace,
664 NULL,
665 NULL);
666 }
667
668 /**
669 * shell_app_get_state:
670 * @app: a #ShellApp
671 *
672 * Returns: State of the application
673 */
674 ShellAppState
675 shell_app_get_state (ShellApp *app)
676 {
677 return app->state;
678 }
679
680 typedef struct {
681 ShellApp *app;
682 MetaWorkspace *active_workspace;
683 } CompareWindowsData;
684
685 static int
686 shell_app_compare_windows (gconstpointer a,
687 gconstpointer b,
688 gpointer datap)
689 {
690 MetaWindow *win_a = (gpointer)a;
691 MetaWindow *win_b = (gpointer)b;
692 CompareWindowsData *data = datap;
693 gboolean ws_a, ws_b;
694 gboolean vis_a, vis_b;
695
696 ws_a = meta_window_get_workspace (win_a) == data->active_workspace;
697 ws_b = meta_window_get_workspace (win_b) == data->active_workspace;
698
699 if (ws_a && !ws_b)
700 return -1;
701 else if (!ws_a && ws_b)
702 return 1;
703
704 vis_a = meta_window_showing_on_its_workspace (win_a);
705 vis_b = meta_window_showing_on_its_workspace (win_b);
706
707 if (vis_a && !vis_b)
708 return -1;
709 else if (!vis_a && vis_b)
710 return 1;
711
712 return meta_window_get_user_time (win_b) - meta_window_get_user_time (win_a);
713 }
714
715 /**
716 * shell_app_get_windows:
717 * @app:
718 *
719 * Get the toplevel, interesting windows which are associated with this
720 * application. The returned list will be sorted first by whether
721 * they're on the active workspace, then by whether they're visible,
722 * and finally by the time the user last interacted with them.
723 *
724 * Returns: (transfer none) (element-type MetaWindow): List of windows
725 */
726 GSList *
727 shell_app_get_windows (ShellApp *app)
728 {
729 if (app->running_state == NULL)
730 return NULL;
731
732 if (app->running_state->window_sort_stale)
733 {
734 CompareWindowsData data;
735 data.app = app;
736 data.active_workspace = meta_screen_get_active_workspace (shell_global_get_screen (shell_global_get ()));
737 app->running_state->windows = g_slist_sort_with_data (app->running_state->windows, shell_app_compare_windows, &data);
738 app->running_state->window_sort_stale = FALSE;
739 }
740
741 return app->running_state->windows;
742 }
743
744 guint
745 shell_app_get_n_windows (ShellApp *app)
746 {
747 if (app->running_state == NULL)
748 return 0;
749 return g_slist_length (app->running_state->windows);
750 }
751
752 static gboolean
753 shell_app_has_visible_windows (ShellApp *app)
754 {
755 GSList *iter;
756
757 if (app->running_state == NULL)
758 return FALSE;
759
760 for (iter = app->running_state->windows; iter; iter = iter->next)
761 {
762 MetaWindow *window = iter->data;
763
764 if (meta_window_showing_on_its_workspace (window))
765 return TRUE;
766 }
767
768 return FALSE;
769 }
770
771 gboolean
772 shell_app_is_on_workspace (ShellApp *app,
773 MetaWorkspace *workspace)
774 {
775 GSList *iter;
776
777 if (shell_app_get_state (app) == SHELL_APP_STATE_STARTING)
778 {
779 if (app->started_on_workspace == -1 ||
780 meta_workspace_index (workspace) == app->started_on_workspace)
781 return TRUE;
782 else
783 return FALSE;
784 }
785
786 if (app->running_state == NULL)
787 return FALSE;
788
789 for (iter = app->running_state->windows; iter; iter = iter->next)
790 {
791 if (meta_window_get_workspace (iter->data) == workspace)
792 return TRUE;
793 }
794
795 return FALSE;
796 }
797
798 static int
799 shell_app_get_last_user_time (ShellApp *app)
800 {
801 GSList *iter;
802 int last_user_time;
803
804 last_user_time = 0;
805
806 if (app->running_state != NULL)
807 {
808 for (iter = app->running_state->windows; iter; iter = iter->next)
809 last_user_time = MAX (last_user_time, meta_window_get_user_time (iter->data));
810 }
811
812 return last_user_time;
813 }
814
815 /**
816 * shell_app_compare:
817 * @app:
818 * @other: A #ShellApp
819 *
820 * Compare one #ShellApp instance to another, in the following way:
821 * - Running applications sort before not-running applications.
822 * - If one of them has visible windows and the other does not, the one
823 * with visible windows is first.
824 * - Finally, the application which the user interacted with most recently
825 * compares earlier.
826 */
827 int
828 shell_app_compare (ShellApp *app,
829 ShellApp *other)
830 {
831 gboolean vis_app, vis_other;
832
833 if (app->state != other->state)
834 {
835 if (app->state == SHELL_APP_STATE_RUNNING)
836 return -1;
837 return 1;
838 }
839
840 vis_app = shell_app_has_visible_windows (app);
841 vis_other = shell_app_has_visible_windows (other);
842
843 if (vis_app && !vis_other)
844 return -1;
845 else if (!vis_app && vis_other)
846 return 1;
847
848 if (app->state == SHELL_APP_STATE_RUNNING)
849 {
850 if (app->running_state->windows && !other->running_state->windows)
851 return -1;
852 else if (!app->running_state->windows && other->running_state->windows)
853 return 1;
854
855 return shell_app_get_last_user_time (other) - shell_app_get_last_user_time (app);
856 }
857
858 return 0;
859 }
860
861 ShellApp *
862 _shell_app_new_for_window (MetaWindow *window)
863 {
864 ShellApp *app;
865
866 app = g_object_new (SHELL_TYPE_APP, NULL);
867
868 app->window_id_string = g_strdup_printf ("window:%d", meta_window_get_stable_sequence (window));
869
870 _shell_app_add_window (app, window);
871
872 return app;
873 }
874
875 ShellApp *
876 _shell_app_new (GMenuTreeEntry *info)
877 {
878 ShellApp *app;
879
880 app = g_object_new (SHELL_TYPE_APP, NULL);
881
882 _shell_app_set_entry (app, info);
883
884 return app;
885 }
886
887 void
888 _shell_app_set_entry (ShellApp *app,
889 GMenuTreeEntry *entry)
890 {
891 if (app->entry != NULL)
892 gmenu_tree_item_unref (app->entry);
893 app->entry = gmenu_tree_item_ref (entry);
894
895 if (app->name_collation_key != NULL)
896 g_free (app->name_collation_key);
897 app->name_collation_key = g_utf8_collate_key (shell_app_get_name (app), -1);
898 }
899
900 static void
901 shell_app_state_transition (ShellApp *app,
902 ShellAppState state)
903 {
904 if (app->state == state)
905 return;
906 g_return_if_fail (!(app->state == SHELL_APP_STATE_RUNNING &&
907 state == SHELL_APP_STATE_STARTING));
908 app->state = state;
909
910 if (app->state == SHELL_APP_STATE_STOPPED && app->running_state)
911 {
912 unref_running_state (app->running_state);
913 app->running_state = NULL;
914 }
915
916 _shell_app_system_notify_app_state_changed (shell_app_system_get_default (), app);
917
918 g_object_notify (G_OBJECT (app), "state");
919 }
920
921 static void
922 shell_app_on_unmanaged (MetaWindow *window,
923 ShellApp *app)
924 {
925 _shell_app_remove_window (app, window);
926 }
927
928 static void
929 shell_app_on_user_time_changed (MetaWindow *window,
930 GParamSpec *pspec,
931 ShellApp *app)
932 {
933 g_assert (app->running_state != NULL);
934
935 /* Ideally we don't want to emit windows-changed if the sort order
936 * isn't actually changing. This check catches most of those.
937 */
938 if (window != app->running_state->windows->data)
939 {
940 app->running_state->window_sort_stale = TRUE;
941 g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
942 }
943 }
944
945 static void
946 shell_app_on_ws_switch (MetaScreen *screen,
947 int from,
948 int to,
949 MetaMotionDirection direction,
950 gpointer data)
951 {
952 ShellApp *app = SHELL_APP (data);
953
954 g_assert (app->running_state != NULL);
955
956 app->running_state->window_sort_stale = TRUE;
957
958 g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
959 }
960
961 void
962 _shell_app_add_window (ShellApp *app,
963 MetaWindow *window)
964 {
965 if (app->running_state && g_slist_find (app->running_state->windows, window))
966 return;
967
968 g_object_freeze_notify (G_OBJECT (app));
969
970 if (!app->running_state)
971 create_running_state (app);
972
973 app->running_state->window_sort_stale = TRUE;
974 app->running_state->windows = g_slist_prepend (app->running_state->windows, g_object_ref (window));
975 g_signal_connect (window, "unmanaged", G_CALLBACK(shell_app_on_unmanaged), app);
976 g_signal_connect (window, "notify::user-time", G_CALLBACK(shell_app_on_user_time_changed), app);
977
978 shell_app_update_app_menu (app, window);
979
980 if (app->state != SHELL_APP_STATE_STARTING)
981 shell_app_state_transition (app, SHELL_APP_STATE_RUNNING);
982
983 g_object_thaw_notify (G_OBJECT (app));
984
985 g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
986 }
987
988 void
989 _shell_app_remove_window (ShellApp *app,
990 MetaWindow *window)
991 {
992 g_assert (app->running_state != NULL);
993
994 if (!g_slist_find (app->running_state->windows, window))
995 return;
996
997 g_signal_handlers_disconnect_by_func (window, G_CALLBACK(shell_app_on_unmanaged), app);
998 g_signal_handlers_disconnect_by_func (window, G_CALLBACK(shell_app_on_user_time_changed), app);
999 g_object_unref (window);
1000 app->running_state->windows = g_slist_remove (app->running_state->windows, window);
1001
1002 if (app->running_state->windows == NULL)
1003 shell_app_state_transition (app, SHELL_APP_STATE_STOPPED);
1004
1005 g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
1006 }
1007
1008 /**
1009 * shell_app_get_pids:
1010 * @app: a #ShellApp
1011 *
1012 * Returns: (transfer container) (element-type int): An unordered list of process identifers associated with this application.
1013 */
1014 GSList *
1015 shell_app_get_pids (ShellApp *app)
1016 {
1017 GSList *result;
1018 GSList *iter;
1019
1020 result = NULL;
1021 for (iter = shell_app_get_windows (app); iter; iter = iter->next)
1022 {
1023 MetaWindow *window = iter->data;
1024 int pid = meta_window_get_pid (window);
1025 /* Note in the (by far) common case, app will only have one pid, so
1026 * we'll hit the first element, so don't worry about O(N^2) here.
1027 */
1028 if (!g_slist_find (result, GINT_TO_POINTER (pid)))
1029 result = g_slist_prepend (result, GINT_TO_POINTER (pid));
1030 }
1031 return result;
1032 }
1033
1034 void
1035 _shell_app_handle_startup_sequence (ShellApp *app,
1036 SnStartupSequence *sequence)
1037 {
1038 gboolean starting = !sn_startup_sequence_get_completed (sequence);
1039
1040 /* The Shell design calls for on application launch, the app title
1041 * appears at top, and no X window is focused. So when we get
1042 * a startup-notification for this app, transition it to STARTING
1043 * if it's currently stopped, set it as our application focus,
1044 * but focus the no_focus window.
1045 */
1046 if (starting && shell_app_get_state (app) == SHELL_APP_STATE_STOPPED)
1047 {
1048 MetaScreen *screen = shell_global_get_screen (shell_global_get ());
1049 MetaDisplay *display = meta_screen_get_display (screen);
1050
1051 shell_app_state_transition (app, SHELL_APP_STATE_STARTING);
1052 meta_display_focus_the_no_focus_window (display, screen,
1053 sn_startup_sequence_get_timestamp (sequence));
1054 app->started_on_workspace = sn_startup_sequence_get_workspace (sequence);
1055 }
1056
1057 if (!starting)
1058 {
1059 if (app->running_state && app->running_state->windows)
1060 shell_app_state_transition (app, SHELL_APP_STATE_RUNNING);
1061 else /* application have > 1 .desktop file */
1062 shell_app_state_transition (app, SHELL_APP_STATE_STOPPED);
1063 }
1064 }
1065
1066 /**
1067 * shell_app_request_quit:
1068 * @app: A #ShellApp
1069 *
1070 * Initiate an asynchronous request to quit this application.
1071 * The application may interact with the user, and the user
1072 * might cancel the quit request from the application UI.
1073 *
1074 * This operation may not be supported for all applications.
1075 *
1076 * Returns: %TRUE if a quit request is supported for this application
1077 */
1078 gboolean
1079 shell_app_request_quit (ShellApp *app)
1080 {
1081 GSList *iter;
1082
1083 if (shell_app_get_state (app) != SHELL_APP_STATE_RUNNING)
1084 return FALSE;
1085
1086 /* TODO - check for an XSMP connection; we could probably use that */
1087
1088 for (iter = app->running_state->windows; iter; iter = iter->next)
1089 {
1090 MetaWindow *win = iter->data;
1091
1092 if (!shell_window_tracker_is_window_interesting (win))
1093 continue;
1094
1095 meta_window_delete (win, shell_global_get_current_time (shell_global_get ()));
1096 }
1097 return TRUE;
1098 }
1099
1100 static void
1101 _gather_pid_callback (GDesktopAppInfo *gapp,
1102 GPid pid,
1103 gpointer data)
1104 {
1105 ShellApp *app;
1106 ShellWindowTracker *tracker;
1107
1108 g_return_if_fail (data != NULL);
1109
1110 app = SHELL_APP (data);
1111 tracker = shell_window_tracker_get_default ();
1112
1113 _shell_window_tracker_add_child_process_app (tracker,
1114 pid,
1115 app);
1116 }
1117
1118 /**
1119 * shell_app_launch:
1120 * @timestamp: Event timestamp, or 0 for current event timestamp
1121 * @uris: (element-type utf8): List of uris to pass to application
1122 * @workspace: Start on this workspace, or -1 for default
1123 * @startup_id: (out): Returned startup notification ID, or %NULL if none
1124 * @error: A #GError
1125 */
1126 gboolean
1127 shell_app_launch (ShellApp *app,
1128 guint timestamp,
1129 GList *uris,
1130 int workspace,
1131 char **startup_id,
1132 GError **error)
1133 {
1134 GDesktopAppInfo *gapp;
1135 GdkAppLaunchContext *context;
1136 gboolean ret;
1137 ShellGlobal *global;
1138 MetaScreen *screen;
1139 GdkDisplay *gdisplay;
1140
1141 if (startup_id)
1142 *startup_id = NULL;
1143
1144 if (app->entry == NULL)
1145 {
1146 MetaWindow *window = window_backed_app_get_window (app);
1147 /* We can't pass URIs into a window; shouldn't hit this
1148 * code path. If we do, fix the caller to disallow it.
1149 */
1150 g_return_val_if_fail (uris == NULL, TRUE);
1151
1152 meta_window_activate (window, timestamp);
1153 return TRUE;
1154 }
1155
1156 global = shell_global_get ();
1157 screen = shell_global_get_screen (global);
1158 gdisplay = gdk_screen_get_display (shell_global_get_gdk_screen (global));
1159
1160 if (timestamp == 0)
1161 timestamp = shell_global_get_current_time (global);
1162
1163 if (workspace < 0)
1164 workspace = meta_screen_get_active_workspace_index (screen);
1165
1166 context = gdk_display_get_app_launch_context (gdisplay);
1167 gdk_app_launch_context_set_timestamp (context, timestamp);
1168 gdk_app_launch_context_set_desktop (context, workspace);
1169
1170 gapp = gmenu_tree_entry_get_app_info (app->entry);
1171 ret = g_desktop_app_info_launch_uris_as_manager (gapp, uris,
1172 G_APP_LAUNCH_CONTEXT (context),
1173 G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
1174 NULL, NULL,
1175 _gather_pid_callback, app,
1176 error);
1177 g_object_unref (context);
1178
1179 return ret;
1180 }
1181
1182 /**
1183 * shell_app_get_app_info:
1184 * @app: a #ShellApp
1185 *
1186 * Returns: (transfer none): The #GDesktopAppInfo for this app, or %NULL if backed by a window
1187 */
1188 GDesktopAppInfo *
1189 shell_app_get_app_info (ShellApp *app)
1190 {
1191 if (app->entry)
1192 return gmenu_tree_entry_get_app_info (app->entry);
1193 return NULL;
1194 }
1195
1196 /**
1197 * shell_app_get_tree_entry:
1198 * @app: a #ShellApp
1199 *
1200 * Returns: (transfer none): The #GMenuTreeEntry for this app, or %NULL if backed by a window
1201 */
1202 GMenuTreeEntry *
1203 shell_app_get_tree_entry (ShellApp *app)
1204 {
1205 return app->entry;
1206 }
1207
1208 static void
1209 create_running_state (ShellApp *app)
1210 {
1211 MetaScreen *screen;
1212
1213 g_assert (app->running_state == NULL);
1214
1215 screen = shell_global_get_screen (shell_global_get ());
1216 app->running_state = g_slice_new0 (ShellAppRunningState);
1217 app->running_state->refcount = 1;
1218 app->running_state->workspace_switch_id =
1219 g_signal_connect (screen, "workspace-switched", G_CALLBACK(shell_app_on_ws_switch), app);
1220
1221 app->running_state->muxer = g_action_muxer_new ();
1222 }
1223
1224 void
1225 shell_app_update_app_menu (ShellApp *app,
1226 MetaWindow *window)
1227 {
1228 const gchar *unique_bus_name;
1229
1230 /* We assume that 'gtk-application-object-path' and
1231 * 'gtk-app-menu-object-path' are the same for all windows which
1232 * have it set.
1233 *
1234 * It could be possible, however, that the first window we see
1235 * belonging to the app didn't have them set. For this reason, we
1236 * take the values from the first window that has them set and ignore
1237 * all the rest (until the app is stopped and restarted).
1238 */
1239
1240 unique_bus_name = meta_window_get_gtk_unique_bus_name (window);
1241
1242 if (app->running_state->remote_menu == NULL ||
1243 g_strcmp0 (app->running_state->unique_bus_name, unique_bus_name) != 0)
1244 {
1245 const gchar *application_object_path;
1246 const gchar *app_menu_object_path;
1247 GDBusConnection *session;
1248 GDBusActionGroup *actions;
1249
1250 application_object_path = meta_window_get_gtk_application_object_path (window);
1251 app_menu_object_path = meta_window_get_gtk_app_menu_object_path (window);
1252
1253 if (application_object_path == NULL || app_menu_object_path == NULL || unique_bus_name == NULL)
1254 return;
1255
1256 session = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
1257 g_assert (session != NULL);
1258 g_clear_pointer (&app->running_state->unique_bus_name, g_free);
1259 app->running_state->unique_bus_name = g_strdup (unique_bus_name);
1260 g_clear_object (&app->running_state->remote_menu);
1261 app->running_state->remote_menu = g_dbus_menu_model_get (session, unique_bus_name, app_menu_object_path);
1262 actions = g_dbus_action_group_get (session, unique_bus_name, application_object_path);
1263 g_action_muxer_insert (app->running_state->muxer, "app", G_ACTION_GROUP (actions));
1264 g_object_unref (actions);
1265 g_object_unref (session);
1266 }
1267 }
1268
1269 static void
1270 unref_running_state (ShellAppRunningState *state)
1271 {
1272 MetaScreen *screen;
1273
1274 g_assert (state->refcount > 0);
1275
1276 state->refcount--;
1277 if (state->refcount > 0)
1278 return;
1279
1280 screen = shell_global_get_screen (shell_global_get ());
1281 g_signal_handler_disconnect (screen, state->workspace_switch_id);
1282
1283 g_clear_object (&state->remote_menu);
1284 g_clear_object (&state->muxer);
1285 g_clear_pointer (&state->unique_bus_name, g_free);
1286 g_clear_pointer (&state->remote_menu, g_free);
1287
1288 g_slice_free (ShellAppRunningState, state);
1289 }
1290
1291 static char *
1292 trim_exec_line (const char *str)
1293 {
1294 const char *start, *end, *pos;
1295
1296 if (str == NULL)
1297 return NULL;
1298
1299 end = strchr (str, ' ');
1300 if (end == NULL)
1301 end = str + strlen (str);
1302
1303 start = str;
1304 while ((pos = strchr (start, '/')) && pos < end)
1305 start = ++pos;
1306
1307 return g_strndup (start, end - start);
1308 }
1309
1310 static void
1311 shell_app_init_search_data (ShellApp *app)
1312 {
1313 const char *name;
1314 const char *generic_name;
1315 const char *exec;
1316 const char * const *keywords;
1317 char *normalized_exec;
1318 GDesktopAppInfo *appinfo;
1319
1320 appinfo = gmenu_tree_entry_get_app_info (app->entry);
1321 name = g_app_info_get_name (G_APP_INFO (appinfo));
1322 app->casefolded_name = shell_util_normalize_and_casefold (name);
1323
1324 generic_name = g_desktop_app_info_get_generic_name (appinfo);
1325 if (generic_name)
1326 app->casefolded_generic_name = shell_util_normalize_and_casefold (generic_name);
1327 else
1328 app->casefolded_generic_name = NULL;
1329
1330 exec = g_app_info_get_executable (G_APP_INFO (appinfo));
1331 normalized_exec = shell_util_normalize_and_casefold (exec);
1332 app->casefolded_exec = trim_exec_line (normalized_exec);
1333 g_free (normalized_exec);
1334
1335 keywords = g_desktop_app_info_get_keywords (appinfo);
1336
1337 if (keywords)
1338 {
1339 int i;
1340
1341 app->casefolded_keywords = g_new0 (char*, g_strv_length ((char **)keywords) + 1);
1342
1343 i = 0;
1344 while (keywords[i])
1345 {
1346 app->casefolded_keywords[i] = shell_util_normalize_and_casefold (keywords[i]);
1347 ++i;
1348 }
1349 app->casefolded_keywords[i] = NULL;
1350 }
1351 else
1352 app->casefolded_keywords = NULL;
1353 }
1354
1355 /**
1356 * shell_app_compare_by_name:
1357 * @app: One app
1358 * @other: The other app
1359 *
1360 * Order two applications by name.
1361 *
1362 * Returns: -1, 0, or 1; suitable for use as a comparison function
1363 * for e.g. g_slist_sort()
1364 */
1365 int
1366 shell_app_compare_by_name (ShellApp *app, ShellApp *other)
1367 {
1368 return strcmp (app->name_collation_key, other->name_collation_key);
1369 }
1370
1371 static ShellAppSearchMatch
1372 _shell_app_match_search_terms (ShellApp *app,
1373 GSList *terms)
1374 {
1375 GSList *iter;
1376 ShellAppSearchMatch match;
1377
1378 if (G_UNLIKELY (!app->casefolded_name))
1379 shell_app_init_search_data (app);
1380
1381 match = MATCH_NONE;
1382 for (iter = terms; iter; iter = iter->next)
1383 {
1384 ShellAppSearchMatch current_match;
1385 const char *term = iter->data;
1386 const char *p;
1387
1388 current_match = MATCH_NONE;
1389
1390 p = strstr (app->casefolded_name, term);
1391 if (p != NULL)
1392 {
1393 if (p == app->casefolded_name || *(p - 1) == ' ')
1394 current_match = MATCH_PREFIX;
1395 else
1396 current_match = MATCH_SUBSTRING;
1397 }
1398
1399 if (app->casefolded_generic_name)
1400 {
1401 p = strstr (app->casefolded_generic_name, term);
1402 if (p != NULL)
1403 {
1404 if (p == app->casefolded_generic_name || *(p - 1) == ' ')
1405 current_match = MATCH_PREFIX;
1406 else if (current_match < MATCH_PREFIX)
1407 current_match = MATCH_SUBSTRING;
1408 }
1409 }
1410
1411 if (app->casefolded_exec)
1412 {
1413 p = strstr (app->casefolded_exec, term);
1414 if (p != NULL)
1415 {
1416 if (p == app->casefolded_exec || *(p - 1) == '-')
1417 current_match = MATCH_PREFIX;
1418 else if (current_match < MATCH_PREFIX)
1419 current_match = MATCH_SUBSTRING;
1420 }
1421 }
1422
1423 if (app->casefolded_keywords)
1424 {
1425 int i = 0;
1426 while (app->casefolded_keywords[i] && current_match < MATCH_PREFIX)
1427 {
1428 p = strstr (app->casefolded_keywords[i], term);
1429 if (p != NULL)
1430 {
1431 if (p == app->casefolded_keywords[i])
1432 current_match = MATCH_PREFIX;
1433 else
1434 current_match = MATCH_SUBSTRING;
1435 }
1436 ++i;
1437 }
1438 }
1439
1440 if (current_match == MATCH_NONE)
1441 return current_match;
1442
1443 if (current_match > match)
1444 match = current_match;
1445 }
1446 return match;
1447 }
1448
1449 void
1450 _shell_app_do_match (ShellApp *app,
1451 GSList *terms,
1452 GSList **prefix_results,
1453 GSList **substring_results)
1454 {
1455 ShellAppSearchMatch match;
1456 GAppInfo *appinfo;
1457
1458 g_assert (app != NULL);
1459
1460 /* Skip window-backed apps */
1461 appinfo = (GAppInfo*)shell_app_get_app_info (app);
1462 if (appinfo == NULL)
1463 return;
1464 /* Skip not-visible apps */
1465 if (!g_app_info_should_show (appinfo))
1466 return;
1467
1468 match = _shell_app_match_search_terms (app, terms);
1469 switch (match)
1470 {
1471 case MATCH_NONE:
1472 break;
1473 case MATCH_PREFIX:
1474 *prefix_results = g_slist_prepend (*prefix_results, app);
1475 break;
1476 case MATCH_SUBSTRING:
1477 *substring_results = g_slist_prepend (*substring_results, app);
1478 break;
1479 }
1480 }
1481
1482
1483 static void
1484 shell_app_init (ShellApp *self)
1485 {
1486 self->state = SHELL_APP_STATE_STOPPED;
1487 }
1488
1489 static void
1490 shell_app_dispose (GObject *object)
1491 {
1492 ShellApp *app = SHELL_APP (object);
1493
1494 if (app->entry)
1495 {
1496 gmenu_tree_item_unref (app->entry);
1497 app->entry = NULL;
1498 }
1499
1500 if (app->running_state)
1501 {
1502 while (app->running_state->windows)
1503 _shell_app_remove_window (app, app->running_state->windows->data);
1504 }
1505 /* We should have been transitioned when we removed all of our windows */
1506 g_assert (app->state == SHELL_APP_STATE_STOPPED);
1507 g_assert (app->running_state == NULL);
1508
1509 G_OBJECT_CLASS(shell_app_parent_class)->dispose (object);
1510 }
1511
1512 static void
1513 shell_app_finalize (GObject *object)
1514 {
1515 ShellApp *app = SHELL_APP (object);
1516
1517 g_free (app->window_id_string);
1518
1519 g_free (app->casefolded_name);
1520 g_free (app->casefolded_generic_name);
1521 g_free (app->name_collation_key);
1522 g_free (app->casefolded_exec);
1523 g_strfreev (app->casefolded_keywords);
1524
1525 G_OBJECT_CLASS(shell_app_parent_class)->finalize (object);
1526 }
1527
1528 static void
1529 shell_app_class_init(ShellAppClass *klass)
1530 {
1531 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1532
1533 gobject_class->get_property = shell_app_get_property;
1534 gobject_class->dispose = shell_app_dispose;
1535 gobject_class->finalize = shell_app_finalize;
1536
1537 shell_app_signals[WINDOWS_CHANGED] = g_signal_new ("windows-changed",
1538 SHELL_TYPE_APP,
1539 G_SIGNAL_RUN_LAST,
1540 0,
1541 NULL, NULL, NULL,
1542 G_TYPE_NONE, 0);
1543
1544 /**
1545 * ShellApp:state:
1546 *
1547 * The high-level state of the application, effectively whether it's
1548 * running or not, or transitioning between those states.
1549 */
1550 g_object_class_install_property (gobject_class,
1551 PROP_STATE,
1552 g_param_spec_enum ("state",
1553 "State",
1554 "Application state",
1555 SHELL_TYPE_APP_STATE,
1556 SHELL_APP_STATE_STOPPED,
1557 G_PARAM_READABLE));
1558
1559 /**
1560 * ShellApp:id:
1561 *
1562 * The id of this application (a desktop filename, or a special string
1563 * like window:0xabcd1234)
1564 */
1565 g_object_class_install_property (gobject_class,
1566 PROP_ID,
1567 g_param_spec_string ("id",
1568 "Application id",
1569 "The desktop file id of this ShellApp",
1570 NULL,
1571 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1572
1573 /**
1574 * ShellApp:action-group:
1575 *
1576 * The #GDBusActionGroup associated with this ShellApp, if any. See the
1577 * documentation of #GApplication and #GActionGroup for details.
1578 */
1579 g_object_class_install_property (gobject_class,
1580 PROP_ACTION_GROUP,
1581 g_param_spec_object ("action-group",
1582 "Application Action Group",
1583 "The action group exported by the remote application",
1584 G_TYPE_ACTION_GROUP,
1585 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1586 /**
1587 * ShellApp:menu:
1588 *
1589 * The #GMenuProxy associated with this ShellApp, if any. See the
1590 * documentation of #GMenuModel for details.
1591 */
1592 g_object_class_install_property (gobject_class,
1593 PROP_MENU,
1594 g_param_spec_object ("menu",
1595 "Application Menu",
1596 "The primary menu exported by the remote application",
1597 G_TYPE_MENU_MODEL,
1598 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1599
1600 }