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 | |
38 | static char * |
39 | get_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 | |
66 | static GIcon * |
67 | get_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 | |
84 | static char * |
85 | gicon_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 | |
114 | static void |
115 | end_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 | |
138 | typedef struct |
139 | { |
140 | GdkDisplay *display; |
141 | char *startup_id; |
142 | gint64 time; |
143 | } StartupNotificationData; |
144 | |
145 | static void |
146 | free_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 | |
155 | typedef struct |
156 | { |
157 | GSList *contexts; |
158 | guint timeout_id; |
159 | } StartupTimeoutData; |
160 | |
161 | static void |
162 | free_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 | |
179 | static gboolean |
180 | startup_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 | |
231 | static void |
232 | add_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 | |
265 | static char * |
266 | gdk_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 | |
395 | static void |
396 | gdk_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 | |
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 | 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 | |