1 /* application object */
2 /* vim: set sw=2 et: */
3
4 /*
5 * Copyright (C) 2001 Havoc Pennington
6 * Copyright (C) 2003 Red Hat, Inc.
7 * Copyright (C) 2005-2007 Vincent Untz
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU Library General Public
20 * License along with this library; if not, write to the
21 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 * Boston, MA 02111-1307, USA.
23 */
24
25 #include <config.h>
26
27 #include <glib/gi18n-lib.h>
28 #include "application.h"
29 #include "private.h"
30
31 /**
32 * SECTION:application
33 * @short_description: an object representing a group of windows of the same
34 * application.
35 * @see_also: wnck_window_get_application()
36 * @stability: Unstable
37 *
38 * The #WnckApplication is a group of #WnckWindow that are all in the same
39 * application. It can be used to represent windows by applications, group
40 * windows by applications or to manipulate all windows of a particular
41 * application.
42 *
43 * A #WnckApplication is identified by the group leader of the #WnckWindow
44 * belonging to it, and new #WnckWindow are added to a #WnckApplication if and
45 * only if they have the group leader of the #WnckApplication.
46 *
47 * The #WnckApplication objects are always owned by libwnck and must not be
48 * referenced or unreferenced.
49 */
50
51 #define FALLBACK_NAME _("Untitled application")
52
53 static GHashTable *app_hash = NULL;
54
55 struct _WnckApplicationPrivate
56 {
57 Window xwindow; /* group leader */
58 WnckScreen *screen;
59 GList *windows;
60 int pid;
61 char *name;
62
63 WnckWindow *name_window; /* window we are using name of */
64
65 GdkPixbuf *icon;
66 GdkPixbuf *mini_icon;
67
68 WnckIconCache *icon_cache;
69
70 WnckWindow *icon_window;
71
72 char *startup_id;
73
74 guint name_from_leader : 1; /* name is from group leader */
75 guint icon_from_leader : 1;
76
77 guint need_emit_icon_changed : 1;
78 };
79
80 G_DEFINE_TYPE (WnckApplication, wnck_application, G_TYPE_OBJECT);
81 #define WNCK_APPLICATION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), WNCK_TYPE_APPLICATION, WnckApplicationPrivate))
82
83 enum {
84 NAME_CHANGED,
85 ICON_CHANGED,
86 LAST_SIGNAL
87 };
88
89 static void emit_name_changed (WnckApplication *app);
90 static void emit_icon_changed (WnckApplication *app);
91
92 static void reset_name (WnckApplication *app);
93 static void update_name (WnckApplication *app);
94
95 static void wnck_application_init (WnckApplication *application);
96 static void wnck_application_class_init (WnckApplicationClass *klass);
97 static void wnck_application_finalize (GObject *object);
98
99
100 static guint signals[LAST_SIGNAL] = { 0 };
101
102 static void
wnck_application_init(WnckApplication * application)103 wnck_application_init (WnckApplication *application)
104 {
105 application->priv = WNCK_APPLICATION_GET_PRIVATE (application);
106
107 application->priv->xwindow = None;
108 application->priv->screen = NULL;
109 application->priv->windows = NULL;
110 application->priv->pid = 0;
111 application->priv->name = NULL;
112
113 application->priv->name_window = NULL;
114
115 application->priv->icon = NULL;
116 application->priv->mini_icon = NULL;
117
118 application->priv->icon_cache = _wnck_icon_cache_new ();
119 _wnck_icon_cache_set_want_fallback (application->priv->icon_cache,
120 FALSE);
121
122 application->priv->icon_window = NULL;
123
124 application->priv->startup_id = NULL;
125
126 application->priv->name_from_leader = FALSE;
127 application->priv->icon_from_leader = FALSE;
128
129 application->priv->need_emit_icon_changed = FALSE;
130 }
131
132 static void
wnck_application_class_init(WnckApplicationClass * klass)133 wnck_application_class_init (WnckApplicationClass *klass)
134 {
135 GObjectClass *object_class = G_OBJECT_CLASS (klass);
136
137 g_type_class_add_private (klass, sizeof (WnckApplicationPrivate));
138
139 object_class->finalize = wnck_application_finalize;
140
141 /**
142 * WnckApplication::name-changed:
143 * @app: the #WnckApplication which emitted the signal.
144 *
145 * Emitted when the name of @app changes.
146 */
147 signals[NAME_CHANGED] =
148 g_signal_new ("name_changed",
149 G_OBJECT_CLASS_TYPE (object_class),
150 G_SIGNAL_RUN_LAST,
151 G_STRUCT_OFFSET (WnckApplicationClass, name_changed),
152 NULL, NULL,
153 g_cclosure_marshal_VOID__VOID,
154 G_TYPE_NONE, 0);
155
156 /**
157 * WnckApplication::icon-changed:
158 * @app: the #WnckApplication which emitted the signal.
159 *
160 * Emitted when the icon of @app changes.
161 */
162 signals[ICON_CHANGED] =
163 g_signal_new ("icon_changed",
164 G_OBJECT_CLASS_TYPE (object_class),
165 G_SIGNAL_RUN_LAST,
166 G_STRUCT_OFFSET (WnckApplicationClass, icon_changed),
167 NULL, NULL,
168 g_cclosure_marshal_VOID__VOID,
169 G_TYPE_NONE, 0);
170 }
171
172 static void
wnck_application_finalize(GObject * object)173 wnck_application_finalize (GObject *object)
174 {
175 WnckApplication *application;
176
177 application = WNCK_APPLICATION (object);
178
179 application->priv->xwindow = None;
180
181 g_list_free (application->priv->windows);
182 application->priv->windows = NULL;
183
184 g_free (application->priv->name);
185 application->priv->name = NULL;
186
187 if (application->priv->icon)
188 g_object_unref (G_OBJECT (application->priv->icon));
189 application->priv->icon = NULL;
190
191 if (application->priv->mini_icon)
192 g_object_unref (G_OBJECT (application->priv->mini_icon));
193 application->priv->mini_icon = NULL;
194
195 _wnck_icon_cache_free (application->priv->icon_cache);
196 application->priv->icon_cache = NULL;
197
198 g_free (application->priv->startup_id);
199 application->priv->startup_id = NULL;
200
201 G_OBJECT_CLASS (wnck_application_parent_class)->finalize (object);
202 }
203
204 /**
205 * wnck_application_get:
206 * @xwindow: the X window ID of a group leader.
207 *
208 * Gets the #WnckApplication corresponding to the group leader with @xwindow
209 * as X window ID.
210 *
211 * Return value: (transfer none): the #WnckApplication corresponding to
212 * @xwindow, or %NULL if there no such #WnckApplication could be found. The
213 * returned #WnckApplication is owned by libwnck and must not be referenced or
214 * unreferenced.
215 */
216 WnckApplication*
wnck_application_get(gulong xwindow)217 wnck_application_get (gulong xwindow)
218 {
219 if (app_hash == NULL)
220 return NULL;
221 else
222 return g_hash_table_lookup (app_hash, &xwindow);
223 }
224
225 /**
226 * wnck_application_get_xid:
227 * @app: a #WnckApplication.
228 *
229 * Gets the X window ID of the group leader window for @app.
230 *
231 * Return value: the X window ID of the group leader window for @app.
232 **/
233 gulong
wnck_application_get_xid(WnckApplication * app)234 wnck_application_get_xid (WnckApplication *app)
235 {
236 g_return_val_if_fail (WNCK_IS_APPLICATION (app), 0);
237
238 return app->priv->xwindow;
239 }
240
241 /**
242 * wnck_application_get_windows:
243 * @app: a #WnckApplication.
244 *
245 * Gets the list of #WnckWindow belonging to @app.
246 *
247 * Return value: (element-type WnckWindow) (transfer none): the list of
248 * #WnckWindow belonging to @app, or %NULL if the application contains no
249 * window. The list should not be modified nor freed, as it is owned by @app.
250 **/
251 GList*
wnck_application_get_windows(WnckApplication * app)252 wnck_application_get_windows (WnckApplication *app)
253 {
254 g_return_val_if_fail (WNCK_IS_APPLICATION (app), NULL);
255
256 return app->priv->windows;
257 }
258
259 /**
260 * wnck_application_get_n_windows:
261 * @app: a #WnckApplication.
262 *
263 * Gets the number of #WnckWindow belonging to @app.
264 *
265 * Return value: the number of #WnckWindow belonging to @app.
266 **/
267 int
wnck_application_get_n_windows(WnckApplication * app)268 wnck_application_get_n_windows (WnckApplication *app)
269 {
270 g_return_val_if_fail (WNCK_IS_APPLICATION (app), 0);
271
272 return g_list_length (app->priv->windows);
273 }
274
275 /**
276 * wnck_application_get_name:
277 * @app: a #WnckApplication.
278 *
279 * Gets the name of @app. Since there is no way to properly find this name,
280 * various suboptimal heuristics are used to find it. GTK+ should probably have
281 * a function to allow applications to set the _NET_WM_NAME property on the
282 * group leader as the application name, and the <ulink
283 * url="http://standards.freedesktop.org/wm-spec/wm-spec-latest.html">EWMH</ulink>
284 * should say that this is where the application name goes.
285 *
286 * Return value: the name of @app, or a fallback name if no name is available.
287 **/
288 const char*
wnck_application_get_name(WnckApplication * app)289 wnck_application_get_name (WnckApplication *app)
290 {
291 g_return_val_if_fail (WNCK_IS_APPLICATION (app), NULL);
292
293 if (app->priv->name)
294 return app->priv->name;
295 else
296 return FALLBACK_NAME;
297 }
298
299 /**
300 * wnck_application_get_icon_name:
301 * @app: a #WnckApplication
302 *
303 * Gets the icon name of @app (to be used when @app is minimized). Since
304 * there is no way to properly find this name, various suboptimal heuristics
305 * are used to find it.
306 *
307 * Return value: the icon name of @app, or a fallback icon name if no icon name
308 * is available.
309 **/
310 const char*
wnck_application_get_icon_name(WnckApplication * app)311 wnck_application_get_icon_name (WnckApplication *app)
312 {
313 g_return_val_if_fail (WNCK_IS_APPLICATION (app), NULL);
314
315 /* FIXME this isn't actually implemented, should be different
316 * from regular name
317 */
318
319 if (app->priv->name)
320 return app->priv->name;
321 else
322 return FALLBACK_NAME;
323 }
324
325 /**
326 * wnck_application_get_pid:
327 * @app: a #WnckApplication.
328 *
329 * Gets the process ID of @app.
330 *
331 * Return value: the process ID of @app, or 0 if none is available.
332 **/
333 int
wnck_application_get_pid(WnckApplication * app)334 wnck_application_get_pid (WnckApplication *app)
335 {
336 g_return_val_if_fail (WNCK_IS_APPLICATION (app), 0);
337
338 return app->priv->pid;
339 }
340
341 static void
get_icons(WnckApplication * app)342 get_icons (WnckApplication *app)
343 {
344 GdkPixbuf *icon;
345 GdkPixbuf *mini_icon;
346
347 icon = NULL;
348 mini_icon = NULL;
349
350 if (_wnck_read_icons (app->priv->xwindow,
351 app->priv->icon_cache,
352 &icon,
353 DEFAULT_ICON_WIDTH, DEFAULT_ICON_HEIGHT,
354 &mini_icon,
355 DEFAULT_MINI_ICON_WIDTH,
356 DEFAULT_MINI_ICON_HEIGHT))
357 {
358 app->priv->need_emit_icon_changed = TRUE;
359 app->priv->icon_from_leader = TRUE;
360
361 if (app->priv->icon)
362 g_object_unref (G_OBJECT (app->priv->icon));
363
364 if (app->priv->mini_icon)
365 g_object_unref (G_OBJECT (app->priv->mini_icon));
366
367 app->priv->icon = icon;
368 app->priv->mini_icon = mini_icon;
369 }
370
371 /* FIXME we should really fall back to using the icon
372 * for one of the windows. But then we need to be more
373 * complicated about icon_changed and when the icon
374 * needs updating and all that.
375 */
376
377 g_assert ((app->priv->icon && app->priv->mini_icon) ||
378 !(app->priv->icon || app->priv->mini_icon));
379 }
380
381 /* Prefer to get group icon from a window of type "normal" */
382 static WnckWindow*
find_icon_window(WnckApplication * app)383 find_icon_window (WnckApplication *app)
384 {
385 GList *tmp;
386
387 tmp = app->priv->windows;
388 while (tmp != NULL)
389 {
390 WnckWindow *w = tmp->data;
391
392 if (wnck_window_get_window_type (w) == WNCK_WINDOW_NORMAL)
393 return w;
394
395 tmp = tmp->next;
396 }
397
398 if (app->priv->windows)
399 return app->priv->windows->data;
400 else
401 return NULL;
402 }
403
404 /**
405 * wnck_application_get_icon:
406 * @app: a #WnckApplication.
407 *
408 * Gets the icon to be used for @app. If no icon is set for @app, a
409 * suboptimal heuristic is used to find an appropriate icon. If no icon was
410 * found, a fallback icon is used.
411 *
412 * Return value: the icon for @app. The caller should reference the returned
413 * <classname>GdkPixbuf</classname> if it needs to keep the icon around.
414 **/
415 GdkPixbuf*
wnck_application_get_icon(WnckApplication * app)416 wnck_application_get_icon (WnckApplication *app)
417 {
418 g_return_val_if_fail (WNCK_IS_APPLICATION (app), NULL);
419
420 get_icons (app);
421 if (app->priv->need_emit_icon_changed)
422 emit_icon_changed (app);
423
424 if (app->priv->icon)
425 return app->priv->icon;
426 else
427 {
428 WnckWindow *w = find_icon_window (app);
429 if (w)
430 return wnck_window_get_icon (w);
431 else
432 return NULL;
433 }
434 }
435
436 /**
437 * wnck_application_get_mini_icon:
438 * @app: a #WnckApplication.
439 *
440 * Gets the mini-icon to be used for @app. If no mini-icon is set for @app,
441 * a suboptimal heuristic is used to find an appropriate icon. If no mini-icon
442 * was found, a fallback mini-icon is used.
443 *
444 * Return value: the mini-icon for @app. The caller should reference the
445 * returned <classname>GdkPixbuf</classname> if it needs to keep the mini-icon
446 * around.
447 **/
448 GdkPixbuf*
wnck_application_get_mini_icon(WnckApplication * app)449 wnck_application_get_mini_icon (WnckApplication *app)
450 {
451 g_return_val_if_fail (WNCK_IS_APPLICATION (app), NULL);
452
453 get_icons (app);
454 if (app->priv->need_emit_icon_changed)
455 emit_icon_changed (app);
456
457 if (app->priv->mini_icon)
458 return app->priv->mini_icon;
459 else
460 {
461 WnckWindow *w = find_icon_window (app);
462 if (w)
463 return wnck_window_get_mini_icon (w);
464 else
465 return NULL;
466 }
467 }
468
469 /**
470 * wnck_application_get_icon_is_fallback:
471 * @app: a #WnckApplication
472 *
473 * Gets whether a default fallback icon is used for @app (because none
474 * was set on @app).
475 *
476 * Return value: %TRUE if the icon for @app is a fallback, %FALSE otherwise.
477 **/
478 gboolean
wnck_application_get_icon_is_fallback(WnckApplication * app)479 wnck_application_get_icon_is_fallback (WnckApplication *app)
480 {
481 g_return_val_if_fail (WNCK_IS_APPLICATION (app), FALSE);
482
483 if (app->priv->icon)
484 return FALSE;
485 else
486 {
487 WnckWindow *w = find_icon_window (app);
488 if (w)
489 return wnck_window_get_icon_is_fallback (w);
490 else
491 return TRUE;
492 }
493 }
494
495 /**
496 * wnck_application_get_startup_id:
497 * @app: a #WnckApplication.
498 *
499 * Gets the startup sequence ID used for startup notification of @app.
500 *
501 * Return value: the startup sequence ID used for startup notification of @app,
502 * or %NULL if none is available.
503 *
504 * Since: 2.2
505 */
506 const char*
wnck_application_get_startup_id(WnckApplication * app)507 wnck_application_get_startup_id (WnckApplication *app)
508 {
509 g_return_val_if_fail (WNCK_IS_APPLICATION (app), NULL);
510
511 return app->priv->startup_id;
512 }
513
514 /* xwindow is a group leader */
515 WnckApplication*
_wnck_application_create(Window xwindow,WnckScreen * screen)516 _wnck_application_create (Window xwindow,
517 WnckScreen *screen)
518 {
519 WnckApplication *application;
520
521 if (app_hash == NULL)
522 app_hash = g_hash_table_new (_wnck_xid_hash, _wnck_xid_equal);
523
524 g_return_val_if_fail (g_hash_table_lookup (app_hash, &xwindow) == NULL,
525 NULL);
526
527 application = g_object_new (WNCK_TYPE_APPLICATION, NULL);
528 application->priv->xwindow = xwindow;
529 application->priv->screen = screen;
530
531 application->priv->name = _wnck_get_name (xwindow);
532
533 if (application->priv->name == NULL)
534 application->priv->name = _wnck_get_res_class_utf8 (xwindow);
535
536 if (application->priv->name)
537 application->priv->name_from_leader = TRUE;
538
539 application->priv->pid = _wnck_get_pid (application->priv->xwindow);
540
541 application->priv->startup_id = _wnck_get_utf8_property (application->priv->xwindow,
542 _wnck_atom_get ("_NET_STARTUP_ID"));
543
544 g_hash_table_insert (app_hash, &application->priv->xwindow, application);
545
546 /* Hash now owns one ref, caller gets none */
547
548 /* Note that xwindow may correspond to a WnckWindow's xwindow,
549 * so we select events needed by either
550 */
551 _wnck_select_input (application->priv->xwindow,
552 WNCK_APP_WINDOW_EVENT_MASK);
553
554 return application;
555 }
556
557 void
_wnck_application_destroy(WnckApplication * application)558 _wnck_application_destroy (WnckApplication *application)
559 {
560 g_return_if_fail (wnck_application_get (application->priv->xwindow) == application);
561
562 g_hash_table_remove (app_hash, &application->priv->xwindow);
563
564 g_return_if_fail (wnck_application_get (application->priv->xwindow) == NULL);
565
566 /* remove hash's ref on the application */
567 g_object_unref (G_OBJECT (application));
568 }
569
570 static void
window_name_changed(WnckWindow * window,WnckApplication * app)571 window_name_changed (WnckWindow *window,
572 WnckApplication *app)
573 {
574 if (window == app->priv->name_window)
575 {
576 reset_name (app);
577 update_name (app);
578 }
579 }
580
581 void
_wnck_application_add_window(WnckApplication * app,WnckWindow * window)582 _wnck_application_add_window (WnckApplication *app,
583 WnckWindow *window)
584 {
585 g_return_if_fail (WNCK_IS_APPLICATION (app));
586 g_return_if_fail (WNCK_IS_WINDOW (window));
587 g_return_if_fail (wnck_window_get_application (window) == NULL);
588
589 app->priv->windows = g_list_prepend (app->priv->windows, window);
590 _wnck_window_set_application (window, app);
591
592 g_signal_connect (G_OBJECT (window), "name_changed",
593 G_CALLBACK (window_name_changed), app);
594
595 /* emits signals, so do it last */
596 reset_name (app);
597 update_name (app);
598
599 /* see if we're using icon from a window */
600 if (app->priv->icon == NULL ||
601 app->priv->mini_icon == NULL)
602 emit_icon_changed (app);
603 }
604
605 void
_wnck_application_remove_window(WnckApplication * app,WnckWindow * window)606 _wnck_application_remove_window (WnckApplication *app,
607 WnckWindow *window)
608 {
609 g_return_if_fail (WNCK_IS_APPLICATION (app));
610 g_return_if_fail (WNCK_IS_WINDOW (window));
611 g_return_if_fail (wnck_window_get_application (window) == app);
612
613 app->priv->windows = g_list_remove (app->priv->windows, window);
614 _wnck_window_set_application (window, NULL);
615
616 g_signal_handlers_disconnect_by_func (G_OBJECT (window),
617 window_name_changed, app);
618
619 /* emits signals, so do it last */
620 reset_name (app);
621 update_name (app);
622
623 /* see if we're using icon from a window */
624 if (app->priv->icon == NULL ||
625 app->priv->mini_icon == NULL)
626 emit_icon_changed (app);
627 }
628
629 void
_wnck_application_process_property_notify(WnckApplication * app,XEvent * xevent)630 _wnck_application_process_property_notify (WnckApplication *app,
631 XEvent *xevent)
632 {
633 /* This prop notify is on the leader window */
634
635 if (xevent->xproperty.atom == XA_WM_NAME ||
636 xevent->xproperty.atom ==
637 _wnck_atom_get ("_NET_WM_NAME") ||
638 xevent->xproperty.atom ==
639 _wnck_atom_get ("_NET_WM_VISIBLE_NAME"))
640 {
641 /* FIXME should change the name */
642 }
643 else if (xevent->xproperty.atom ==
644 XA_WM_ICON_NAME ||
645 xevent->xproperty.atom ==
646 _wnck_atom_get ("_NET_WM_ICON_NAME") ||
647 xevent->xproperty.atom ==
648 _wnck_atom_get ("_NET_WM_VISIBLE_ICON_NAME"))
649 {
650 /* FIXME */
651 }
652 else if (xevent->xproperty.atom ==
653 _wnck_atom_get ("_NET_WM_ICON") ||
654 xevent->xproperty.atom ==
655 _wnck_atom_get ("KWM_WIN_ICON") ||
656 xevent->xproperty.atom ==
657 _wnck_atom_get ("WM_NORMAL_HINTS"))
658 {
659 _wnck_icon_cache_property_changed (app->priv->icon_cache,
660 xevent->xproperty.atom);
661 emit_icon_changed (app);
662 }
663 else if (xevent->xproperty.atom ==
664 _wnck_atom_get ("_NET_STARTUP_ID"))
665 {
666 /* FIXME update startup id */
667 }
668 }
669
670 static void
emit_name_changed(WnckApplication * app)671 emit_name_changed (WnckApplication *app)
672 {
673 g_signal_emit (G_OBJECT (app),
674 signals[NAME_CHANGED],
675 0);
676 }
677
678 static void
emit_icon_changed(WnckApplication * app)679 emit_icon_changed (WnckApplication *app)
680 {
681 app->priv->need_emit_icon_changed = FALSE;
682 g_signal_emit (G_OBJECT (app),
683 signals[ICON_CHANGED],
684 0);
685 }
686
687 static void
reset_name(WnckApplication * app)688 reset_name (WnckApplication *app)
689 {
690 if (!app->priv->name_from_leader)
691 {
692 g_free (app->priv->name);
693 app->priv->name = NULL;
694 app->priv->name_window = NULL;
695 }
696 }
697
698 static void
update_name(WnckApplication * app)699 update_name (WnckApplication *app)
700 {
701 g_assert (app->priv->name_from_leader || app->priv->name == NULL);
702
703 if (app->priv->name == NULL)
704 {
705 /* if only one window, get name from there. If more than one and
706 * they all have the same res_class, use that. Else we want to
707 * use the fallback name, since using the title of one of the
708 * windows would look wrong.
709 */
710 if (app->priv->windows &&
711 app->priv->windows->next == NULL)
712 {
713 app->priv->name =
714 g_strdup (wnck_window_get_name (app->priv->windows->data));
715 app->priv->name_window = app->priv->windows->data;
716 emit_name_changed (app);
717 }
718 else if (app->priv->windows)
719 {
720 /* more than one */
721 app->priv->name =
722 _wnck_get_res_class_utf8 (wnck_window_get_xid (app->priv->windows->data));
723 if (app->priv->name)
724 {
725 app->priv->name_window = app->priv->windows->data;
726 emit_name_changed (app);
727 }
728 }
729 }
730 }
731