1 /*
2  * ROX-Filer, filer for the ROX desktop project
3  * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
4  *
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.
9  *
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.
14  *
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
18  */
19 
20 /* tasklist.c - code for tracking windows
21  *
22  * Loosly based on code in GNOME's libwnck.
23  */
24 
25 #include "config.h"
26 
27 #include <stdlib.h>
28 
29 #include <gtk/gtk.h>
30 #include <gdk/gdk.h>
31 #include <gdk/gdkx.h>
32 
33 #include <X11/Xlib.h>
34 #include <X11/Xatom.h>
35 
36 #include "global.h"
37 
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"
46 
47 /* There is one of these for each window controlled by the window
48  * manager (all tasks) in the _NET_CLIENT_LIST property.
49  */
50 typedef struct _IconWindow IconWindow;
51 
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 */
59 };
60 
61 /* If TRUE, only iconfied windows with _NET_WM_STATE_HIDDEN are really icons */
62 static gboolean wm_supports_hidden = FALSE;
63 
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_DESKTOP_GEOMETRY = GDK_NONE;
77 static GdkAtom xa__NET_DESKTOP_VIEWPORT = GDK_NONE;
78 
79 /* We have selected destroy and property events on every window in
80  * this table.
81  */
82 static GHashTable *known = NULL;	/* XID -> IconWindow */
83 
84 /* Static prototypes */
85 static void remove_window(Window win);
86 static void tasklist_update(gboolean to_empty);
87 static GdkFilterReturn window_filter(GdkXEvent *xevent,
88 				     GdkEvent *event,
89 				     gpointer data);
90 static guint xid_hash(XID *xid);
91 static gboolean xid_equal(XID *a, XID *b);
92 static void state_changed(IconWindow *win);
93 static void show_icon(IconWindow *win);
94 static void icon_win_free(IconWindow *win);
95 static void update_style(gpointer key, gpointer data, gpointer user_data);
96 static void update_supported(void);
97 static gboolean update_title(gpointer data);
98 static void update_current_desktop(void);
99 
100 /* remember what desktop number (and viewport) is currently displayed */
101 static int cur_desktop = 0;
102 static GdkRectangle cur_viewport;
103 
104 /****************************************************************
105  *			EXTERNAL INTERFACE			*
106  ****************************************************************/
107 
tasklist_set_active(gboolean active)108 void tasklist_set_active(gboolean active)
109 {
110 	static gboolean need_init = TRUE;
111 	static gboolean tasklist_active = FALSE;
112 
113 	if (active == tasklist_active)
114 	{
115 		if (o_pinboard_tasklist_per_workspace.has_changed && active)
116 			tasklist_update(FALSE);
117 		return;
118 	}
119 	tasklist_active = active;
120 
121 	if (need_init)
122 	{
123 		GdkWindow *root;
124 
125 		root = gdk_get_default_root_window();
126 
127 		xa__NET_SUPPORTED = gdk_atom_intern("_NET_SUPPORTED", FALSE);
128 		xa_WM_STATE = gdk_atom_intern("WM_STATE", FALSE);
129 		xa_WM_ICON_NAME = gdk_atom_intern("WM_ICON_NAME", FALSE);
130 		xa_WM_NAME = gdk_atom_intern("WM_NAME", FALSE);
131 		xa_UTF8_STRING = gdk_atom_intern("UTF8_STRING", FALSE);
132 		xa_TEXT = gdk_atom_intern("TEXT", FALSE);
133 		xa__NET_CLIENT_LIST =
134 				gdk_atom_intern("_NET_CLIENT_LIST", FALSE);
135 		xa__NET_WM_VISIBLE_NAME =
136 			gdk_atom_intern("_NET_WM_VISIBLE_NAME", FALSE);
137 		xa__NET_WM_ICON_NAME =
138 			gdk_atom_intern("_NET_WM_ICON_NAME", FALSE);
139 		xa__NET_WM_ICON_GEOMETRY =
140 			gdk_atom_intern("_NET_WM_ICON_GEOMETRY", FALSE);
141 		xa__NET_WM_STATE = gdk_atom_intern("_NET_WM_STATE", FALSE);
142 		xa__NET_WM_STATE_HIDDEN =
143 			gdk_atom_intern("_NET_WM_STATE_HIDDEN", FALSE);
144 		xa__NET_DESKTOP_GEOMETRY = gdk_atom_intern("_NET_DESKTOP_GEOMETRY", FALSE);
145 		xa__NET_DESKTOP_VIEWPORT = gdk_atom_intern("_NET_DESKTOP_VIEWPORT", FALSE);
146 
147 		known = g_hash_table_new_full((GHashFunc) xid_hash,
148 					      (GEqualFunc) xid_equal,
149 					      NULL,
150 					      (GDestroyNotify) icon_win_free);
151 		gdk_window_set_events(root, gdk_window_get_events(root) |
152 					GDK_PROPERTY_CHANGE_MASK);
153 		need_init = FALSE;
154 	}
155 
156 	if (active)
157 	{
158 		update_current_desktop();
159 		gdk_window_add_filter(NULL, window_filter, NULL);
160 		update_supported();
161 	}
162 	else
163 		gdk_window_remove_filter(NULL, window_filter, NULL);
164 
165 	tasklist_update(!active);
166 }
167 
168 /* User has changes the colours in the options box... */
tasklist_style_changed(void)169 void tasklist_style_changed(void)
170 {
171 	if (known)
172 		g_hash_table_foreach(known, update_style, NULL);
173 }
174 
175 /****************************************************************
176  *			INTERNAL FUNCTIONS			*
177  ****************************************************************/
178 
icon_win_free(IconWindow * win)179 static void icon_win_free(IconWindow *win)
180 {
181 	g_return_if_fail(win->widget == NULL);
182 	g_return_if_fail(win->label == NULL);
183 
184 	if (win->timeout_update)
185 		g_source_remove(win->timeout_update);
186 
187 	g_free(win->text);
188 	g_free(win);
189 }
190 
191 /* From gdk */
xid_hash(XID * xid)192 static guint xid_hash(XID *xid)
193 {
194 	return *xid;
195 }
196 
197 /* From gdk */
xid_equal(XID * a,XID * b)198 static gboolean xid_equal(XID *a, XID *b)
199 {
200 	return (*a == *b);
201 }
202 
wincmp(const void * a,const void * b)203 static int wincmp(const void *a, const void *b)
204 {
205 	const Window *aw = a;
206 	const Window *bw = b;
207 
208 	if (*aw < *bw)
209 		return -1;
210 	else if (*aw > *bw)
211 		return 1;
212 	else
213 		return 0;
214 }
215 
216 /* Read the list of WINDOWs from (xwindow,atom), returning them
217  * in a (sorted) Array of Windows. On error, an empty array is
218  * returned.
219  * Free the array afterwards.
220  */
get_window_list(Window xwindow,GdkAtom atom)221 static GArray *get_window_list(Window xwindow, GdkAtom atom)
222 {
223 	GArray *array;
224 	Atom type;
225 	int format;
226 	gulong nitems;
227 	gulong bytes_after;
228 	unsigned char *data;
229 	int err, result;
230 	int i;
231 
232 	array = g_array_new(FALSE, FALSE, sizeof(Window));
233 
234 	gdk_error_trap_push();
235 	type = None;
236 	result = XGetWindowProperty(gdk_display,
237 			xwindow,
238 			gdk_x11_atom_to_xatom(atom),
239 			0, G_MAXLONG,
240 			False, XA_WINDOW, &type, &format, &nitems,
241 			&bytes_after, &data);
242 	err = gdk_error_trap_pop();
243 
244 	if (err != Success || result != Success)
245 		return array;
246 
247 	if (type == XA_WINDOW)
248 	{
249 		for (i = 0; i < nitems; i++)
250 			g_array_append_val(array, ((Window *) data)[i]);
251 
252 		if (array->len)
253 			g_array_sort(array, wincmp);
254 	}
255 
256 	XFree(data);
257 
258 	return array;
259 }
260 
get_str(IconWindow * win,GdkAtom atom)261 static guchar *get_str(IconWindow *win, GdkAtom atom)
262 {
263 	Atom rtype;
264 	int format;
265 	gulong nitems;
266 	gulong bytes_after;
267 	unsigned char *data, *str = NULL;
268 	int err, result;
269 
270 	gdk_error_trap_push();
271 
272 	result = XGetWindowProperty(gdk_display, win->xwindow,
273 			gdk_x11_atom_to_xatom(atom),
274 			0, G_MAXLONG, False,
275 			AnyPropertyType,
276 			&rtype, &format, &nitems,
277 			&bytes_after, &data);
278 
279 	err = gdk_error_trap_pop();
280 
281 	if (err == Success && result == Success && data)
282 	{
283 		if (*data)
284 			str = g_strdup(data);
285 		XFree(data);
286 	}
287 
288 	return str;
289 }
290 
get_icon_name(IconWindow * win)291 static void get_icon_name(IconWindow *win)
292 {
293 	null_g_free(&win->text);
294 
295 	/* Keep this list in sync with window_filter */
296 
297 	win->text = get_str(win, xa__NET_WM_ICON_NAME);
298 	if (!win->text)
299 		win->text = get_str(win, xa__NET_WM_VISIBLE_NAME);
300 	if (!win->text)
301 		win->text = get_str(win, xa_WM_ICON_NAME);
302 	if (!win->text)
303 		win->text = get_str(win, xa_WM_NAME);
304 	if (!win->text)
305 		win->text = g_strdup(_("Window"));
306 }
307 
update_title(gpointer data)308 static gboolean update_title(gpointer data)
309 {
310 	IconWindow *win = (IconWindow *) data;
311 
312 	if (!win->widget)
313 		return FALSE;		/* No longer an icon */
314 
315 	get_icon_name(win);
316 	wrapped_label_set_text(WRAPPED_LABEL(win->label), win->text);
317 
318 	win->timeout_update = 0;
319 
320 	return FALSE;
321 }
322 
323 /* Call from within error_push/pop
324  * See wnck_window_is_in_viewport */
within_viewport(Window xwindow)325 static gboolean within_viewport(Window xwindow)
326 {
327   int x, y;
328   unsigned int width, height, bw, depth;
329   Window root_window, child;
330   GdkRectangle win_rect;
331 
332   XGetGeometry (gdk_display,
333                 xwindow,
334                 &root_window,
335                 &x, &y, &width, &height, &bw, &depth);
336   XTranslateCoordinates (gdk_display,
337                          xwindow,
338 			 root_window,
339                          0, 0,
340                          &x, &y, &child);
341 
342   win_rect.x = x + cur_viewport.x;
343   win_rect.y = y + cur_viewport.y;
344   win_rect.width = width;
345   win_rect.height = height;
346 
347   return gdk_rectangle_intersect (&cur_viewport, &win_rect, &win_rect);
348 }
349 
350 /* Call from within error_push/pop */
window_check_status(IconWindow * win)351 static void window_check_status(IconWindow *win)
352 {
353 	Atom type;
354 	int format;
355 	gulong nitems;
356 	gulong bytes_after;
357 	unsigned char *data;
358 	Window transient_for;
359 	gboolean iconic = FALSE;
360 
361 	if (XGetTransientForHint(gdk_display, win->xwindow, &transient_for) && transient_for)
362 		iconic = FALSE;
363 	else if (wm_supports_hidden && XGetWindowProperty(gdk_display, win->xwindow,
364 			gdk_x11_atom_to_xatom(xa__NET_WM_STATE),
365 			0, G_MAXLONG, False,
366 			XA_ATOM,
367 			&type, &format, &nitems,
368 			&bytes_after, &data) == Success && data)
369 	{
370 		GdkAtom state;
371 		int i;
372 
373 		for (i = 0; i < nitems; i++)
374 		{
375 			state = gdk_x11_xatom_to_atom(((Atom *) data)[i]);
376 			if (state == xa__NET_WM_STATE_HIDDEN)
377 			{
378 				iconic = TRUE;
379 				break;
380 			}
381 		}
382 		XFree(data);
383 	}
384 	else if (XGetWindowProperty(gdk_display, win->xwindow,
385 			gdk_x11_atom_to_xatom(xa_WM_STATE),
386 			0, 1, False,
387 			gdk_x11_atom_to_xatom(xa_WM_STATE),
388 			&type, &format, &nitems,
389 			&bytes_after, &data) == Success && data)
390 	{
391 		iconic = ((guint32 *) data)[0] == 3;
392 		XFree(data);
393 	}
394 	else
395 		iconic = FALSE;
396 
397 	/* Iconified windows on another desktops are not shown */
398 	if (o_pinboard_tasklist_per_workspace.int_value &&
399 	    XGetWindowProperty(gdk_display, win->xwindow,
400 			gdk_x11_atom_to_xatom(xa__NET_WM_DESKTOP),
401 			0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
402 			&bytes_after, &data) == Success && data)
403 	{
404 		guint32 desk_on = ((guint32 *) data)[0];
405 
406 		if ((desk_on != 0xffffffff) && ((desk_on != cur_desktop) ||
407 					       !within_viewport(win->xwindow)))
408 			iconic = FALSE;
409 		XFree(data);
410 	}
411 
412 	if (win->iconified == iconic)
413 		return;
414 
415 	win->iconified = iconic;
416 
417 	state_changed(win);
418 
419 	gdk_flush();
420 }
421 
update_current_desktop(void)422 static void update_current_desktop(void)
423 {
424 	unsigned char *data;
425 	Atom type;
426 	int format;
427 	gulong nitems;
428 	gulong bytes_after;
429 
430 	if (XGetWindowProperty(gdk_display, gdk_x11_get_default_root_xwindow(),
431 			gdk_x11_atom_to_xatom(xa__NET_CURRENT_DESKTOP),
432 			0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
433 			&bytes_after, (unsigned char **)(&data)) == Success && data) {
434 		cur_desktop = ((gint32*)data)[0];
435 		XFree(data);
436 	}
437 
438 	if (XGetWindowProperty(gdk_display, gdk_x11_get_default_root_xwindow(),
439 			gdk_x11_atom_to_xatom(xa__NET_DESKTOP_VIEWPORT),
440 			0, G_MAXLONG, False, XA_CARDINAL, &type, &format,
441 			&nitems,
442 			&bytes_after, (unsigned char **)(&data)) == Success &&
443 	    data) {
444 		cur_viewport.x = ((gint32*)data)[0];
445 		cur_viewport.y = ((gint32*)data)[1];
446 		cur_viewport.width = screen_width;
447 		cur_viewport.height = screen_height;
448 		XFree(data);
449 	}
450 }
451 
452 /* Called for all events on all windows */
window_filter(GdkXEvent * xevent,GdkEvent * event,gpointer data)453 static GdkFilterReturn window_filter(GdkXEvent *xevent,
454 				     GdkEvent *event,
455 				     gpointer data)
456 {
457 	XEvent *xev = (XEvent *) xevent;
458 	IconWindow *w;
459 
460 	if (xev->type == PropertyNotify)
461 	{
462 		GdkAtom atom = gdk_x11_xatom_to_atom(xev->xproperty.atom);
463 		Window win = ((XPropertyEvent *) xev)->window;
464 
465 		if (atom == xa_WM_STATE || atom == xa__NET_WM_STATE)
466 		{
467 			w = g_hash_table_lookup(known, &win);
468 
469 			if (w)
470 			{
471 				gdk_error_trap_push();
472 				window_check_status(w);
473 				if (gdk_error_trap_pop() != Success)
474 					g_hash_table_remove(known, &win);
475 			}
476 		}
477 		else if (atom == xa__NET_CURRENT_DESKTOP ||
478 			 atom == xa__NET_DESKTOP_VIEWPORT ||
479 			 atom == xa__NET_DESKTOP_GEOMETRY)
480 		{
481 			update_current_desktop();
482 			tasklist_update(FALSE);
483 		}
484 		else if (atom == xa__NET_WM_ICON_NAME ||
485 			 atom == xa__NET_WM_VISIBLE_NAME ||
486 			 atom == xa_WM_ICON_NAME ||
487 			 atom == xa_WM_NAME)
488 		{
489 			/* Keep this list in sync with get_icon_name */
490 			w = g_hash_table_lookup(known, &win);
491 
492 			if (w && w->widget && !w->timeout_update)
493 				w->timeout_update = g_timeout_add(100,
494 							update_title, w);
495 		}
496 		else if (atom == xa__NET_CLIENT_LIST)
497 			tasklist_update(FALSE);
498 		else if (atom == xa__NET_SUPPORTED)
499 			update_supported();
500 	}
501 
502 	return GDK_FILTER_CONTINUE;
503 }
504 
505 /* Window has been added to list of managed windows */
add_window(Window win)506 static void add_window(Window win)
507 {
508 	IconWindow *w;
509 
510 	/* g_print("[ New window %ld ]\n", (long) win); */
511 
512 	w = g_hash_table_lookup(known, &win);
513 
514 	if (!w)
515 	{
516 		XWindowAttributes attr;
517 
518 		gdk_error_trap_push();
519 
520 		XGetWindowAttributes(gdk_display, win, &attr);
521 
522 		if (gdk_error_trap_pop() != Success)
523 			return;
524 		gdk_error_trap_push();
525 
526 		XSelectInput(gdk_display, win, attr.your_event_mask |
527 			PropertyChangeMask);
528 
529 		gdk_flush();
530 
531 		if (gdk_error_trap_pop() != Success)
532 			return;
533 
534 		w = g_new(IconWindow, 1);
535 		w->widget = NULL;
536 		w->label = NULL;
537 		w->text = NULL;
538 		w->xwindow = win;
539 		w->iconified = FALSE;
540 		w->timeout_update = 0;
541 
542 		g_hash_table_insert(known, &w->xwindow, w);
543 	}
544 
545 	gdk_error_trap_push();
546 
547 	window_check_status(w);
548 
549 #if 0
550 	set_iconify_pos(w);
551 #endif
552 
553 	if (gdk_error_trap_pop() != Success)
554 		g_hash_table_remove(known, &win);
555 }
556 
557 /* Window is no longer managed, but hasn't been destroyed yet */
remove_window(Window win)558 static void remove_window(Window win)
559 {
560 	IconWindow *w;
561 
562 	/* g_print("[ Remove window %ld ]\n", (long) win); */
563 
564 	w = g_hash_table_lookup(known, &win);
565 	if (w)
566 	{
567 		if (w->iconified)
568 		{
569 			w->iconified = FALSE;
570 			state_changed(w);
571 		}
572 
573 		g_hash_table_remove(known, &win);
574 	}
575 }
576 
577 /* Make sure the window list is up-to-date. Call once to start, and then
578  * everytime _NET_CLIENT_LIST changes.
579  * If 'to_empty' is set them pretend all windows have disappeared.
580  */
tasklist_update(gboolean to_empty)581 static void tasklist_update(gboolean to_empty)
582 {
583 	static GArray *old_mapping = NULL;
584 	GArray *mapping = NULL;
585 	int new_i, old_i;
586 
587 	if (!old_mapping)
588 	{
589 		old_mapping = g_array_new(FALSE, FALSE, sizeof(Window));
590 	}
591 
592 	if (to_empty)
593 		mapping = g_array_new(FALSE, FALSE, sizeof(Window));
594 	else
595 		mapping = get_window_list(gdk_x11_get_default_root_xwindow(),
596 				gdk_atom_intern("_NET_CLIENT_LIST", FALSE));
597 
598 	new_i = 0;
599 	old_i = 0;
600 	while (new_i < mapping->len && old_i < old_mapping->len)
601 	{
602 		Window new = g_array_index(mapping, Window, new_i);
603 		Window old = g_array_index(old_mapping, Window, old_i);
604 
605 		if (new == old)
606 		{
607 			add_window(new);
608 			new_i++;
609 			old_i++;
610 		}
611 		else if (new < old)
612 		{
613 			add_window(new);
614 			new_i++;
615 		}
616 		else
617 		{
618 			remove_window(old);
619 			old_i++;
620 		}
621 	}
622 	while (new_i < mapping->len)
623 	{
624 		add_window(g_array_index(mapping, Window, new_i));
625 		new_i++;
626 	}
627 	while (old_i < old_mapping->len)
628 	{
629 		remove_window(g_array_index(old_mapping, Window, old_i));
630 		old_i++;
631 	}
632 
633 	g_array_free(old_mapping, TRUE);
634 	old_mapping = mapping;
635 }
636 
637 /* Called when the user clicks on the button */
uniconify(IconWindow * win,guint32 timestamp)638 static void uniconify(IconWindow *win, guint32 timestamp)
639 {
640 	XClientMessageEvent sev;
641 
642 	sev.type = ClientMessage;
643 	sev.display = gdk_display;
644 	sev.format = 32;
645 	sev.window = win->xwindow;
646 	sev.message_type = gdk_x11_atom_to_xatom(
647 			gdk_atom_intern("_NET_ACTIVE_WINDOW", FALSE));
648 	sev.data.l[0] = 2;
649 	sev.data.l[1] = timestamp;
650 	sev.data.l[2] = 0;
651 
652 	gdk_error_trap_push();
653 
654 	XSendEvent(gdk_display, DefaultRootWindow(gdk_display), False,
655 			SubstructureNotifyMask | SubstructureRedirectMask,
656 			(XEvent *) &sev);
657 	XSync(gdk_display, False);
658 
659 	gdk_error_trap_pop();
660 }
661 
662 static gint drag_start_x = -1;
663 static gint drag_start_y = -1;
664 static gboolean drag_started = FALSE;
665 static gint drag_off_x = -1;
666 static gint drag_off_y = -1;
667 
icon_button_press(GtkWidget * widget,GdkEventButton * event,IconWindow * win)668 static void icon_button_press(GtkWidget *widget,
669 			      GdkEventButton *event,
670 			      IconWindow *win)
671 {
672 	if (event->button == 1)
673 	{
674 		drag_start_x = event->x_root;
675 		drag_start_y = event->y_root;
676 		drag_started = FALSE;
677 
678 		drag_off_x = event->x;
679 		drag_off_y = event->y;
680 	}
681 }
682 
icon_motion_notify(GtkWidget * widget,GdkEventMotion * event,IconWindow * win)683 static gboolean icon_motion_notify(GtkWidget *widget,
684 			       GdkEventMotion *event,
685 			       IconWindow *win)
686 {
687 	if (event->state & GDK_BUTTON1_MASK)
688 	{
689 		int dx = event->x_root - drag_start_x;
690 		int dy = event->y_root - drag_start_y;
691 
692 		if (!drag_started)
693 		{
694 			if (abs(dx) < 5 && abs(dy) < 5)
695 				return FALSE;
696 			drag_started = TRUE;
697 		}
698 
699 		fixed_move_fast(GTK_FIXED(win->widget->parent),
700 				win->widget,
701 				event->x_root - drag_off_x,
702 				event->y_root - drag_off_y);
703 
704 		pinboard_moved_widget(win->widget, win->text,
705 				      event->x_root - drag_off_x,
706 				      event->y_root - drag_off_y);
707 	}
708 
709 	return FALSE;
710 }
711 
button_released(GtkWidget * widget,GdkEventButton * event,IconWindow * win)712 static void button_released(GtkWidget *widget, GdkEventButton *event,
713 		IconWindow *win)
714 {
715 	if (!drag_started && event->button == 1)
716 		uniconify(win, event->time);
717 }
718 
get_cmap(GdkPixmap * pixmap)719 static GdkColormap* get_cmap(GdkPixmap *pixmap)
720 {
721 	GdkColormap *cmap;
722 
723 	cmap = gdk_drawable_get_colormap(pixmap);
724 	if (cmap)
725 		g_object_ref(G_OBJECT(cmap));
726 
727 	if (cmap == NULL)
728 	{
729 		if (gdk_drawable_get_depth(pixmap) == 1)
730 		{
731 			/* Masks don't need colourmaps */
732 			cmap = NULL;
733 		}
734 		else
735 		{
736 			/* Try system cmap */
737 			cmap = gdk_colormap_get_system();
738 			g_object_ref(G_OBJECT(cmap));
739 		}
740 	}
741 
742 	/* Be sure we aren't going to blow up due to visual mismatch */
743 	if (cmap && (gdk_colormap_get_visual(cmap)->depth !=
744 			 gdk_drawable_get_depth(pixmap)))
745 		cmap = NULL;
746 
747 	return cmap;
748 }
749 
750 /* Copy a pixmap from the server to a client-side pixbuf */
pixbuf_from_pixmap(Pixmap xpixmap)751 static GdkPixbuf* pixbuf_from_pixmap(Pixmap xpixmap)
752 {
753 	GdkDrawable *drawable;
754 	GdkPixbuf *retval;
755 	GdkColormap *cmap;
756 	int width, height;
757 
758 	retval = NULL;
759 
760 	drawable = gdk_xid_table_lookup(xpixmap);
761 
762 	if (GDK_IS_DRAWABLE(drawable))
763 		g_object_ref(G_OBJECT(drawable));
764 	else
765 	{
766 		drawable = gdk_pixmap_foreign_new(xpixmap);
767 		if (!GDK_IS_DRAWABLE(drawable))
768 			return retval;
769 	}
770 
771 	cmap = get_cmap(drawable);
772 
773 	/* GDK is supposed to do this but doesn't in GTK 2.0.2,
774 	 * fixed in 2.0.3
775 	 */
776 	gdk_drawable_get_size(drawable, &width, &height);
777 
778 	retval = gdk_pixbuf_get_from_drawable(NULL, drawable, cmap,
779 						0, 0, 0, 0, width, height);
780 
781 	if (cmap)
782 		g_object_unref(G_OBJECT(cmap));
783 	g_object_unref(G_OBJECT(drawable));
784 
785 	return retval;
786 }
787 
788 /* Creates a new masked pixbuf from a non-masked pixbuf and a mask */
apply_mask(GdkPixbuf * pixbuf,GdkPixbuf * mask)789 static GdkPixbuf* apply_mask(GdkPixbuf *pixbuf, GdkPixbuf *mask)
790 {
791 	int w, h;
792 	int i, j;
793 	GdkPixbuf *with_alpha;
794 	guchar *src;
795 	guchar *dest;
796 	int src_stride;
797 	int dest_stride;
798 
799 	w = MIN(gdk_pixbuf_get_width(mask), gdk_pixbuf_get_width(pixbuf));
800 	h = MIN(gdk_pixbuf_get_height(mask), gdk_pixbuf_get_height(pixbuf));
801 
802 	with_alpha = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
803 
804 	dest = gdk_pixbuf_get_pixels(with_alpha);
805 	src = gdk_pixbuf_get_pixels(mask);
806 
807 	dest_stride = gdk_pixbuf_get_rowstride(with_alpha);
808 	src_stride = gdk_pixbuf_get_rowstride(mask);
809 
810 	i = 0;
811 	while (i < h)
812 	{
813 		j = 0;
814 		while (j < w)
815 		{
816 			guchar *s = src + i * src_stride + j * 3;
817 			guchar *d = dest + i * dest_stride + j * 4;
818 
819 			/* s[0] == s[1] == s[2], they are 255 if the bit was
820 			 * set, 0 otherwise
821 			 */
822 			if (s[0] == 0)
823 				d[3] = 0;   /* transparent */
824 			else
825 				d[3] = 255; /* opaque */
826 
827 			++j;
828 		}
829 
830 		++i;
831 	}
832 
833 	return with_alpha;
834 }
835 
836 #define BORDER_X 16
837 #define BORDER_Y 8
838 /* Take the icon that the iconified window has given us and modify it to make
839  * it obvious what it is.
840  */
apply_window_effect(GdkPixbuf * src)841 static GdkPixbuf *apply_window_effect(GdkPixbuf *src)
842 {
843 	GdkPixbuf *new;
844 	int w, h;
845 
846 	w = gdk_pixbuf_get_width(src);
847 	h = gdk_pixbuf_get_height(src);
848 
849 	new = gdk_pixbuf_new(gdk_pixbuf_get_colorspace(src), TRUE,
850 			8, w + BORDER_X * 2, h + BORDER_Y * 2);
851 	gdk_pixbuf_fill(new, 0x88888888);
852 
853 	gdk_pixbuf_composite(src, new,
854 			BORDER_X, BORDER_Y, w, h,
855 			BORDER_X, BORDER_Y, 1, 1,
856 			GDK_INTERP_NEAREST, 255);
857 
858 	return new;
859 }
860 
861 /* Return a suitable icon for this window. unref the result.
862  * Never returns NULL.
863  */
get_image_for(IconWindow * win)864 static GdkPixbuf *get_image_for(IconWindow *win)
865 {
866 	static MaskedPixmap *default_icon = NULL;
867 	Pixmap pixmap = None;
868 	Pixmap mask = None;
869 	XWMHints *hints;
870 	GdkPixbuf *retval = NULL;
871 
872 	/* Try the pixmap and mask in the old WMHints... */
873 	gdk_error_trap_push();
874 	hints = XGetWMHints(gdk_display, win->xwindow);
875 
876 	if (hints)
877 	{
878 		if (hints->flags & IconPixmapHint)
879 			pixmap = hints->icon_pixmap;
880 		if (hints->flags & IconMaskHint)
881 			mask = hints->icon_mask;
882 
883 		XFree(hints);
884 		hints = NULL;
885 	}
886 
887 	if (pixmap != None)
888 	{
889 		GdkPixbuf *mask_pb = NULL;
890 
891 		retval = pixbuf_from_pixmap(pixmap);
892 
893 		if (retval && mask != None)
894 			mask_pb = pixbuf_from_pixmap(mask);
895 
896 		if (mask_pb)
897 		{
898 			GdkPixbuf *masked;
899 
900 			masked = apply_mask(retval, mask_pb);
901 			g_object_unref(G_OBJECT(mask_pb));
902 
903 			if (masked)
904 			{
905 				g_object_unref(G_OBJECT(retval));
906 				retval = masked;
907 			}
908 		}
909 	}
910 
911 	gdk_flush();
912 
913 	gdk_error_trap_pop();
914 
915 	if (!retval)
916 	{
917 		if (!default_icon)
918 			default_icon = load_pixmap("iconified");
919 
920 		retval = default_icon->pixbuf;
921 		g_object_ref(retval);
922 	}
923 
924 	/* Apply a special effect to make this look different from normal
925 	 * pinboard icons.
926 	 */
927 	{
928 		GdkPixbuf *old = retval;
929 		GdkPixbuf *small;
930 		small = scale_pixbuf(old, ICON_WIDTH, ICON_HEIGHT);
931 		g_object_unref(old);
932 		retval = apply_window_effect(small);
933 		g_object_unref(small);
934 	}
935 
936 	return retval;
937 }
938 
939 /* Stop the button from highlighting */
icon_expose(GtkWidget * widget,GdkEventExpose * event,IconWindow * win)940 static gint icon_expose(GtkWidget *widget, GdkEventExpose *event,
941 			IconWindow *win)
942 {
943 	static GtkWidgetClass *parent_class = NULL;
944 
945 	g_return_val_if_fail(win != NULL, TRUE);
946 	g_return_val_if_fail(win->label != NULL, TRUE);
947 
948 	if (!parent_class)
949 	{
950 		gpointer c = ((GTypeInstance *) widget)->g_class;
951 		parent_class = (GtkWidgetClass *) g_type_class_peek_parent(c);
952 	}
953 
954 	draw_label_shadow((WrappedLabel *) win->label, event->region);
955 
956 	gdk_gc_set_clip_region(win->label->style->fg_gc[win->label->state],
957 				event->region);
958 	(parent_class->expose_event)(widget, event);
959 	gdk_gc_set_clip_region(win->label->style->fg_gc[win->label->state],
960 				NULL);
961 
962 	/* Stop the button effect */
963 	return TRUE;
964 }
965 
966 /* A window has been iconified -- display it on the screen */
show_icon(IconWindow * win)967 static void show_icon(IconWindow *win)
968 {
969 	GdkPixbuf *pixbuf;
970 	GtkWidget *vbox;
971 
972 	g_return_if_fail(win->widget == NULL);
973 	g_return_if_fail(win->label == NULL);
974 
975 	win->widget = gtk_button_new();
976 	gtk_button_set_relief(GTK_BUTTON(win->widget), GTK_RELIEF_NONE);
977 	g_signal_connect(win->widget, "expose-event",
978 			G_CALLBACK(icon_expose), win);
979 	vbox = gtk_vbox_new(FALSE, 0);
980 	gtk_container_add(GTK_CONTAINER(win->widget), vbox);
981 
982 	pixbuf = get_image_for(win);
983 
984 	gtk_box_pack_start(GTK_BOX(vbox), simple_image_new(pixbuf),
985 			  FALSE, TRUE, 0);
986 	g_object_unref(pixbuf);
987 
988 	win->label = wrapped_label_new(win->text, 180);
989 
990 	update_style(NULL, win, NULL);
991 
992 	gtk_box_pack_start(GTK_BOX(vbox), win->label, FALSE, TRUE, 0);
993 
994 	gtk_widget_add_events(win->widget,
995 			GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK);
996 	g_signal_connect(win->widget, "button-press-event",
997 			G_CALLBACK(icon_button_press), win);
998 	g_signal_connect(win->widget, "motion-notify-event",
999 			G_CALLBACK(icon_motion_notify), win);
1000 	g_signal_connect(win->widget, "button-release-event",
1001 			G_CALLBACK(button_released), win);
1002 
1003 	gtk_widget_show_all(vbox);	/* So the size comes out right */
1004 	pinboard_add_widget(win->widget, win->text);
1005 	gtk_widget_show(win->widget);
1006 }
1007 
1008 /* A window has been destroyed/expanded -- remove its icon */
hide_icon(IconWindow * win)1009 static void hide_icon(IconWindow *win)
1010 {
1011 	g_return_if_fail(win->widget != NULL);
1012 
1013 	gtk_widget_hide(win->widget);	/* Stops flicker - stupid GtkFixed! */
1014 	gtk_widget_destroy(win->widget);
1015 	win->widget = NULL;
1016 	win->label = NULL;
1017 }
1018 
state_changed(IconWindow * win)1019 static void state_changed(IconWindow *win)
1020 {
1021 	if (win->iconified)
1022 	{
1023 		get_icon_name(win);
1024 		show_icon(win);
1025 	}
1026 	else
1027 		hide_icon(win);
1028 }
1029 
1030 #if 0
1031 /* Set the _NET_WM_ICON_GEOMETRY property, which indicates where this window
1032  * will be iconified to. Should be inside a push/pop.
1033  */
1034 static void set_iconify_pos(IconWindow *win)
1035 {
1036 	gint32 data[4];
1037 
1038 	data[0] = iconify_next_x;
1039 	data[1] = iconify_next_y;
1040 	data[2] = 100;
1041 	data[3] = 32;
1042 
1043 	XChangeProperty(gdk_display, win->xwindow,
1044 			gdk_x11_atom_to_xatom(xa__NET_WM_ICON_GEOMETRY),
1045 			XA_CARDINAL, 32, PropModeReplace, (guchar *) data, 4);
1046 }
1047 #endif
1048 
update_style(gpointer key,gpointer data,gpointer user_data)1049 static void update_style(gpointer key, gpointer data, gpointer user_data)
1050 {
1051 	IconWindow *win = (IconWindow *) data;
1052 
1053 	if (!win->widget)
1054 		return;
1055 
1056 	widget_modify_font(win->label, pinboard_font);
1057 	gtk_widget_modify_fg(win->label, GTK_STATE_NORMAL, &pin_text_fg_col);
1058 	gtk_widget_modify_bg(win->label, GTK_STATE_NORMAL, &pin_text_bg_col);
1059 	gtk_widget_modify_fg(win->label, GTK_STATE_PRELIGHT, &pin_text_fg_col);
1060 	gtk_widget_modify_bg(win->label, GTK_STATE_PRELIGHT, &pin_text_bg_col);
1061 }
1062 
1063 /* Find out what the new window manager can do... */
update_supported(void)1064 static void update_supported(void)
1065 {
1066 	Atom type;
1067 	int format;
1068 	gulong nitems;
1069 	gulong bytes_after;
1070 	unsigned char *data;
1071 	int err, result;
1072 	int i;
1073 	gboolean old_supports_hidden = wm_supports_hidden;
1074 
1075 	wm_supports_hidden = FALSE;
1076 
1077 	gdk_error_trap_push();
1078 	type = None;
1079 	result = XGetWindowProperty(gdk_display,
1080 			gdk_x11_get_default_root_xwindow(),
1081 			gdk_x11_atom_to_xatom(xa__NET_SUPPORTED),
1082 			0, G_MAXLONG,
1083 			False, XA_ATOM, &type, &format, &nitems,
1084 			&bytes_after, &data);
1085 	err = gdk_error_trap_pop();
1086 
1087 	if (err != Success || result != Success)
1088 		goto out;
1089 
1090 	for (i = 0; i < nitems; i++)
1091 	{
1092 		GdkAtom atom = gdk_x11_xatom_to_atom(((Atom *) data)[i]);
1093 
1094 		if (atom == xa__NET_WM_STATE_HIDDEN)
1095 			wm_supports_hidden = TRUE;
1096 	}
1097 
1098 	XFree(data);
1099 out:
1100 
1101 	if (wm_supports_hidden != old_supports_hidden)
1102 	{
1103 		tasklist_update(TRUE);
1104 		tasklist_update(FALSE);
1105 	}
1106 }
1107