Applied Bernard Jungen's patch and suggestion:
[rox-filer/th.git] / ROX-Filer / src / tasklist.c
blob66c0b68adb5c93db21faf8d155a89ee7c3523fe6
1 /*
2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
20 /* tasklist.c - code for tracking windows
22 * Loosly based on code in GNOME's libwnck.
25 #include "config.h"
27 #include <stdlib.h>
29 #include <gtk/gtk.h>
30 #include <gdk/gdk.h>
31 #include <gdk/gdkx.h>
33 #include <X11/Xlib.h>
34 #include <X11/Xatom.h>
36 #include "global.h"
38 #include "tasklist.h"
39 #include "wrapped.h"
40 #include "options.h"
41 #include "gui_support.h"
42 #include "main.h"
43 #include "pinboard.h"
44 #include "pixmaps.h"
45 #include "support.h"
47 /* There is one of these for each window controlled by the window
48 * manager (all tasks) in the _NET_CLIENT_LIST property.
50 typedef struct _IconWindow IconWindow;
52 struct _IconWindow {
53 GtkWidget *widget; /* Widget used for icon when iconified */
54 GtkWidget *label;
55 gchar *text;
56 Window xwindow;
57 gboolean iconified;
58 gint timeout_update; /* Non-zero => timeout callback in use */
61 /* If TRUE, only iconfied windows with _NET_WM_STATE_HIDDEN are really icons */
62 static gboolean wm_supports_hidden = FALSE;
64 static GdkAtom xa__NET_SUPPORTED = GDK_NONE;
65 static GdkAtom xa_WM_STATE = GDK_NONE;
66 static GdkAtom xa_WM_NAME = GDK_NONE;
67 static GdkAtom xa_WM_ICON_NAME = GDK_NONE;
68 static GdkAtom xa_UTF8_STRING = GDK_NONE;
69 static GdkAtom xa_TEXT = GDK_NONE;
70 static GdkAtom xa__NET_WM_VISIBLE_NAME = GDK_NONE;
71 static GdkAtom xa__NET_WM_ICON_NAME = GDK_NONE;
72 static GdkAtom xa__NET_CLIENT_LIST = GDK_NONE;
73 static GdkAtom xa__NET_WM_ICON_GEOMETRY = GDK_NONE;
74 static GdkAtom xa__NET_WM_STATE = GDK_NONE;
75 static GdkAtom xa__NET_WM_STATE_HIDDEN = GDK_NONE;
76 static GdkAtom xa__NET_WM_DESKTOP = GDK_NONE;
77 static GdkAtom xa__NET_CURRENT_DESKTOP = GDK_NONE;
78 static GdkAtom xa__NET_DESKTOP_GEOMETRY = GDK_NONE;
79 static GdkAtom xa__NET_DESKTOP_VIEWPORT = GDK_NONE;
81 /* We have selected destroy and property events on every window in
82 * this table.
84 static GHashTable *known = NULL; /* XID -> IconWindow */
86 /* Static prototypes */
87 static void remove_window(Window win);
88 static void tasklist_update(gboolean to_empty);
89 static GdkFilterReturn window_filter(GdkXEvent *xevent,
90 GdkEvent *event,
91 gpointer data);
92 static guint xid_hash(XID *xid);
93 static gboolean xid_equal(XID *a, XID *b);
94 static void state_changed(IconWindow *win);
95 static void show_icon(IconWindow *win);
96 static void icon_win_free(IconWindow *win);
97 static void update_style(gpointer key, gpointer data, gpointer user_data);
98 static void update_supported(void);
99 static gboolean update_title(gpointer data);
100 static void update_current_desktop(void);
102 /* remember what desktop number (and viewport) is currently displayed */
103 static int cur_desktop = 0;
104 static GdkRectangle cur_viewport;
106 /****************************************************************
107 * EXTERNAL INTERFACE *
108 ****************************************************************/
110 void tasklist_set_active(gboolean active)
112 static gboolean need_init = TRUE;
113 static gboolean tasklist_active = FALSE;
115 if (active == tasklist_active)
117 if (o_pinboard_tasklist_per_workspace.has_changed && active)
118 tasklist_update(FALSE);
119 return;
121 tasklist_active = active;
123 if (need_init)
125 GdkWindow *root;
127 root = gdk_get_default_root_window();
129 xa__NET_SUPPORTED = gdk_atom_intern("_NET_SUPPORTED", FALSE);
130 xa_WM_STATE = gdk_atom_intern("WM_STATE", FALSE);
131 xa_WM_ICON_NAME = gdk_atom_intern("WM_ICON_NAME", FALSE);
132 xa_WM_NAME = gdk_atom_intern("WM_NAME", FALSE);
133 xa_UTF8_STRING = gdk_atom_intern("UTF8_STRING", FALSE);
134 xa_TEXT = gdk_atom_intern("TEXT", FALSE);
135 xa__NET_CLIENT_LIST =
136 gdk_atom_intern("_NET_CLIENT_LIST", FALSE);
137 xa__NET_WM_VISIBLE_NAME =
138 gdk_atom_intern("_NET_WM_VISIBLE_NAME", FALSE);
139 xa__NET_WM_ICON_NAME =
140 gdk_atom_intern("_NET_WM_ICON_NAME", FALSE);
141 xa__NET_WM_ICON_GEOMETRY =
142 gdk_atom_intern("_NET_WM_ICON_GEOMETRY", FALSE);
143 xa__NET_WM_STATE = gdk_atom_intern("_NET_WM_STATE", FALSE);
144 xa__NET_WM_STATE_HIDDEN =
145 gdk_atom_intern("_NET_WM_STATE_HIDDEN", FALSE);
146 xa__NET_WM_DESKTOP = gdk_atom_intern("_NET_WM_DESKTOP", FALSE);
147 xa__NET_CURRENT_DESKTOP = gdk_atom_intern("_NET_CURRENT_DESKTOP", FALSE);
148 xa__NET_DESKTOP_GEOMETRY = gdk_atom_intern("_NET_DESKTOP_GEOMETRY", FALSE);
149 xa__NET_DESKTOP_VIEWPORT = gdk_atom_intern("_NET_DESKTOP_VIEWPORT", FALSE);
151 known = g_hash_table_new_full((GHashFunc) xid_hash,
152 (GEqualFunc) xid_equal,
153 NULL,
154 (GDestroyNotify) icon_win_free);
155 gdk_window_set_events(root, gdk_window_get_events(root) |
156 GDK_PROPERTY_CHANGE_MASK);
157 need_init = FALSE;
160 if (active)
162 update_current_desktop();
163 gdk_window_add_filter(NULL, window_filter, NULL);
164 update_supported();
166 else
167 gdk_window_remove_filter(NULL, window_filter, NULL);
169 tasklist_update(!active);
172 /* User has changes the colours in the options box... */
173 void tasklist_style_changed(void)
175 if (known)
176 g_hash_table_foreach(known, update_style, NULL);
179 /****************************************************************
180 * INTERNAL FUNCTIONS *
181 ****************************************************************/
183 static void icon_win_free(IconWindow *win)
185 g_return_if_fail(win->widget == NULL);
186 g_return_if_fail(win->label == NULL);
188 if (win->timeout_update)
189 g_source_remove(win->timeout_update);
191 g_free(win->text);
192 g_free(win);
195 /* From gdk */
196 static guint xid_hash(XID *xid)
198 return *xid;
201 /* From gdk */
202 static gboolean xid_equal(XID *a, XID *b)
204 return (*a == *b);
207 static int wincmp(const void *a, const void *b)
209 const Window *aw = a;
210 const Window *bw = b;
212 if (*aw < *bw)
213 return -1;
214 else if (*aw > *bw)
215 return 1;
216 else
217 return 0;
220 /* Read the list of WINDOWs from (xwindow,atom), returning them
221 * in a (sorted) Array of Windows. On error, an empty array is
222 * returned.
223 * Free the array afterwards.
225 static GArray *get_window_list(Window xwindow, GdkAtom atom)
227 GArray *array;
228 Atom type;
229 int format;
230 gulong nitems;
231 gulong bytes_after;
232 unsigned char *data;
233 int err, result;
234 int i;
236 array = g_array_new(FALSE, FALSE, sizeof(Window));
238 gdk_error_trap_push();
239 type = None;
240 result = XGetWindowProperty(gdk_display,
241 xwindow,
242 gdk_x11_atom_to_xatom(atom),
243 0, G_MAXLONG,
244 False, XA_WINDOW, &type, &format, &nitems,
245 &bytes_after, &data);
246 err = gdk_error_trap_pop();
248 if (err != Success || result != Success)
249 return array;
251 if (type == XA_WINDOW)
253 for (i = 0; i < nitems; i++)
254 g_array_append_val(array, ((Window *) data)[i]);
256 if (array->len)
257 g_array_sort(array, wincmp);
260 XFree(data);
262 return array;
265 static guchar *get_str(IconWindow *win, GdkAtom atom)
267 Atom rtype;
268 int format;
269 gulong nitems;
270 gulong bytes_after;
271 unsigned char *data, *str = NULL;
272 int err, result;
274 gdk_error_trap_push();
276 result = XGetWindowProperty(gdk_display, win->xwindow,
277 gdk_x11_atom_to_xatom(atom),
278 0, G_MAXLONG, False,
279 AnyPropertyType,
280 &rtype, &format, &nitems,
281 &bytes_after, &data);
283 err = gdk_error_trap_pop();
285 if (err == Success && result == Success && data)
287 if (*data)
288 str = g_strdup(data);
289 XFree(data);
292 return str;
295 static void get_icon_name(IconWindow *win)
297 null_g_free(&win->text);
299 /* Keep this list in sync with window_filter */
301 win->text = get_str(win, xa__NET_WM_ICON_NAME);
302 if (!win->text)
303 win->text = get_str(win, xa__NET_WM_VISIBLE_NAME);
304 if (!win->text)
305 win->text = get_str(win, xa_WM_ICON_NAME);
306 if (!win->text)
307 win->text = get_str(win, xa_WM_NAME);
308 if (!win->text)
309 win->text = g_strdup(_("Window"));
312 static gboolean update_title(gpointer data)
314 IconWindow *win = (IconWindow *) data;
316 if (!win->widget)
317 return FALSE; /* No longer an icon */
319 get_icon_name(win);
320 wrapped_label_set_text(WRAPPED_LABEL(win->label), win->text);
322 win->timeout_update = 0;
324 return FALSE;
327 /* Call from within error_push/pop
328 * See wnck_window_is_in_viewport */
329 static gboolean within_viewport(Window xwindow)
331 int x, y;
332 unsigned int width, height, bw, depth;
333 Window root_window, child;
334 GdkRectangle win_rect;
336 XGetGeometry (gdk_display,
337 xwindow,
338 &root_window,
339 &x, &y, &width, &height, &bw, &depth);
340 XTranslateCoordinates (gdk_display,
341 xwindow,
342 root_window,
343 0, 0,
344 &x, &y, &child);
346 win_rect.x = x + cur_viewport.x;
347 win_rect.y = y + cur_viewport.y;
348 win_rect.width = width;
349 win_rect.height = height;
351 return gdk_rectangle_intersect (&cur_viewport, &win_rect, &win_rect);
354 /* Call from within error_push/pop */
355 static void window_check_status(IconWindow *win)
357 Atom type;
358 int format;
359 gulong nitems;
360 gulong bytes_after;
361 unsigned char *data;
362 Window transient_for;
363 gboolean iconic = FALSE;
365 if (XGetTransientForHint(gdk_display, win->xwindow, &transient_for) && transient_for)
366 iconic = FALSE;
367 else if (wm_supports_hidden && XGetWindowProperty(gdk_display, win->xwindow,
368 gdk_x11_atom_to_xatom(xa__NET_WM_STATE),
369 0, G_MAXLONG, False,
370 XA_ATOM,
371 &type, &format, &nitems,
372 &bytes_after, &data) == Success && data)
374 GdkAtom state;
375 int i;
377 for (i = 0; i < nitems; i++)
379 state = gdk_x11_xatom_to_atom(((Atom *) data)[i]);
380 if (state == xa__NET_WM_STATE_HIDDEN)
382 iconic = TRUE;
383 break;
386 XFree(data);
388 else if (XGetWindowProperty(gdk_display, win->xwindow,
389 gdk_x11_atom_to_xatom(xa_WM_STATE),
390 0, 1, False,
391 gdk_x11_atom_to_xatom(xa_WM_STATE),
392 &type, &format, &nitems,
393 &bytes_after, &data) == Success && data)
395 iconic = ((guint32 *) data)[0] == 3;
396 XFree(data);
398 else
399 iconic = FALSE;
401 /* Iconified windows on another desktops are not shown */
402 if (o_pinboard_tasklist_per_workspace.int_value &&
403 XGetWindowProperty(gdk_display, win->xwindow,
404 gdk_x11_atom_to_xatom(xa__NET_WM_DESKTOP),
405 0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
406 &bytes_after, &data) == Success && data)
408 guint32 desk_on = ((guint32 *) data)[0];
410 if ((desk_on != 0xffffffff) && ((desk_on != cur_desktop) ||
411 !within_viewport(win->xwindow)))
412 iconic = FALSE;
413 XFree(data);
416 if (win->iconified == iconic)
417 return;
419 win->iconified = iconic;
421 state_changed(win);
423 gdk_flush();
426 static void update_current_desktop(void)
428 unsigned char *data;
429 Atom type;
430 int format;
431 gulong nitems;
432 gulong bytes_after;
434 if (XGetWindowProperty(gdk_display, gdk_x11_get_default_root_xwindow(),
435 gdk_x11_atom_to_xatom(xa__NET_CURRENT_DESKTOP),
436 0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
437 &bytes_after, (unsigned char **)(&data)) == Success && data) {
438 cur_desktop = ((gint32*)data)[0];
439 XFree(data);
442 if (XGetWindowProperty(gdk_display, gdk_x11_get_default_root_xwindow(),
443 gdk_x11_atom_to_xatom(xa__NET_DESKTOP_VIEWPORT),
444 0, G_MAXLONG, False, XA_CARDINAL, &type, &format,
445 &nitems,
446 &bytes_after, (unsigned char **)(&data)) == Success &&
447 data) {
448 cur_viewport.x = ((gint32*)data)[0];
449 cur_viewport.y = ((gint32*)data)[1];
450 cur_viewport.width = screen_width;
451 cur_viewport.height = screen_height;
452 XFree(data);
456 /* Called for all events on all windows */
457 static GdkFilterReturn window_filter(GdkXEvent *xevent,
458 GdkEvent *event,
459 gpointer data)
461 XEvent *xev = (XEvent *) xevent;
462 IconWindow *w;
464 if (xev->type == PropertyNotify)
466 GdkAtom atom = gdk_x11_xatom_to_atom(xev->xproperty.atom);
467 Window win = ((XPropertyEvent *) xev)->window;
469 if (atom == xa_WM_STATE || atom == xa__NET_WM_STATE)
471 w = g_hash_table_lookup(known, &win);
473 if (w)
475 gdk_error_trap_push();
476 window_check_status(w);
477 if (gdk_error_trap_pop() != Success)
478 g_hash_table_remove(known, &win);
481 else if (atom == xa__NET_CURRENT_DESKTOP ||
482 atom == xa__NET_DESKTOP_VIEWPORT ||
483 atom == xa__NET_DESKTOP_GEOMETRY)
485 update_current_desktop();
486 tasklist_update(FALSE);
488 else if (atom == xa__NET_WM_ICON_NAME ||
489 atom == xa__NET_WM_VISIBLE_NAME ||
490 atom == xa_WM_ICON_NAME ||
491 atom == xa_WM_NAME)
493 /* Keep this list in sync with get_icon_name */
494 w = g_hash_table_lookup(known, &win);
496 if (w && w->widget && !w->timeout_update)
497 w->timeout_update = g_timeout_add(100,
498 update_title, w);
500 else if (atom == xa__NET_CLIENT_LIST)
501 tasklist_update(FALSE);
502 else if (atom == xa__NET_SUPPORTED)
503 update_supported();
506 return GDK_FILTER_CONTINUE;
509 /* Window has been added to list of managed windows */
510 static void add_window(Window win)
512 IconWindow *w;
514 /* g_print("[ New window %ld ]\n", (long) win); */
516 w = g_hash_table_lookup(known, &win);
518 if (!w)
520 XWindowAttributes attr;
522 gdk_error_trap_push();
524 XGetWindowAttributes(gdk_display, win, &attr);
526 if (gdk_error_trap_pop() != Success)
527 return;
528 gdk_error_trap_push();
530 XSelectInput(gdk_display, win, attr.your_event_mask |
531 PropertyChangeMask);
533 gdk_flush();
535 if (gdk_error_trap_pop() != Success)
536 return;
538 w = g_new(IconWindow, 1);
539 w->widget = NULL;
540 w->label = NULL;
541 w->text = NULL;
542 w->xwindow = win;
543 w->iconified = FALSE;
544 w->timeout_update = 0;
546 g_hash_table_insert(known, &w->xwindow, w);
549 gdk_error_trap_push();
551 window_check_status(w);
553 #if 0
554 set_iconify_pos(w);
555 #endif
557 if (gdk_error_trap_pop() != Success)
558 g_hash_table_remove(known, &win);
561 /* Window is no longer managed, but hasn't been destroyed yet */
562 static void remove_window(Window win)
564 IconWindow *w;
566 /* g_print("[ Remove window %ld ]\n", (long) win); */
568 w = g_hash_table_lookup(known, &win);
569 if (w)
571 if (w->iconified)
573 w->iconified = FALSE;
574 state_changed(w);
577 g_hash_table_remove(known, &win);
581 /* Make sure the window list is up-to-date. Call once to start, and then
582 * everytime _NET_CLIENT_LIST changes.
583 * If 'to_empty' is set them pretend all windows have disappeared.
585 static void tasklist_update(gboolean to_empty)
587 static GArray *old_mapping = NULL;
588 GArray *mapping = NULL;
589 int new_i, old_i;
591 if (!old_mapping)
593 old_mapping = g_array_new(FALSE, FALSE, sizeof(Window));
596 if (to_empty)
597 mapping = g_array_new(FALSE, FALSE, sizeof(Window));
598 else
599 mapping = get_window_list(gdk_x11_get_default_root_xwindow(),
600 gdk_atom_intern("_NET_CLIENT_LIST", FALSE));
602 new_i = 0;
603 old_i = 0;
604 while (new_i < mapping->len && old_i < old_mapping->len)
606 Window new = g_array_index(mapping, Window, new_i);
607 Window old = g_array_index(old_mapping, Window, old_i);
609 if (new == old)
611 add_window(new);
612 new_i++;
613 old_i++;
615 else if (new < old)
617 add_window(new);
618 new_i++;
620 else
622 remove_window(old);
623 old_i++;
626 while (new_i < mapping->len)
628 add_window(g_array_index(mapping, Window, new_i));
629 new_i++;
631 while (old_i < old_mapping->len)
633 remove_window(g_array_index(old_mapping, Window, old_i));
634 old_i++;
637 g_array_free(old_mapping, TRUE);
638 old_mapping = mapping;
641 /* Called when the user clicks on the button */
642 static void uniconify(IconWindow *win, guint32 timestamp)
644 XClientMessageEvent sev;
646 sev.type = ClientMessage;
647 sev.display = gdk_display;
648 sev.format = 32;
649 sev.window = win->xwindow;
650 sev.message_type = gdk_x11_atom_to_xatom(
651 gdk_atom_intern("_NET_ACTIVE_WINDOW", FALSE));
652 sev.data.l[0] = 2;
653 sev.data.l[1] = timestamp;
654 sev.data.l[2] = 0;
656 gdk_error_trap_push();
658 XSendEvent(gdk_display, DefaultRootWindow(gdk_display), False,
659 SubstructureNotifyMask | SubstructureRedirectMask,
660 (XEvent *) &sev);
661 XSync(gdk_display, False);
663 gdk_error_trap_pop();
666 static gint drag_start_x = -1;
667 static gint drag_start_y = -1;
668 static gboolean drag_started = FALSE;
669 static gint drag_off_x = -1;
670 static gint drag_off_y = -1;
672 static void icon_button_press(GtkWidget *widget,
673 GdkEventButton *event,
674 IconWindow *win)
676 if (event->button == 1)
678 drag_start_x = event->x_root;
679 drag_start_y = event->y_root;
680 drag_started = FALSE;
682 drag_off_x = event->x;
683 drag_off_y = event->y;
687 static gboolean icon_motion_notify(GtkWidget *widget,
688 GdkEventMotion *event,
689 IconWindow *win)
691 if (event->state & GDK_BUTTON1_MASK)
693 int dx = event->x_root - drag_start_x;
694 int dy = event->y_root - drag_start_y;
696 if (!drag_started)
698 if (abs(dx) < 5 && abs(dy) < 5)
699 return FALSE;
700 drag_started = TRUE;
703 fixed_move_fast(GTK_FIXED(win->widget->parent),
704 win->widget,
705 event->x_root - drag_off_x,
706 event->y_root - drag_off_y);
708 pinboard_moved_widget(win->widget, win->text,
709 event->x_root - drag_off_x,
710 event->y_root - drag_off_y);
713 return FALSE;
716 static void button_released(GtkWidget *widget, GdkEventButton *event,
717 IconWindow *win)
719 if (!drag_started && event->button == 1)
720 uniconify(win, event->time);
723 static GdkColormap* get_cmap(GdkPixmap *pixmap)
725 GdkColormap *cmap;
727 cmap = gdk_drawable_get_colormap(pixmap);
728 if (cmap)
729 g_object_ref(G_OBJECT(cmap));
731 if (cmap == NULL)
733 if (gdk_drawable_get_depth(pixmap) == 1)
735 /* Masks don't need colourmaps */
736 cmap = NULL;
738 else
740 /* Try system cmap */
741 cmap = gdk_colormap_get_system();
742 g_object_ref(G_OBJECT(cmap));
746 /* Be sure we aren't going to blow up due to visual mismatch */
747 if (cmap && (gdk_colormap_get_visual(cmap)->depth !=
748 gdk_drawable_get_depth(pixmap)))
749 cmap = NULL;
751 return cmap;
754 /* Copy a pixmap from the server to a client-side pixbuf */
755 static GdkPixbuf* pixbuf_from_pixmap(Pixmap xpixmap)
757 GdkDrawable *drawable;
758 GdkPixbuf *retval;
759 GdkColormap *cmap;
760 int width, height;
762 retval = NULL;
764 drawable = gdk_xid_table_lookup(xpixmap);
766 if (GDK_IS_DRAWABLE(drawable))
767 g_object_ref(G_OBJECT(drawable));
768 else
770 drawable = gdk_pixmap_foreign_new(xpixmap);
771 if (!GDK_IS_DRAWABLE(drawable))
772 return retval;
775 cmap = get_cmap(drawable);
777 /* GDK is supposed to do this but doesn't in GTK 2.0.2,
778 * fixed in 2.0.3
780 gdk_drawable_get_size(drawable, &width, &height);
782 retval = gdk_pixbuf_get_from_drawable(NULL, drawable, cmap,
783 0, 0, 0, 0, width, height);
785 if (cmap)
786 g_object_unref(G_OBJECT(cmap));
787 g_object_unref(G_OBJECT(drawable));
789 return retval;
792 /* Creates a new masked pixbuf from a non-masked pixbuf and a mask */
793 static GdkPixbuf* apply_mask(GdkPixbuf *pixbuf, GdkPixbuf *mask)
795 int w, h;
796 int i, j;
797 GdkPixbuf *with_alpha;
798 guchar *src;
799 guchar *dest;
800 int src_stride;
801 int dest_stride;
803 w = MIN(gdk_pixbuf_get_width(mask), gdk_pixbuf_get_width(pixbuf));
804 h = MIN(gdk_pixbuf_get_height(mask), gdk_pixbuf_get_height(pixbuf));
806 with_alpha = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
808 dest = gdk_pixbuf_get_pixels(with_alpha);
809 src = gdk_pixbuf_get_pixels(mask);
811 dest_stride = gdk_pixbuf_get_rowstride(with_alpha);
812 src_stride = gdk_pixbuf_get_rowstride(mask);
814 i = 0;
815 while (i < h)
817 j = 0;
818 while (j < w)
820 guchar *s = src + i * src_stride + j * 3;
821 guchar *d = dest + i * dest_stride + j * 4;
823 /* s[0] == s[1] == s[2], they are 255 if the bit was
824 * set, 0 otherwise
826 if (s[0] == 0)
827 d[3] = 0; /* transparent */
828 else
829 d[3] = 255; /* opaque */
831 ++j;
834 ++i;
837 return with_alpha;
840 #define BORDER_X 16
841 #define BORDER_Y 8
842 /* Take the icon that the iconified window has given us and modify it to make
843 * it obvious what it is.
845 static GdkPixbuf *apply_window_effect(GdkPixbuf *src)
847 GdkPixbuf *new;
848 int w, h;
850 w = gdk_pixbuf_get_width(src);
851 h = gdk_pixbuf_get_height(src);
853 new = gdk_pixbuf_new(gdk_pixbuf_get_colorspace(src), TRUE,
854 8, w + BORDER_X * 2, h + BORDER_Y * 2);
855 gdk_pixbuf_fill(new, 0x88888888);
857 gdk_pixbuf_composite(src, new,
858 BORDER_X, BORDER_Y, w, h,
859 BORDER_X, BORDER_Y, 1, 1,
860 GDK_INTERP_NEAREST, 255);
862 return new;
865 /* Return a suitable icon for this window. unref the result.
866 * Never returns NULL.
868 static GdkPixbuf *get_image_for(IconWindow *win)
870 static MaskedPixmap *default_icon = NULL;
871 Pixmap pixmap = None;
872 Pixmap mask = None;
873 XWMHints *hints;
874 GdkPixbuf *retval = NULL;
876 /* Try the pixmap and mask in the old WMHints... */
877 gdk_error_trap_push();
878 hints = XGetWMHints(gdk_display, win->xwindow);
880 if (hints)
882 if (hints->flags & IconPixmapHint)
883 pixmap = hints->icon_pixmap;
884 if (hints->flags & IconMaskHint)
885 mask = hints->icon_mask;
887 XFree(hints);
888 hints = NULL;
891 if (pixmap != None)
893 GdkPixbuf *mask_pb = NULL;
895 retval = pixbuf_from_pixmap(pixmap);
897 if (retval && mask != None)
898 mask_pb = pixbuf_from_pixmap(mask);
900 if (mask_pb)
902 GdkPixbuf *masked;
904 masked = apply_mask(retval, mask_pb);
905 g_object_unref(G_OBJECT(mask_pb));
907 if (masked)
909 g_object_unref(G_OBJECT(retval));
910 retval = masked;
915 gdk_flush();
917 gdk_error_trap_pop();
919 if (!retval)
921 if (!default_icon)
922 default_icon = load_pixmap("iconified");
924 retval = default_icon->pixbuf;
925 g_object_ref(retval);
928 /* Apply a special effect to make this look different from normal
929 * pinboard icons.
932 GdkPixbuf *old = retval;
933 GdkPixbuf *small;
934 small = scale_pixbuf(old, ICON_WIDTH, ICON_HEIGHT);
935 g_object_unref(old);
936 retval = apply_window_effect(small);
937 g_object_unref(small);
940 return retval;
943 /* Stop the button from highlighting */
944 static gint icon_expose(GtkWidget *widget, GdkEventExpose *event,
945 IconWindow *win)
947 static GtkWidgetClass *parent_class = NULL;
949 g_return_val_if_fail(win != NULL, TRUE);
950 g_return_val_if_fail(win->label != NULL, TRUE);
952 if (!parent_class)
954 gpointer c = ((GTypeInstance *) widget)->g_class;
955 parent_class = (GtkWidgetClass *) g_type_class_peek_parent(c);
958 draw_label_shadow((WrappedLabel *) win->label, event->region);
960 gdk_gc_set_clip_region(win->label->style->fg_gc[win->label->state],
961 event->region);
962 (parent_class->expose_event)(widget, event);
963 gdk_gc_set_clip_region(win->label->style->fg_gc[win->label->state],
964 NULL);
966 /* Stop the button effect */
967 return TRUE;
970 /* A window has been iconified -- display it on the screen */
971 static void show_icon(IconWindow *win)
973 GdkPixbuf *pixbuf;
974 GtkWidget *vbox;
976 g_return_if_fail(win->widget == NULL);
977 g_return_if_fail(win->label == NULL);
979 win->widget = gtk_button_new();
980 gtk_button_set_relief(GTK_BUTTON(win->widget), GTK_RELIEF_NONE);
981 g_signal_connect(win->widget, "expose-event",
982 G_CALLBACK(icon_expose), win);
983 vbox = gtk_vbox_new(FALSE, 0);
984 gtk_container_add(GTK_CONTAINER(win->widget), vbox);
986 pixbuf = get_image_for(win);
988 gtk_box_pack_start(GTK_BOX(vbox), simple_image_new(pixbuf),
989 FALSE, TRUE, 0);
990 g_object_unref(pixbuf);
992 win->label = wrapped_label_new(win->text, 180);
994 update_style(NULL, win, NULL);
996 gtk_box_pack_start(GTK_BOX(vbox), win->label, FALSE, TRUE, 0);
998 gtk_widget_add_events(win->widget,
999 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK);
1000 g_signal_connect(win->widget, "button-press-event",
1001 G_CALLBACK(icon_button_press), win);
1002 g_signal_connect(win->widget, "motion-notify-event",
1003 G_CALLBACK(icon_motion_notify), win);
1004 g_signal_connect(win->widget, "button-release-event",
1005 G_CALLBACK(button_released), win);
1007 gtk_widget_show_all(vbox); /* So the size comes out right */
1008 pinboard_add_widget(win->widget, win->text);
1009 gtk_widget_show(win->widget);
1012 /* A window has been destroyed/expanded -- remove its icon */
1013 static void hide_icon(IconWindow *win)
1015 g_return_if_fail(win->widget != NULL);
1017 gtk_widget_hide(win->widget); /* Stops flicker - stupid GtkFixed! */
1018 gtk_widget_destroy(win->widget);
1019 win->widget = NULL;
1020 win->label = NULL;
1023 static void state_changed(IconWindow *win)
1025 if (win->iconified)
1027 get_icon_name(win);
1028 show_icon(win);
1030 else
1031 hide_icon(win);
1034 #if 0
1035 /* Set the _NET_WM_ICON_GEOMETRY property, which indicates where this window
1036 * will be iconified to. Should be inside a push/pop.
1038 static void set_iconify_pos(IconWindow *win)
1040 gint32 data[4];
1042 data[0] = iconify_next_x;
1043 data[1] = iconify_next_y;
1044 data[2] = 100;
1045 data[3] = 32;
1047 XChangeProperty(gdk_display, win->xwindow,
1048 gdk_x11_atom_to_xatom(xa__NET_WM_ICON_GEOMETRY),
1049 XA_CARDINAL, 32, PropModeReplace, (guchar *) data, 4);
1051 #endif
1053 static void update_style(gpointer key, gpointer data, gpointer user_data)
1055 IconWindow *win = (IconWindow *) data;
1057 if (!win->widget)
1058 return;
1060 widget_modify_font(win->label, pinboard_font);
1061 gtk_widget_modify_fg(win->label, GTK_STATE_NORMAL, &pin_text_fg_col);
1062 gtk_widget_modify_bg(win->label, GTK_STATE_NORMAL, &pin_text_bg_col);
1063 gtk_widget_modify_fg(win->label, GTK_STATE_PRELIGHT, &pin_text_fg_col);
1064 gtk_widget_modify_bg(win->label, GTK_STATE_PRELIGHT, &pin_text_bg_col);
1067 /* Find out what the new window manager can do... */
1068 static void update_supported(void)
1070 Atom type;
1071 int format;
1072 gulong nitems;
1073 gulong bytes_after;
1074 unsigned char *data;
1075 int err, result;
1076 int i;
1077 gboolean old_supports_hidden = wm_supports_hidden;
1079 wm_supports_hidden = FALSE;
1081 gdk_error_trap_push();
1082 type = None;
1083 result = XGetWindowProperty(gdk_display,
1084 gdk_x11_get_default_root_xwindow(),
1085 gdk_x11_atom_to_xatom(xa__NET_SUPPORTED),
1086 0, G_MAXLONG,
1087 False, XA_ATOM, &type, &format, &nitems,
1088 &bytes_after, &data);
1089 err = gdk_error_trap_pop();
1091 if (err != Success || result != Success)
1092 goto out;
1094 for (i = 0; i < nitems; i++)
1096 GdkAtom atom = gdk_x11_xatom_to_atom(((Atom *) data)[i]);
1098 if (atom == xa__NET_WM_STATE_HIDDEN)
1099 wm_supports_hidden = TRUE;
1102 XFree(data);
1103 out:
1105 if (wm_supports_hidden != old_supports_hidden)
1107 tasklist_update(TRUE);
1108 tasklist_update(FALSE);