]> Pileus Git - ~andy/gtk/blob - gdk/x11/gdkapplaunchcontext-x11.c
wayland: Synthesize fullscreen window state change
[~andy/gtk] / gdk / x11 / gdkapplaunchcontext-x11.c
1 /* gdkapplaunchcontext-x11.c - Gtk+ implementation for GAppLaunchContext
2
3    Copyright (C) 2007 Red Hat, Inc.
4
5    The Gnome Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    The Gnome Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14
15    You should have received a copy of the GNU Library General Public
16    License along with this library. If not, see <http://www.gnu.org/licenses/>.
17
18    Author: Alexander Larsson <alexl@redhat.com>
19 */
20
21 #include "config.h"
22
23 #include "gdkx11applaunchcontext.h"
24 #include "gdkapplaunchcontextprivate.h"
25 #include "gdkscreen.h"
26 #include "gdkintl.h"
27 #include "gdkprivate-x11.h"
28
29 #include <glib.h>
30 #include <gio/gdesktopappinfo.h>
31
32 #include <string.h>
33 #include <unistd.h>
34
35 static char *
36 get_display_name (GFile     *file,
37                   GFileInfo *info)
38 {
39   char *name, *tmp;
40
41   name = NULL;
42   if (info)
43     name = g_strdup (g_file_info_get_display_name (info));
44
45   if (name == NULL)
46     {
47       name = g_file_get_basename (file);
48       if (!g_utf8_validate (name, -1, NULL))
49         {
50           tmp = name;
51           name =
52             g_uri_escape_string (name, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
53           g_free (tmp);
54         }
55     }
56
57   return name;
58 }
59
60 static GIcon *
61 get_icon (GFile     *file,
62           GFileInfo *info)
63 {
64   GIcon *icon;
65
66   icon = NULL;
67
68   if (info)
69     {
70       icon = g_file_info_get_icon (info);
71       if (icon)
72         g_object_ref (icon);
73     }
74
75   return icon;
76 }
77
78 static char *
79 gicon_to_string (GIcon *icon)
80 {
81   GFile *file;
82   const char *const *names;
83
84   if (G_IS_FILE_ICON (icon))
85     {
86       file = g_file_icon_get_file (G_FILE_ICON (icon));
87       if (file)
88         return g_file_get_path (file);
89     }
90   else if (G_IS_THEMED_ICON (icon))
91     {
92       names = g_themed_icon_get_names (G_THEMED_ICON (icon));
93       if (names)
94         return g_strdup (names[0]);
95     }
96   else if (G_IS_EMBLEMED_ICON (icon))
97     {
98       GIcon *base;
99
100       base = g_emblemed_icon_get_icon (G_EMBLEMED_ICON (icon));
101
102       return gicon_to_string (base);
103     }
104
105   return NULL;
106 }
107
108 static void
109 end_startup_notification (GdkDisplay *display,
110                           const char *startup_id)
111 {
112   gdk_x11_display_broadcast_startup_message (display, "remove",
113                                              "ID", startup_id,
114                                              NULL);
115 }
116
117
118 /* This should be fairly long, as it's confusing to users if a startup
119  * ends when it shouldn't (it appears that the startup failed, and
120  * they have to relaunch the app). Also the timeout only matters when
121  * there are bugs and apps don't end their own startup sequence.
122  *
123  * This timeout is a "last resort" timeout that ignores whether the
124  * startup sequence has shown activity or not.  Metacity and the
125  * tasklist have smarter, and correspondingly able-to-be-shorter
126  * timeouts. The reason our timeout is dumb is that we don't monitor
127  * the sequence (don't use an SnMonitorContext)
128  */
129 #define STARTUP_TIMEOUT_LENGTH_SECONDS 30
130 #define STARTUP_TIMEOUT_LENGTH (STARTUP_TIMEOUT_LENGTH_SECONDS * 1000)
131
132 typedef struct
133 {
134   GdkDisplay *display;
135   char *startup_id;
136   GTimeVal time;
137 } StartupNotificationData;
138
139 static void
140 free_startup_notification_data (gpointer data)
141 {
142   StartupNotificationData *sn_data = data;
143
144   g_object_unref (sn_data->display);
145   g_free (sn_data->startup_id);
146   g_free (sn_data);
147 }
148
149 typedef struct
150 {
151   GSList *contexts;
152   guint timeout_id;
153 } StartupTimeoutData;
154
155 static void
156 free_startup_timeout (void *data)
157 {
158   StartupTimeoutData *std;
159
160   std = data;
161
162   g_slist_foreach (std->contexts, (GFunc) free_startup_notification_data, NULL);
163   g_slist_free (std->contexts);
164
165   if (std->timeout_id != 0)
166     {
167       g_source_remove (std->timeout_id);
168       std->timeout_id = 0;
169     }
170
171   g_free (std);
172 }
173
174 static gboolean
175 startup_timeout (void *data)
176 {
177   StartupTimeoutData *std;
178   GSList *tmp;
179   GTimeVal now;
180   int min_timeout;
181
182   std = data;
183
184   min_timeout = STARTUP_TIMEOUT_LENGTH;
185
186   g_get_current_time (&now);
187
188   tmp = std->contexts;
189   while (tmp != NULL)
190     {
191       StartupNotificationData *sn_data;
192       GSList *next;
193       double elapsed;
194
195       sn_data = tmp->data;
196       next = tmp->next;
197
198       elapsed =
199         ((((double) now.tv_sec - sn_data->time.tv_sec) * G_USEC_PER_SEC +
200           (now.tv_usec - sn_data->time.tv_usec))) / 1000.0;
201
202       if (elapsed >= STARTUP_TIMEOUT_LENGTH)
203         {
204           std->contexts = g_slist_remove (std->contexts, sn_data);
205           end_startup_notification (sn_data->display, sn_data->startup_id);
206           free_startup_notification_data (sn_data);
207         }
208       else
209         {
210           min_timeout = MIN (min_timeout, (STARTUP_TIMEOUT_LENGTH - elapsed));
211         }
212
213       tmp = next;
214     }
215
216   if (std->contexts == NULL)
217     std->timeout_id = 0;
218   else
219     std->timeout_id = g_timeout_add_seconds ((min_timeout + 500)/1000, startup_timeout, std);
220
221   /* always remove this one, but we may have reinstalled another one. */
222   return G_SOURCE_REMOVE;
223 }
224
225
226 static void
227 add_startup_timeout (GdkScreen  *screen,
228                      const char *startup_id)
229 {
230   StartupTimeoutData *data;
231   StartupNotificationData *sn_data;
232
233   data = g_object_get_data (G_OBJECT (screen), "appinfo-startup-data");
234
235   if (data == NULL)
236     {
237       data = g_new (StartupTimeoutData, 1);
238       data->contexts = NULL;
239       data->timeout_id = 0;
240
241       g_object_set_data_full (G_OBJECT (screen), "appinfo-startup-data",
242                               data, free_startup_timeout);
243     }
244
245   sn_data = g_new (StartupNotificationData, 1);
246   sn_data->display = g_object_ref (gdk_screen_get_display (screen));
247   sn_data->startup_id = g_strdup (startup_id);
248   g_get_current_time (&sn_data->time);
249
250   data->contexts = g_slist_prepend (data->contexts, sn_data);
251
252   if (data->timeout_id == 0)
253     data->timeout_id = g_timeout_add_seconds (STARTUP_TIMEOUT_LENGTH_SECONDS,
254                                               startup_timeout, data);
255 }
256
257
258 static char *
259 gdk_x11_app_launch_context_get_startup_notify_id (GAppLaunchContext *context,
260                                                   GAppInfo          *info,
261                                                   GList             *files)
262 {
263   static int sequence = 0;
264   GdkDisplay *display;
265   GdkScreen *screen;
266   int files_count;
267   char *description;
268   char *icon_name;
269   const char *binary_name;
270   const char *application_id;
271   char *screen_str;
272   char *workspace_str;
273   GIcon *icon;
274   guint32 timestamp;
275   char *startup_id;
276   GFileInfo *fileinfo;
277   GdkAppLaunchContext *ctx;
278
279   ctx = GDK_APP_LAUNCH_CONTEXT (context);
280
281   display = ctx->display;
282   if (ctx->screen)
283     screen = ctx->screen;
284   else
285     screen = gdk_display_get_default_screen (ctx->display);
286
287   fileinfo = NULL;
288
289   files_count = g_list_length (files);
290   if (files_count == 0)
291     {
292       description = g_strdup_printf (_("Starting %s"), g_app_info_get_name (info));
293     }
294   else if (files_count == 1)
295     {
296       gchar *display_name;
297
298       if (g_file_is_native (files->data))
299         fileinfo = g_file_query_info (files->data,
300                                       G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
301                                       G_FILE_ATTRIBUTE_STANDARD_ICON,
302                                       0, NULL, NULL);
303
304       display_name = get_display_name (files->data, fileinfo);
305       description = g_strdup_printf (_("Opening %s"), display_name);
306       g_free (display_name);
307     }
308   else
309     description = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE,
310                                                 "Opening %d Item",
311                                                 "Opening %d Items",
312                                                 files_count), files_count);
313
314   icon_name = NULL;
315   if (ctx->icon_name)
316     icon_name = g_strdup (ctx->icon_name);
317   else
318     {
319       icon = NULL;
320
321       if (ctx->icon != NULL)
322         icon = g_object_ref (ctx->icon);
323       else if (files_count == 1)
324         icon = get_icon (files->data, fileinfo);
325
326       if (icon == NULL)
327         {
328           icon = g_app_info_get_icon (info);
329           if (icon != NULL)
330             g_object_ref (icon);
331         }
332
333       if (icon != NULL)
334         {
335           icon_name = gicon_to_string (icon);
336           g_object_unref (icon);
337         }
338     }
339
340   binary_name = g_app_info_get_executable (info);
341
342   timestamp = ctx->timestamp;
343   if (timestamp == GDK_CURRENT_TIME)
344     timestamp = gdk_x11_display_get_user_time (display);
345
346   screen_str = g_strdup_printf ("%d", gdk_screen_get_number (screen));
347   if (ctx->workspace > -1)
348     workspace_str = g_strdup_printf ("%d", ctx->workspace);
349   else
350     workspace_str = NULL;
351
352   if (G_IS_DESKTOP_APP_INFO (info))
353     application_id = g_desktop_app_info_get_filename (G_DESKTOP_APP_INFO (info));
354   else
355     application_id = NULL;
356
357   startup_id = g_strdup_printf ("%s-%lu-%s-%s-%d_TIME%lu",
358                                 g_get_prgname (),
359                                 (unsigned long)getpid (),
360                                 g_get_host_name (),
361                                 binary_name,
362                                 sequence++,
363                                 (unsigned long)timestamp);
364
365   gdk_x11_display_broadcast_startup_message (display, "new",
366                                              "ID", startup_id,
367                                              "NAME", g_app_info_get_name (info),
368                                              "SCREEN", screen_str,
369                                              "BIN", binary_name,
370                                              "ICON", icon_name,
371                                              "DESKTOP", workspace_str,
372                                              "DESCRIPTION", description,
373                                              "WMCLASS", NULL, /* FIXME */
374                                              "APPLICATION_ID", application_id,
375                                              NULL);
376
377   g_free (description);
378   g_free (screen_str);
379   g_free (workspace_str);
380   g_free (icon_name);
381   if (fileinfo)
382     g_object_unref (fileinfo);
383
384   add_startup_timeout (screen, startup_id);
385
386   return startup_id;
387 }
388
389
390 static void
391 gdk_x11_app_launch_context_launch_failed (GAppLaunchContext *context,
392                                           const gchar       *startup_notify_id)
393 {
394   GdkAppLaunchContext *ctx;
395   GdkScreen *screen;
396   StartupTimeoutData *data;
397   StartupNotificationData *sn_data;
398   GSList *l;
399
400   ctx = GDK_APP_LAUNCH_CONTEXT (context);
401
402   if (ctx->screen)
403     screen = ctx->screen;
404   else
405     screen = gdk_display_get_default_screen (ctx->display);
406
407   data = g_object_get_data (G_OBJECT (screen), "appinfo-startup-data");
408
409   if (data)
410     {
411       for (l = data->contexts; l != NULL; l = l->next)
412         {
413           sn_data = l->data;
414           if (strcmp (startup_notify_id, sn_data->startup_id) == 0)
415             {
416               data->contexts = g_slist_remove (data->contexts, sn_data);
417               end_startup_notification (sn_data->display, sn_data->startup_id);
418               free_startup_notification_data (sn_data);
419
420               break;
421             }
422         }
423
424       if (data->contexts == NULL)
425         {
426           g_source_remove (data->timeout_id);
427           data->timeout_id = 0;
428         }
429     }
430 }
431
432 struct _GdkX11AppLaunchContext
433 {
434   GdkAppLaunchContext parent_instance;
435 };
436
437 struct _GdkX11AppLaunchContextClass
438 {
439   GdkAppLaunchContextClass parent_class;
440 };
441
442
443 G_DEFINE_TYPE (GdkX11AppLaunchContext, gdk_x11_app_launch_context, GDK_TYPE_APP_LAUNCH_CONTEXT)
444
445 static void
446 gdk_x11_app_launch_context_class_init (GdkX11AppLaunchContextClass *klass)
447 {
448   GAppLaunchContextClass *ctx_class = G_APP_LAUNCH_CONTEXT_CLASS (klass);
449
450   ctx_class->get_startup_notify_id = gdk_x11_app_launch_context_get_startup_notify_id;
451   ctx_class->launch_failed = gdk_x11_app_launch_context_launch_failed;
452 }
453
454 static void
455 gdk_x11_app_launch_context_init (GdkX11AppLaunchContext *ctx)
456 {
457 }
458
459 GdkAppLaunchContext *
460 _gdk_x11_display_get_app_launch_context (GdkDisplay *display)
461 {
462   GdkAppLaunchContext *ctx;
463
464   ctx = g_object_new (GDK_TYPE_X11_APP_LAUNCH_CONTEXT,
465                       "display", display,
466                       NULL);
467
468   return ctx;
469 }