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