r1467: Pinboard text fg colour works again (for tasklist icons too).
[rox-filer.git] / ROX-Filer / src / tasklist.c
blobcc03f5688f1adfe9cd9ad9089739ba543bc7680b
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 <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 "options.h"
40 #include "gui_support.h"
41 #include "main.h"
42 #include "pinboard.h"
43 #include "pixmaps.h"
45 /* There is one of these for each window controlled by the window
46 * manager (all tasks) in the _NET_CLIENT_LIST property.
48 typedef struct _IconWindow IconWindow;
50 struct _IconWindow {
51 GtkWidget *widget; /* Widget used for icon when iconified */
52 GtkWidget *label;
53 gchar *text;
54 Window xwindow;
55 gboolean iconified;
58 static GdkAtom xa_WM_STATE = GDK_NONE;
59 static GdkAtom xa_WM_NAME = GDK_NONE;
60 static GdkAtom xa_WM_ICON_NAME = GDK_NONE;
61 static GdkAtom xa_UTF8_STRING = GDK_NONE;
62 static GdkAtom xa_TEXT = GDK_NONE;
63 static GdkAtom xa__NET_WM_VISIBLE_NAME = GDK_NONE;
64 static GdkAtom xa__NET_WM_ICON_NAME = GDK_NONE;
65 static GdkAtom xa__NET_CLIENT_LIST = GDK_NONE;
66 static GdkAtom xa__NET_WM_ICON_GEOMETRY = GDK_NONE;
68 /* We have selected destroy and property events on every window in
69 * this table.
71 static GHashTable *known = NULL; /* XID -> IconWindow */
73 /* Top-left corner of next icon to be created */
74 static int iconify_next_x = 0;
75 static int iconify_next_y = 0;
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 set_iconify_pos(IconWindow *win);
89 static void update_style(gpointer key, gpointer data, gpointer user_data);
91 /****************************************************************
92 * EXTERNAL INTERFACE *
93 ****************************************************************/
95 void tasklist_set_active(gboolean active)
97 static gboolean need_init = TRUE;
98 static gboolean tasklist_active = FALSE;
100 if (active == tasklist_active)
101 return;
102 tasklist_active = active;
104 if (need_init)
106 GdkWindow *root;
108 root = gdk_get_default_root_window();
110 xa_WM_STATE = gdk_atom_intern("WM_STATE", FALSE);
111 xa_WM_ICON_NAME = gdk_atom_intern("WM_ICON_NAME", FALSE);
112 xa_WM_NAME = gdk_atom_intern("WM_NAME", FALSE);
113 xa_UTF8_STRING = gdk_atom_intern("UTF8_STRING", FALSE);
114 xa_TEXT = gdk_atom_intern("TEXT", FALSE);
115 xa__NET_CLIENT_LIST =
116 gdk_atom_intern("_NET_CLIENT_LIST", FALSE);
117 xa__NET_WM_VISIBLE_NAME =
118 gdk_atom_intern("_NET_WM_VISIBLE_NAME", FALSE);
119 xa__NET_WM_ICON_NAME =
120 gdk_atom_intern("_NET_WM_ICON_NAME", FALSE);
121 xa__NET_WM_ICON_GEOMETRY =
122 gdk_atom_intern("_NET_WM_ICON_GEOMETRY", FALSE);
124 known = g_hash_table_new_full((GHashFunc) xid_hash,
125 (GEqualFunc) xid_equal,
126 NULL,
127 (GDestroyNotify) icon_win_free);
128 gdk_window_set_events(root, gdk_window_get_events(root) |
129 GDK_PROPERTY_CHANGE_MASK);
130 need_init = FALSE;
133 if (active)
134 gdk_window_add_filter(NULL, window_filter, NULL);
135 else
136 gdk_window_remove_filter(NULL, window_filter, NULL);
138 tasklist_update(!active);
141 /* User has changes the colours in the options box... */
142 void tasklist_style_changed(void)
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 if (win->text)
157 g_free(win->text);
158 g_free(win);
161 /* From gdk */
162 static guint xid_hash(XID *xid)
164 return *xid;
167 /* From gdk */
168 static gboolean xid_equal(XID *a, XID *b)
170 return (*a == *b);
173 static int wincmp(const void *a, const void *b)
175 const Window *aw = a;
176 const Window *bw = b;
178 if (*aw < *bw)
179 return -1;
180 else if (*aw > *bw)
181 return 1;
182 else
183 return 0;
186 /* Read the list of WINDOWs from (xwindow,atom), returning them
187 * in a (sorted) Array of Windows. On error, an empty array is
188 * returned.
189 * Free the array afterwards.
191 static GArray *get_window_list(Window xwindow, GdkAtom atom)
193 GArray *array;
194 Atom type;
195 int format;
196 gulong nitems;
197 gulong bytes_after;
198 Window *data;
199 int err, result;
200 int i;
202 array = g_array_new(FALSE, FALSE, sizeof(Window));
204 gdk_error_trap_push();
205 type = None;
206 result = XGetWindowProperty(gdk_display,
207 xwindow,
208 gdk_x11_atom_to_xatom(atom),
209 0, G_MAXLONG,
210 False, XA_WINDOW, &type, &format, &nitems,
211 &bytes_after, (guchar **)&data);
212 err = gdk_error_trap_pop();
214 if (err != Success || result != Success)
215 return array;
217 if (type == XA_WINDOW)
219 for (i = 0; i < nitems; i++)
220 g_array_append_val(array, data[i]);
222 if (array->len)
223 g_array_sort(array, wincmp);
226 XFree(data);
228 return array;
231 static gchar *get_str(IconWindow *win, GdkAtom atom)
233 Atom rtype;
234 int format;
235 gulong nitems;
236 gulong bytes_after;
237 char *data, *str = NULL;
239 if (XGetWindowProperty(gdk_display, win->xwindow,
240 gdk_x11_atom_to_xatom(atom),
241 0, G_MAXLONG, False,
242 AnyPropertyType,
243 &rtype, &format, &nitems,
244 &bytes_after, (guchar **) &data) == Success && data)
246 if (*data)
247 str = g_strdup(data);
248 XFree(data);
251 return str;
254 static void get_icon_name(IconWindow *win)
256 if (win->text)
258 g_free(win->text);
259 win->text = NULL;
262 win->text = get_str(win, xa__NET_WM_ICON_NAME);
263 if (!win->text)
264 win->text = get_str(win, xa__NET_WM_VISIBLE_NAME);
265 if (!win->text)
266 win->text = get_str(win, xa_WM_ICON_NAME);
267 if (!win->text)
268 win->text = get_str(win, xa_WM_NAME);
269 if (!win->text)
270 win->text = g_strdup(_("Window"));
273 /* Call from within error_push/pop */
274 static void window_check_status(IconWindow *win)
276 Atom type;
277 int format;
278 gulong nitems;
279 gulong bytes_after;
280 gint32 *data;
281 gboolean iconic;
283 if (XGetWindowProperty(gdk_display, win->xwindow,
284 gdk_x11_atom_to_xatom(xa_WM_STATE),
285 0, 1, False,
286 gdk_x11_atom_to_xatom(xa_WM_STATE),
287 &type, &format, &nitems,
288 &bytes_after, (guchar **) &data) == Success && data)
290 iconic = data[0] == 3;
291 XFree(data);
293 else
294 iconic = FALSE;
296 if (win->iconified == iconic)
297 return;
299 win->iconified = iconic;
301 state_changed(win);
304 /* Called for all events on all windows */
305 static GdkFilterReturn window_filter(GdkXEvent *xevent,
306 GdkEvent *event,
307 gpointer data)
309 XEvent *xev = (XEvent *) xevent;
310 IconWindow *w;
312 if (xev->type == PropertyNotify)
314 GdkAtom atom = gdk_x11_xatom_to_atom(xev->xproperty.atom);
315 Window win = ((XPropertyEvent *) xev)->window;
317 if (atom == xa_WM_STATE)
319 w = g_hash_table_lookup(known, &win);
321 if (w)
323 gdk_error_trap_push();
324 window_check_status(w);
325 if (gdk_error_trap_pop() != Success)
326 g_hash_table_remove(known, &win);
330 if (atom == xa__NET_CLIENT_LIST)
331 tasklist_update(FALSE);
334 return GDK_FILTER_CONTINUE;
337 /* Window has been added to list of managed windows */
338 static void add_window(Window win)
340 IconWindow *w;
342 /* g_print("[ New window %ld ]\n", (long) win); */
344 w = g_hash_table_lookup(known, &win);
346 if (!w)
348 XWindowAttributes attr;
350 gdk_error_trap_push();
352 XGetWindowAttributes(gdk_display, win, &attr);
354 XSelectInput(gdk_display, win, attr.your_event_mask |
355 PropertyChangeMask);
357 if (gdk_error_trap_pop() != Success)
358 return;
360 w = g_new(IconWindow, 1);
361 w->widget = NULL;
362 w->label = NULL;
363 w->text = NULL;
364 w->xwindow = win;
365 w->iconified = FALSE;
367 g_hash_table_insert(known, &w->xwindow, w);
370 gdk_error_trap_push();
372 window_check_status(w);
374 set_iconify_pos(w);
376 if (gdk_error_trap_pop() != Success)
377 g_hash_table_remove(known, &win);
380 /* Window is no longer managed, but hasn't been destroyed yet */
381 static void remove_window(Window win)
383 IconWindow *w;
385 /* g_print("[ Remove window %ld ]\n", (long) win); */
387 w = g_hash_table_lookup(known, &win);
388 if (w)
390 if (w->iconified)
392 w->iconified = FALSE;
393 state_changed(w);
396 g_hash_table_remove(known, &win);
400 /* Make sure the window list is up-to-date. Call once to start, and then
401 * everytime _NET_CLIENT_LIST changes.
402 * If 'to_empty' is set them pretend all windows have disappeared.
404 static void tasklist_update(gboolean to_empty)
406 static GArray *old_mapping = NULL;
407 GArray *mapping = NULL;
408 int new_i, old_i;
410 if (!old_mapping)
412 old_mapping = g_array_new(FALSE, FALSE, sizeof(Window));
415 if (to_empty)
416 mapping = g_array_new(FALSE, FALSE, sizeof(Window));
417 else
418 mapping = get_window_list(gdk_x11_get_default_root_xwindow(),
419 gdk_atom_intern("_NET_CLIENT_LIST", FALSE));
421 new_i = 0;
422 old_i = 0;
423 while (new_i < mapping->len && old_i < old_mapping->len)
425 Window new = g_array_index(mapping, Window, new_i);
426 Window old = g_array_index(old_mapping, Window, old_i);
428 if (new == old)
430 new_i++;
431 old_i++;
433 else if (new < old)
435 add_window(new);
436 new_i++;
438 else
440 remove_window(old);
441 old_i++;
444 while (new_i < mapping->len)
446 add_window(g_array_index(mapping, Window, new_i));
447 new_i++;
449 while (old_i < old_mapping->len)
451 remove_window(g_array_index(old_mapping, Window, old_i));
452 old_i++;
455 g_array_free(old_mapping, TRUE);
456 old_mapping = mapping;
459 /* Called when the user clicks on the button */
460 static void uniconify(IconWindow *win)
462 XClientMessageEvent sev;
464 sev.type = ClientMessage;
465 sev.display = gdk_display;
466 sev.format = 32;
467 sev.window = win->xwindow;
468 sev.message_type = gdk_x11_atom_to_xatom(
469 gdk_atom_intern("_NET_ACTIVE_WINDOW", FALSE));
470 sev.data.l[0] = 0;
472 gdk_error_trap_push();
474 XSendEvent(gdk_display, DefaultRootWindow(gdk_display), False,
475 SubstructureNotifyMask | SubstructureRedirectMask,
476 (XEvent *) &sev);
477 XSync (gdk_display, False);
479 gdk_error_trap_pop();
482 static gint drag_start_x = -1;
483 static gint drag_start_y = -1;
484 static gboolean drag_started = FALSE;
485 static gint drag_off_x = -1;
486 static gint drag_off_y = -1;
488 static void icon_button_press(GtkWidget *widget,
489 GdkEventButton *event,
490 IconWindow *win)
492 if (event->button == 1)
494 drag_start_x = event->x_root;
495 drag_start_y = event->y_root;
496 drag_started = FALSE;
498 drag_off_x = event->x;
499 drag_off_y = event->y;
503 static gboolean icon_motion_notify(GtkWidget *widget,
504 GdkEventMotion *event,
505 IconWindow *win)
507 if (event->state & GDK_BUTTON1_MASK)
509 int dx = event->x_root - drag_start_x;
510 int dy = event->y_root - drag_start_y;
512 if (!drag_started)
514 if (abs(dx) < 5 && abs(dy) < 5)
515 return FALSE;
516 drag_started = TRUE;
519 fixed_move_fast(GTK_FIXED(win->widget->parent),
520 win->widget,
521 event->x_root - drag_off_x,
522 event->y_root - drag_off_y);
525 return FALSE;
528 static void button_released(GtkWidget *widget, IconWindow *win)
530 if (!drag_started)
531 uniconify(win);
534 /* A window has been iconified -- display it on the screen */
535 static void show_icon(IconWindow *win)
537 static MaskedPixmap *icon = NULL;
538 GtkRequisition req;
539 GtkWidget *vbox;
541 g_return_if_fail(win->widget == NULL);
542 g_return_if_fail(win->label == NULL);
544 if (!icon)
545 icon = load_pixmap("images/iconified.png");
547 win->widget = gtk_button_new();
548 vbox = gtk_vbox_new(FALSE, 0);
549 gtk_container_add(GTK_CONTAINER(win->widget), vbox);
551 gtk_box_pack_start(GTK_BOX(vbox),
552 gtk_image_new_from_pixbuf(icon->pixbuf),
553 FALSE, TRUE, 0);
555 gtk_button_set_relief(GTK_BUTTON(win->widget), GTK_RELIEF_NONE);
557 win->label = gtk_label_new(win->text);
559 update_style(NULL, win, NULL);
561 gtk_box_pack_start(GTK_BOX(vbox), win->label, FALSE, TRUE, 0);
563 gtk_widget_add_events(win->widget, GDK_BUTTON1_MOTION_MASK);
564 g_signal_connect(win->widget, "button-press-event",
565 G_CALLBACK(icon_button_press), win);
566 g_signal_connect(win->widget, "motion-notify-event",
567 G_CALLBACK(icon_motion_notify), win);
568 g_signal_connect(win->widget, "released",
569 G_CALLBACK(button_released), win);
571 pinboard_add_widget(win->widget, iconify_next_x, iconify_next_y);
572 gtk_widget_show_all(win->widget);
573 gtk_widget_size_request(win->widget, &req);
575 iconify_next_y += req.height;
576 if (iconify_next_y + req.height > screen_height)
577 iconify_next_y = 0;
580 /* A window has been destroyed/expanded -- remove its icon */
581 static void hide_icon(IconWindow *win)
583 GtkWidget *widget = win->widget;
585 g_return_if_fail(widget != NULL);
587 win->widget = NULL;
588 win->label = NULL;
589 gtk_widget_destroy(widget);
592 static void state_changed(IconWindow *win)
594 if (win->iconified)
596 get_icon_name(win);
597 show_icon(win);
599 else
600 hide_icon(win);
603 /* Set the _NET_WM_ICON_GEOMETRY property, which indicates where this window
604 * will be iconified to. Should be inside a push/pop.
606 static void set_iconify_pos(IconWindow *win)
608 gint32 data[4];
610 data[0] = iconify_next_x;
611 data[1] = iconify_next_y;
612 data[2] = 100;
613 data[3] = 32;
615 XChangeProperty(gdk_display, win->xwindow,
616 gdk_x11_atom_to_xatom(xa__NET_WM_ICON_GEOMETRY),
617 XA_CARDINAL, 32, PropModeReplace, (guchar *) data, 4);
620 static void update_style(gpointer key, gpointer data, gpointer user_data)
622 IconWindow *win = (IconWindow *) data;
624 if (win->widget)
626 gtk_widget_modify_fg(win->label,
627 GTK_STATE_NORMAL, &pin_text_fg_col);
628 gtk_widget_modify_bg(win->label,
629 GTK_STATE_NORMAL, &pin_text_bg_col);