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

source code of gtk/gdk/x11/gdkapplaunchcontext-x11.c