r1996: Cope slightly better with invalid filenames in various places (reported by
[rox-filer.git] / ROX-Filer / src / tasklist.c
blob40dc7cef6f094443eb99840527792750fefd9343
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* tasklist.c - code for tracking windows
24 * Loosly based on code in GNOME's libwnck.
27 #include "config.h"
29 #include <stdlib.h>
31 #include <gtk/gtk.h>
32 #include <gdk/gdk.h>
33 #include <gdk/gdkx.h>
35 #include <X11/Xlib.h>
36 #include <X11/Xatom.h>
38 #include "global.h"
40 #include "tasklist.h"
41 #include "wrapped.h"
42 #include "options.h"
43 #include "gui_support.h"
44 #include "main.h"
45 #include "pinboard.h"
46 #include "pixmaps.h"
47 #include "support.h"
49 /* There is one of these for each window controlled by the window
50 * manager (all tasks) in the _NET_CLIENT_LIST property.
52 typedef struct _IconWindow IconWindow;
54 struct _IconWindow {
55 GtkWidget *widget; /* Widget used for icon when iconified */
56 GtkWidget *label;
57 gchar *text;
58 Window xwindow;
59 gboolean iconified;
62 static GdkAtom xa_WM_STATE = GDK_NONE;
63 static GdkAtom xa_WM_NAME = GDK_NONE;
64 static GdkAtom xa_WM_ICON_NAME = GDK_NONE;
65 static GdkAtom xa_UTF8_STRING = GDK_NONE;
66 static GdkAtom xa_TEXT = GDK_NONE;
67 static GdkAtom xa__NET_WM_VISIBLE_NAME = GDK_NONE;
68 static GdkAtom xa__NET_WM_ICON_NAME = GDK_NONE;
69 static GdkAtom xa__NET_CLIENT_LIST = GDK_NONE;
70 static GdkAtom xa__NET_WM_ICON_GEOMETRY = GDK_NONE;
72 /* We have selected destroy and property events on every window in
73 * this table.
75 static GHashTable *known = NULL; /* XID -> IconWindow */
77 /* Static prototypes */
78 static void remove_window(Window win);
79 static void tasklist_update(gboolean to_empty);
80 static GdkFilterReturn window_filter(GdkXEvent *xevent,
81 GdkEvent *event,
82 gpointer data);
83 static guint xid_hash(XID *xid);
84 static gboolean xid_equal(XID *a, XID *b);
85 static void state_changed(IconWindow *win);
86 static void show_icon(IconWindow *win);
87 static void icon_win_free(IconWindow *win);
88 static void update_style(gpointer key, gpointer data, gpointer user_data);
90 /****************************************************************
91 * EXTERNAL INTERFACE *
92 ****************************************************************/
94 void tasklist_set_active(gboolean active)
96 static gboolean need_init = TRUE;
97 static gboolean tasklist_active = FALSE;
99 if (active == tasklist_active)
100 return;
101 tasklist_active = active;
103 if (need_init)
105 GdkWindow *root;
107 root = gdk_get_default_root_window();
109 xa_WM_STATE = gdk_atom_intern("WM_STATE", FALSE);
110 xa_WM_ICON_NAME = gdk_atom_intern("WM_ICON_NAME", FALSE);
111 xa_WM_NAME = gdk_atom_intern("WM_NAME", FALSE);
112 xa_UTF8_STRING = gdk_atom_intern("UTF8_STRING", FALSE);
113 xa_TEXT = gdk_atom_intern("TEXT", FALSE);
114 xa__NET_CLIENT_LIST =
115 gdk_atom_intern("_NET_CLIENT_LIST", FALSE);
116 xa__NET_WM_VISIBLE_NAME =
117 gdk_atom_intern("_NET_WM_VISIBLE_NAME", FALSE);
118 xa__NET_WM_ICON_NAME =
119 gdk_atom_intern("_NET_WM_ICON_NAME", FALSE);
120 xa__NET_WM_ICON_GEOMETRY =
121 gdk_atom_intern("_NET_WM_ICON_GEOMETRY", FALSE);
123 known = g_hash_table_new_full((GHashFunc) xid_hash,
124 (GEqualFunc) xid_equal,
125 NULL,
126 (GDestroyNotify) icon_win_free);
127 gdk_window_set_events(root, gdk_window_get_events(root) |
128 GDK_PROPERTY_CHANGE_MASK);
129 need_init = FALSE;
132 if (active)
133 gdk_window_add_filter(NULL, window_filter, NULL);
134 else
135 gdk_window_remove_filter(NULL, window_filter, NULL);
137 tasklist_update(!active);
140 /* User has changes the colours in the options box... */
141 void tasklist_style_changed(void)
143 if (known)
144 g_hash_table_foreach(known, update_style, NULL);
147 /****************************************************************
148 * INTERNAL FUNCTIONS *
149 ****************************************************************/
151 static void icon_win_free(IconWindow *win)
153 g_return_if_fail(win->widget == NULL);
154 g_return_if_fail(win->label == NULL);
156 g_free(win->text);
157 g_free(win);
160 /* From gdk */
161 static guint xid_hash(XID *xid)
163 return *xid;
166 /* From gdk */
167 static gboolean xid_equal(XID *a, XID *b)
169 return (*a == *b);
172 static int wincmp(const void *a, const void *b)
174 const Window *aw = a;
175 const Window *bw = b;
177 if (*aw < *bw)
178 return -1;
179 else if (*aw > *bw)
180 return 1;
181 else
182 return 0;
185 /* Read the list of WINDOWs from (xwindow,atom), returning them
186 * in a (sorted) Array of Windows. On error, an empty array is
187 * returned.
188 * Free the array afterwards.
190 static GArray *get_window_list(Window xwindow, GdkAtom atom)
192 GArray *array;
193 Atom type;
194 int format;
195 gulong nitems;
196 gulong bytes_after;
197 Window *data;
198 int err, result;
199 int i;
201 array = g_array_new(FALSE, FALSE, sizeof(Window));
203 gdk_error_trap_push();
204 type = None;
205 result = XGetWindowProperty(gdk_display,
206 xwindow,
207 gdk_x11_atom_to_xatom(atom),
208 0, G_MAXLONG,
209 False, XA_WINDOW, &type, &format, &nitems,
210 &bytes_after, (guchar **)&data);
211 err = gdk_error_trap_pop();
213 if (err != Success || result != Success)
214 return array;
216 if (type == XA_WINDOW)
218 for (i = 0; i < nitems; i++)
219 g_array_append_val(array, data[i]);
221 if (array->len)
222 g_array_sort(array, wincmp);
225 XFree(data);
227 return array;
230 static gchar *get_str(IconWindow *win, GdkAtom atom)
232 Atom rtype;
233 int format;
234 gulong nitems;
235 gulong bytes_after;
236 char *data, *str = NULL;
238 if (XGetWindowProperty(gdk_display, win->xwindow,
239 gdk_x11_atom_to_xatom(atom),
240 0, G_MAXLONG, False,
241 AnyPropertyType,
242 &rtype, &format, &nitems,
243 &bytes_after, (guchar **) &data) == Success && data)
245 if (*data)
246 str = g_strdup(data);
247 XFree(data);
250 return str;
253 static void get_icon_name(IconWindow *win)
255 null_g_free(&win->text);
257 win->text = get_str(win, xa__NET_WM_ICON_NAME);
258 if (!win->text)
259 win->text = get_str(win, xa__NET_WM_VISIBLE_NAME);
260 if (!win->text)
261 win->text = get_str(win, xa_WM_ICON_NAME);
262 if (!win->text)
263 win->text = get_str(win, xa_WM_NAME);
264 if (!win->text)
265 win->text = g_strdup(_("Window"));
268 /* Call from within error_push/pop */
269 static void window_check_status(IconWindow *win)
271 Atom type;
272 int format;
273 gulong nitems;
274 gulong bytes_after;
275 gint32 *data;
276 gboolean iconic;
278 if (XGetWindowProperty(gdk_display, win->xwindow,
279 gdk_x11_atom_to_xatom(xa_WM_STATE),
280 0, 1, False,
281 gdk_x11_atom_to_xatom(xa_WM_STATE),
282 &type, &format, &nitems,
283 &bytes_after, (guchar **) &data) == Success && data)
285 iconic = data[0] == 3;
286 XFree(data);
288 else
289 iconic = FALSE;
291 if (win->iconified == iconic)
292 return;
294 win->iconified = iconic;
296 state_changed(win);
299 /* Called for all events on all windows */
300 static GdkFilterReturn window_filter(GdkXEvent *xevent,
301 GdkEvent *event,
302 gpointer data)
304 XEvent *xev = (XEvent *) xevent;
305 IconWindow *w;
307 if (xev->type == PropertyNotify)
309 GdkAtom atom = gdk_x11_xatom_to_atom(xev->xproperty.atom);
310 Window win = ((XPropertyEvent *) xev)->window;
312 if (atom == xa_WM_STATE)
314 w = g_hash_table_lookup(known, &win);
316 if (w)
318 gdk_error_trap_push();
319 window_check_status(w);
320 if (gdk_error_trap_pop() != Success)
321 g_hash_table_remove(known, &win);
325 if (atom == xa__NET_CLIENT_LIST)
326 tasklist_update(FALSE);
329 return GDK_FILTER_CONTINUE;
332 /* Window has been added to list of managed windows */
333 static void add_window(Window win)
335 IconWindow *w;
337 /* g_print("[ New window %ld ]\n", (long) win); */
339 w = g_hash_table_lookup(known, &win);
341 if (!w)
343 XWindowAttributes attr;
345 gdk_error_trap_push();
347 XGetWindowAttributes(gdk_display, win, &attr);
349 if (gdk_error_trap_pop() != Success)
350 return;
351 gdk_error_trap_push();
353 XSelectInput(gdk_display, win, attr.your_event_mask |
354 PropertyChangeMask);
356 gdk_flush();
358 if (gdk_error_trap_pop() != Success)
359 return;
361 w = g_new(IconWindow, 1);
362 w->widget = NULL;
363 w->label = NULL;
364 w->text = NULL;
365 w->xwindow = win;
366 w->iconified = FALSE;
368 g_hash_table_insert(known, &w->xwindow, w);
371 gdk_error_trap_push();
373 window_check_status(w);
375 #if 0
376 set_iconify_pos(w);
377 #endif
379 if (gdk_error_trap_pop() != Success)
380 g_hash_table_remove(known, &win);
383 /* Window is no longer managed, but hasn't been destroyed yet */
384 static void remove_window(Window win)
386 IconWindow *w;
388 /* g_print("[ Remove window %ld ]\n", (long) win); */
390 w = g_hash_table_lookup(known, &win);
391 if (w)
393 if (w->iconified)
395 w->iconified = FALSE;
396 state_changed(w);
399 g_hash_table_remove(known, &win);
403 /* Make sure the window list is up-to-date. Call once to start, and then
404 * everytime _NET_CLIENT_LIST changes.
405 * If 'to_empty' is set them pretend all windows have disappeared.
407 static void tasklist_update(gboolean to_empty)
409 static GArray *old_mapping = NULL;
410 GArray *mapping = NULL;
411 int new_i, old_i;
413 if (!old_mapping)
415 old_mapping = g_array_new(FALSE, FALSE, sizeof(Window));
418 if (to_empty)
419 mapping = g_array_new(FALSE, FALSE, sizeof(Window));
420 else
421 mapping = get_window_list(gdk_x11_get_default_root_xwindow(),
422 gdk_atom_intern("_NET_CLIENT_LIST", FALSE));
424 new_i = 0;
425 old_i = 0;
426 while (new_i < mapping->len && old_i < old_mapping->len)
428 Window new = g_array_index(mapping, Window, new_i);
429 Window old = g_array_index(old_mapping, Window, old_i);
431 if (new == old)
433 new_i++;
434 old_i++;
436 else if (new < old)
438 add_window(new);
439 new_i++;
441 else
443 remove_window(old);
444 old_i++;
447 while (new_i < mapping->len)
449 add_window(g_array_index(mapping, Window, new_i));
450 new_i++;
452 while (old_i < old_mapping->len)
454 remove_window(g_array_index(old_mapping, Window, old_i));
455 old_i++;
458 g_array_free(old_mapping, TRUE);
459 old_mapping = mapping;
462 /* Called when the user clicks on the button */
463 static void uniconify(IconWindow *win)
465 XClientMessageEvent sev;
467 sev.type = ClientMessage;
468 sev.display = gdk_display;
469 sev.format = 32;
470 sev.window = win->xwindow;
471 sev.message_type = gdk_x11_atom_to_xatom(
472 gdk_atom_intern("_NET_ACTIVE_WINDOW", FALSE));
473 sev.data.l[0] = 0;
475 gdk_error_trap_push();
477 XSendEvent(gdk_display, DefaultRootWindow(gdk_display), False,
478 SubstructureNotifyMask | SubstructureRedirectMask,
479 (XEvent *) &sev);
480 XSync(gdk_display, False);
482 gdk_error_trap_pop();
485 static gint drag_start_x = -1;
486 static gint drag_start_y = -1;
487 static gboolean drag_started = FALSE;
488 static gint drag_off_x = -1;
489 static gint drag_off_y = -1;
491 static void icon_button_press(GtkWidget *widget,
492 GdkEventButton *event,
493 IconWindow *win)
495 if (event->button == 1)
497 drag_start_x = event->x_root;
498 drag_start_y = event->y_root;
499 drag_started = FALSE;
501 drag_off_x = event->x;
502 drag_off_y = event->y;
506 static gboolean icon_motion_notify(GtkWidget *widget,
507 GdkEventMotion *event,
508 IconWindow *win)
510 if (event->state & GDK_BUTTON1_MASK)
512 int dx = event->x_root - drag_start_x;
513 int dy = event->y_root - drag_start_y;
515 if (!drag_started)
517 if (abs(dx) < 5 && abs(dy) < 5)
518 return FALSE;
519 drag_started = TRUE;
522 fixed_move_fast(GTK_FIXED(win->widget->parent),
523 win->widget,
524 event->x_root - drag_off_x,
525 event->y_root - drag_off_y);
528 return FALSE;
531 static void button_released(GtkWidget *widget, IconWindow *win)
533 if (!drag_started)
534 uniconify(win);
537 static GdkColormap* get_cmap(GdkPixmap *pixmap)
539 GdkColormap *cmap;
541 cmap = gdk_drawable_get_colormap(pixmap);
542 if (cmap)
543 g_object_ref(G_OBJECT(cmap));
545 if (cmap == NULL)
547 if (gdk_drawable_get_depth(pixmap) == 1)
549 /* Masks don't need colourmaps */
550 cmap = NULL;
552 else
554 /* Try system cmap */
555 cmap = gdk_colormap_get_system();
556 g_object_ref(G_OBJECT(cmap));
560 /* Be sure we aren't going to blow up due to visual mismatch */
561 if (cmap && (gdk_colormap_get_visual(cmap)->depth !=
562 gdk_drawable_get_depth(pixmap)))
563 cmap = NULL;
565 return cmap;
568 /* Copy a pixmap from the server to a client-side pixbuf */
569 static GdkPixbuf* pixbuf_from_pixmap(Pixmap xpixmap)
571 GdkDrawable *drawable;
572 GdkPixbuf *retval;
573 GdkColormap *cmap;
574 int width, height;
576 retval = NULL;
578 drawable = gdk_xid_table_lookup(xpixmap);
580 if (drawable)
581 g_object_ref(G_OBJECT(drawable));
582 else
583 drawable = gdk_pixmap_foreign_new(xpixmap);
585 cmap = get_cmap(drawable);
587 /* GDK is supposed to do this but doesn't in GTK 2.0.2,
588 * fixed in 2.0.3
590 gdk_drawable_get_size(drawable, &width, &height);
592 retval = gdk_pixbuf_get_from_drawable(NULL, drawable, cmap,
593 0, 0, 0, 0, width, height);
595 if (cmap)
596 g_object_unref(G_OBJECT(cmap));
597 g_object_unref(G_OBJECT(drawable));
599 return retval;
602 /* Creates a new masked pixbuf from a non-masked pixbuf and a mask */
603 static GdkPixbuf* apply_mask(GdkPixbuf *pixbuf, GdkPixbuf *mask)
605 int w, h;
606 int i, j;
607 GdkPixbuf *with_alpha;
608 guchar *src;
609 guchar *dest;
610 int src_stride;
611 int dest_stride;
613 w = MIN(gdk_pixbuf_get_width(mask), gdk_pixbuf_get_width(pixbuf));
614 h = MIN(gdk_pixbuf_get_height(mask), gdk_pixbuf_get_height(pixbuf));
616 with_alpha = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
618 dest = gdk_pixbuf_get_pixels(with_alpha);
619 src = gdk_pixbuf_get_pixels(mask);
621 dest_stride = gdk_pixbuf_get_rowstride(with_alpha);
622 src_stride = gdk_pixbuf_get_rowstride(mask);
624 i = 0;
625 while (i < h)
627 j = 0;
628 while (j < w)
630 guchar *s = src + i * src_stride + j * 3;
631 guchar *d = dest + i * dest_stride + j * 4;
633 /* s[0] == s[1] == s[2], they are 255 if the bit was
634 * set, 0 otherwise
636 if (s[0] == 0)
637 d[3] = 0; /* transparent */
638 else
639 d[3] = 255; /* opaque */
641 ++j;
644 ++i;
647 return with_alpha;
650 /* Return a suitable icon for this window. unref the result.
651 * Never returns NULL.
653 static GdkPixbuf *get_image_for(IconWindow *win)
655 static MaskedPixmap *default_icon = NULL;
656 Pixmap pixmap = None;
657 Pixmap mask = None;
658 XWMHints *hints;
659 GdkPixbuf *retval = NULL;
661 /* Try the pixmap and mask in the old WMHints... */
662 gdk_error_trap_push();
663 hints = XGetWMHints(gdk_display, win->xwindow);
665 if (hints)
667 if (hints->flags & IconPixmapHint)
668 pixmap = hints->icon_pixmap;
669 if (hints->flags & IconMaskHint)
670 mask = hints->icon_mask;
672 XFree(hints);
673 hints = NULL;
676 if (pixmap != None)
678 GdkPixbuf *mask_pb = NULL;
680 retval = pixbuf_from_pixmap(pixmap);
682 if (retval && mask != None)
683 mask_pb = pixbuf_from_pixmap(mask);
685 if (mask_pb)
687 GdkPixbuf *masked;
689 masked = apply_mask(retval, mask_pb);
690 g_object_unref(G_OBJECT(mask_pb));
692 if (masked)
694 g_object_unref(G_OBJECT(retval));
695 retval = masked;
700 gdk_flush();
702 gdk_error_trap_pop();
704 if (!retval)
706 if (!default_icon)
707 default_icon = load_pixmap("iconified");
709 retval = default_icon->pixbuf;
710 g_object_ref(retval);
713 return retval;
716 /* A window has been iconified -- display it on the screen */
717 static void show_icon(IconWindow *win)
719 GdkPixbuf *pixbuf;
720 GtkWidget *vbox;
722 g_return_if_fail(win->widget == NULL);
723 g_return_if_fail(win->label == NULL);
725 win->widget = gtk_button_new();
726 vbox = gtk_vbox_new(FALSE, 0);
727 gtk_container_add(GTK_CONTAINER(win->widget), vbox);
729 pixbuf = get_image_for(win);
731 gtk_box_pack_start(GTK_BOX(vbox), gtk_image_new_from_pixbuf(pixbuf),
732 FALSE, TRUE, 0);
733 g_object_unref(pixbuf);
735 gtk_button_set_relief(GTK_BUTTON(win->widget), GTK_RELIEF_NONE);
737 win->label = wrapped_label_new(win->text, 180);
739 update_style(NULL, win, NULL);
741 gtk_box_pack_start(GTK_BOX(vbox), win->label, FALSE, TRUE, 0);
743 gtk_widget_add_events(win->widget, GDK_BUTTON1_MOTION_MASK);
744 g_signal_connect(win->widget, "button-press-event",
745 G_CALLBACK(icon_button_press), win);
746 g_signal_connect(win->widget, "motion-notify-event",
747 G_CALLBACK(icon_motion_notify), win);
748 g_signal_connect(win->widget, "released",
749 G_CALLBACK(button_released), win);
751 gtk_widget_show_all(vbox); /* So the size comes out right */
752 pinboard_add_widget(win->widget);
753 gtk_widget_show(win->widget);
756 /* A window has been destroyed/expanded -- remove its icon */
757 static void hide_icon(IconWindow *win)
759 GtkWidget *widget = win->widget;
761 g_return_if_fail(widget != NULL);
763 win->widget = NULL;
764 win->label = NULL;
765 gtk_widget_destroy(widget);
768 static void state_changed(IconWindow *win)
770 if (win->iconified)
772 get_icon_name(win);
773 show_icon(win);
775 else
776 hide_icon(win);
779 #if 0
780 /* Set the _NET_WM_ICON_GEOMETRY property, which indicates where this window
781 * will be iconified to. Should be inside a push/pop.
783 static void set_iconify_pos(IconWindow *win)
785 gint32 data[4];
787 data[0] = iconify_next_x;
788 data[1] = iconify_next_y;
789 data[2] = 100;
790 data[3] = 32;
792 XChangeProperty(gdk_display, win->xwindow,
793 gdk_x11_atom_to_xatom(xa__NET_WM_ICON_GEOMETRY),
794 XA_CARDINAL, 32, PropModeReplace, (guchar *) data, 4);
796 #endif
798 static void update_style(gpointer key, gpointer data, gpointer user_data)
800 IconWindow *win = (IconWindow *) data;
802 if (win->widget)
804 gtk_widget_modify_fg(win->label,
805 GTK_STATE_NORMAL, &pin_text_fg_col);
806 gtk_widget_modify_bg(win->label,
807 GTK_STATE_NORMAL, &pin_text_bg_col);
808 widget_modify_font(win->label, pinboard_font);