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