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