r1523: Check for application/x-executable.
[rox-filer.git] / ROX-Filer / src / tasklist.c
blob156e56ab665112c11423a9baae6167e53030e3e7
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"
48 /* There is one of these for each window controlled by the window
49 * manager (all tasks) in the _NET_CLIENT_LIST property.
51 typedef struct _IconWindow IconWindow;
53 struct _IconWindow {
54 GtkWidget *widget; /* Widget used for icon when iconified */
55 GtkWidget *label;
56 gchar *text;
57 Window xwindow;
58 gboolean iconified;
61 static GdkAtom xa_WM_STATE = GDK_NONE;
62 static GdkAtom xa_WM_NAME = GDK_NONE;
63 static GdkAtom xa_WM_ICON_NAME = GDK_NONE;
64 static GdkAtom xa_UTF8_STRING = GDK_NONE;
65 static GdkAtom xa_TEXT = GDK_NONE;
66 static GdkAtom xa__NET_WM_VISIBLE_NAME = GDK_NONE;
67 static GdkAtom xa__NET_WM_ICON_NAME = GDK_NONE;
68 static GdkAtom xa__NET_CLIENT_LIST = GDK_NONE;
69 static GdkAtom xa__NET_WM_ICON_GEOMETRY = GDK_NONE;
71 /* We have selected destroy and property events on every window in
72 * this table.
74 static GHashTable *known = NULL; /* XID -> IconWindow */
76 /* Static prototypes */
77 static void remove_window(Window win);
78 static void tasklist_update(gboolean to_empty);
79 static GdkFilterReturn window_filter(GdkXEvent *xevent,
80 GdkEvent *event,
81 gpointer data);
82 static guint xid_hash(XID *xid);
83 static gboolean xid_equal(XID *a, XID *b);
84 static void state_changed(IconWindow *win);
85 static void show_icon(IconWindow *win);
86 static void icon_win_free(IconWindow *win);
87 static void update_style(gpointer key, gpointer data, gpointer user_data);
89 /****************************************************************
90 * EXTERNAL INTERFACE *
91 ****************************************************************/
93 void tasklist_set_active(gboolean active)
95 static gboolean need_init = TRUE;
96 static gboolean tasklist_active = FALSE;
98 if (active == tasklist_active)
99 return;
100 tasklist_active = active;
102 if (need_init)
104 GdkWindow *root;
106 root = gdk_get_default_root_window();
108 xa_WM_STATE = gdk_atom_intern("WM_STATE", FALSE);
109 xa_WM_ICON_NAME = gdk_atom_intern("WM_ICON_NAME", FALSE);
110 xa_WM_NAME = gdk_atom_intern("WM_NAME", FALSE);
111 xa_UTF8_STRING = gdk_atom_intern("UTF8_STRING", FALSE);
112 xa_TEXT = gdk_atom_intern("TEXT", FALSE);
113 xa__NET_CLIENT_LIST =
114 gdk_atom_intern("_NET_CLIENT_LIST", FALSE);
115 xa__NET_WM_VISIBLE_NAME =
116 gdk_atom_intern("_NET_WM_VISIBLE_NAME", FALSE);
117 xa__NET_WM_ICON_NAME =
118 gdk_atom_intern("_NET_WM_ICON_NAME", FALSE);
119 xa__NET_WM_ICON_GEOMETRY =
120 gdk_atom_intern("_NET_WM_ICON_GEOMETRY", FALSE);
122 known = g_hash_table_new_full((GHashFunc) xid_hash,
123 (GEqualFunc) xid_equal,
124 NULL,
125 (GDestroyNotify) icon_win_free);
126 gdk_window_set_events(root, gdk_window_get_events(root) |
127 GDK_PROPERTY_CHANGE_MASK);
128 need_init = FALSE;
131 if (active)
132 gdk_window_add_filter(NULL, window_filter, NULL);
133 else
134 gdk_window_remove_filter(NULL, window_filter, NULL);
136 tasklist_update(!active);
139 /* User has changes the colours in the options box... */
140 void tasklist_style_changed(void)
142 g_hash_table_foreach(known, update_style, NULL);
145 /****************************************************************
146 * INTERNAL FUNCTIONS *
147 ****************************************************************/
149 static void icon_win_free(IconWindow *win)
151 g_return_if_fail(win->widget == NULL);
152 g_return_if_fail(win->label == NULL);
154 if (win->text)
155 g_free(win->text);
156 g_free(win);
159 /* From gdk */
160 static guint xid_hash(XID *xid)
162 return *xid;
165 /* From gdk */
166 static gboolean xid_equal(XID *a, XID *b)
168 return (*a == *b);
171 static int wincmp(const void *a, const void *b)
173 const Window *aw = a;
174 const Window *bw = b;
176 if (*aw < *bw)
177 return -1;
178 else if (*aw > *bw)
179 return 1;
180 else
181 return 0;
184 /* Read the list of WINDOWs from (xwindow,atom), returning them
185 * in a (sorted) Array of Windows. On error, an empty array is
186 * returned.
187 * Free the array afterwards.
189 static GArray *get_window_list(Window xwindow, GdkAtom atom)
191 GArray *array;
192 Atom type;
193 int format;
194 gulong nitems;
195 gulong bytes_after;
196 Window *data;
197 int err, result;
198 int i;
200 array = g_array_new(FALSE, FALSE, sizeof(Window));
202 gdk_error_trap_push();
203 type = None;
204 result = XGetWindowProperty(gdk_display,
205 xwindow,
206 gdk_x11_atom_to_xatom(atom),
207 0, G_MAXLONG,
208 False, XA_WINDOW, &type, &format, &nitems,
209 &bytes_after, (guchar **)&data);
210 err = gdk_error_trap_pop();
212 if (err != Success || result != Success)
213 return array;
215 if (type == XA_WINDOW)
217 for (i = 0; i < nitems; i++)
218 g_array_append_val(array, data[i]);
220 if (array->len)
221 g_array_sort(array, wincmp);
224 XFree(data);
226 return array;
229 static gchar *get_str(IconWindow *win, GdkAtom atom)
231 Atom rtype;
232 int format;
233 gulong nitems;
234 gulong bytes_after;
235 char *data, *str = NULL;
237 if (XGetWindowProperty(gdk_display, win->xwindow,
238 gdk_x11_atom_to_xatom(atom),
239 0, G_MAXLONG, False,
240 AnyPropertyType,
241 &rtype, &format, &nitems,
242 &bytes_after, (guchar **) &data) == Success && data)
244 if (*data)
245 str = g_strdup(data);
246 XFree(data);
249 return str;
252 static void get_icon_name(IconWindow *win)
254 if (win->text)
256 g_free(win->text);
257 win->text = NULL;
260 win->text = get_str(win, xa__NET_WM_ICON_NAME);
261 if (!win->text)
262 win->text = get_str(win, xa__NET_WM_VISIBLE_NAME);
263 if (!win->text)
264 win->text = get_str(win, xa_WM_ICON_NAME);
265 if (!win->text)
266 win->text = get_str(win, xa_WM_NAME);
267 if (!win->text)
268 win->text = g_strdup(_("Window"));
271 /* Call from within error_push/pop */
272 static void window_check_status(IconWindow *win)
274 Atom type;
275 int format;
276 gulong nitems;
277 gulong bytes_after;
278 gint32 *data;
279 gboolean iconic;
281 if (XGetWindowProperty(gdk_display, win->xwindow,
282 gdk_x11_atom_to_xatom(xa_WM_STATE),
283 0, 1, False,
284 gdk_x11_atom_to_xatom(xa_WM_STATE),
285 &type, &format, &nitems,
286 &bytes_after, (guchar **) &data) == Success && data)
288 iconic = data[0] == 3;
289 XFree(data);
291 else
292 iconic = FALSE;
294 if (win->iconified == iconic)
295 return;
297 win->iconified = iconic;
299 state_changed(win);
302 /* Called for all events on all windows */
303 static GdkFilterReturn window_filter(GdkXEvent *xevent,
304 GdkEvent *event,
305 gpointer data)
307 XEvent *xev = (XEvent *) xevent;
308 IconWindow *w;
310 if (xev->type == PropertyNotify)
312 GdkAtom atom = gdk_x11_xatom_to_atom(xev->xproperty.atom);
313 Window win = ((XPropertyEvent *) xev)->window;
315 if (atom == xa_WM_STATE)
317 w = g_hash_table_lookup(known, &win);
319 if (w)
321 gdk_error_trap_push();
322 window_check_status(w);
323 if (gdk_error_trap_pop() != Success)
324 g_hash_table_remove(known, &win);
328 if (atom == xa__NET_CLIENT_LIST)
329 tasklist_update(FALSE);
332 return GDK_FILTER_CONTINUE;
335 /* Window has been added to list of managed windows */
336 static void add_window(Window win)
338 IconWindow *w;
340 /* g_print("[ New window %ld ]\n", (long) win); */
342 w = g_hash_table_lookup(known, &win);
344 if (!w)
346 XWindowAttributes attr;
348 gdk_error_trap_push();
350 XGetWindowAttributes(gdk_display, win, &attr);
352 if (gdk_error_trap_pop() != Success)
353 return;
354 gdk_error_trap_push();
356 XSelectInput(gdk_display, win, attr.your_event_mask |
357 PropertyChangeMask);
358 gdk_flush();
360 if (gdk_error_trap_pop() != Success)
361 return;
363 w = g_new(IconWindow, 1);
364 w->widget = NULL;
365 w->label = NULL;
366 w->text = NULL;
367 w->xwindow = win;
368 w->iconified = FALSE;
370 g_hash_table_insert(known, &w->xwindow, w);
373 gdk_error_trap_push();
375 window_check_status(w);
377 #if 0
378 set_iconify_pos(w);
379 #endif
381 if (gdk_error_trap_pop() != Success)
382 g_hash_table_remove(known, &win);
385 /* Window is no longer managed, but hasn't been destroyed yet */
386 static void remove_window(Window win)
388 IconWindow *w;
390 /* g_print("[ Remove window %ld ]\n", (long) win); */
392 w = g_hash_table_lookup(known, &win);
393 if (w)
395 if (w->iconified)
397 w->iconified = FALSE;
398 state_changed(w);
401 g_hash_table_remove(known, &win);
405 /* Make sure the window list is up-to-date. Call once to start, and then
406 * everytime _NET_CLIENT_LIST changes.
407 * If 'to_empty' is set them pretend all windows have disappeared.
409 static void tasklist_update(gboolean to_empty)
411 static GArray *old_mapping = NULL;
412 GArray *mapping = NULL;
413 int new_i, old_i;
415 if (!old_mapping)
417 old_mapping = g_array_new(FALSE, FALSE, sizeof(Window));
420 if (to_empty)
421 mapping = g_array_new(FALSE, FALSE, sizeof(Window));
422 else
423 mapping = get_window_list(gdk_x11_get_default_root_xwindow(),
424 gdk_atom_intern("_NET_CLIENT_LIST", FALSE));
426 new_i = 0;
427 old_i = 0;
428 while (new_i < mapping->len && old_i < old_mapping->len)
430 Window new = g_array_index(mapping, Window, new_i);
431 Window old = g_array_index(old_mapping, Window, old_i);
433 if (new == old)
435 new_i++;
436 old_i++;
438 else if (new < old)
440 add_window(new);
441 new_i++;
443 else
445 remove_window(old);
446 old_i++;
449 while (new_i < mapping->len)
451 add_window(g_array_index(mapping, Window, new_i));
452 new_i++;
454 while (old_i < old_mapping->len)
456 remove_window(g_array_index(old_mapping, Window, old_i));
457 old_i++;
460 g_array_free(old_mapping, TRUE);
461 old_mapping = mapping;
464 /* Called when the user clicks on the button */
465 static void uniconify(IconWindow *win)
467 XClientMessageEvent sev;
469 sev.type = ClientMessage;
470 sev.display = gdk_display;
471 sev.format = 32;
472 sev.window = win->xwindow;
473 sev.message_type = gdk_x11_atom_to_xatom(
474 gdk_atom_intern("_NET_ACTIVE_WINDOW", FALSE));
475 sev.data.l[0] = 0;
477 gdk_error_trap_push();
479 XSendEvent(gdk_display, DefaultRootWindow(gdk_display), False,
480 SubstructureNotifyMask | SubstructureRedirectMask,
481 (XEvent *) &sev);
482 XSync (gdk_display, False);
484 gdk_error_trap_pop();
487 static gint drag_start_x = -1;
488 static gint drag_start_y = -1;
489 static gboolean drag_started = FALSE;
490 static gint drag_off_x = -1;
491 static gint drag_off_y = -1;
493 static void icon_button_press(GtkWidget *widget,
494 GdkEventButton *event,
495 IconWindow *win)
497 if (event->button == 1)
499 drag_start_x = event->x_root;
500 drag_start_y = event->y_root;
501 drag_started = FALSE;
503 drag_off_x = event->x;
504 drag_off_y = event->y;
508 static gboolean icon_motion_notify(GtkWidget *widget,
509 GdkEventMotion *event,
510 IconWindow *win)
512 if (event->state & GDK_BUTTON1_MASK)
514 int dx = event->x_root - drag_start_x;
515 int dy = event->y_root - drag_start_y;
517 if (!drag_started)
519 if (abs(dx) < 5 && abs(dy) < 5)
520 return FALSE;
521 drag_started = TRUE;
524 fixed_move_fast(GTK_FIXED(win->widget->parent),
525 win->widget,
526 event->x_root - drag_off_x,
527 event->y_root - drag_off_y);
530 return FALSE;
533 static void button_released(GtkWidget *widget, IconWindow *win)
535 if (!drag_started)
536 uniconify(win);
539 static GdkColormap* get_cmap(GdkPixmap *pixmap)
541 GdkColormap *cmap;
543 cmap = gdk_drawable_get_colormap(pixmap);
544 if (cmap)
545 g_object_ref(G_OBJECT(cmap));
547 if (cmap == NULL)
549 if (gdk_drawable_get_depth(pixmap) == 1)
551 /* Masks don't need colourmaps */
552 cmap = NULL;
554 else
556 /* Try system cmap */
557 cmap = gdk_colormap_get_system();
558 g_object_ref(G_OBJECT(cmap));
562 /* Be sure we aren't going to blow up due to visual mismatch */
563 if (cmap && (gdk_colormap_get_visual(cmap)->depth !=
564 gdk_drawable_get_depth(pixmap)))
565 cmap = NULL;
567 return cmap;
570 /* Copy a pixmap from the server to a client-side pixbuf */
571 static GdkPixbuf* pixbuf_from_pixmap(Pixmap xpixmap)
573 GdkDrawable *drawable;
574 GdkPixbuf *retval;
575 GdkColormap *cmap;
576 int width, height;
578 retval = NULL;
580 drawable = gdk_xid_table_lookup(xpixmap);
582 if (drawable)
583 g_object_ref(G_OBJECT(drawable));
584 else
585 drawable = gdk_pixmap_foreign_new(xpixmap);
587 cmap = get_cmap(drawable);
589 /* GDK is supposed to do this but doesn't in GTK 2.0.2,
590 * fixed in 2.0.3
592 gdk_drawable_get_size(drawable, &width, &height);
594 retval = gdk_pixbuf_get_from_drawable(NULL, drawable, cmap,
595 0, 0, 0, 0, width, height);
597 if (cmap)
598 g_object_unref(G_OBJECT(cmap));
599 g_object_unref(G_OBJECT(drawable));
601 return retval;
604 /* Creates a new masked pixbuf from a non-masked pixbuf and a mask */
605 static GdkPixbuf* apply_mask(GdkPixbuf *pixbuf, GdkPixbuf *mask)
607 int w, h;
608 int i, j;
609 GdkPixbuf *with_alpha;
610 guchar *src;
611 guchar *dest;
612 int src_stride;
613 int dest_stride;
615 w = MIN(gdk_pixbuf_get_width(mask), gdk_pixbuf_get_width(pixbuf));
616 h = MIN(gdk_pixbuf_get_height(mask), gdk_pixbuf_get_height(pixbuf));
618 with_alpha = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
620 dest = gdk_pixbuf_get_pixels(with_alpha);
621 src = gdk_pixbuf_get_pixels(mask);
623 dest_stride = gdk_pixbuf_get_rowstride(with_alpha);
624 src_stride = gdk_pixbuf_get_rowstride(mask);
626 i = 0;
627 while (i < h)
629 j = 0;
630 while (j < w)
632 guchar *s = src + i * src_stride + j * 3;
633 guchar *d = dest + i * dest_stride + j * 4;
635 /* s[0] == s[1] == s[2], they are 255 if the bit was
636 * set, 0 otherwise
638 if (s[0] == 0)
639 d[3] = 0; /* transparent */
640 else
641 d[3] = 255; /* opaque */
643 ++j;
646 ++i;
649 return with_alpha;
652 /* Return a suitable icon for this window. unref the result.
653 * Never returns NULL.
655 static GdkPixbuf *get_image_for(IconWindow *win)
657 static MaskedPixmap *default_icon = NULL;
658 Pixmap pixmap = None;
659 Pixmap mask = None;
660 XWMHints *hints;
661 GdkPixbuf *retval = NULL;
663 /* Try the pixmap and mask in the old WMHints... */
664 gdk_error_trap_push();
665 hints = XGetWMHints(gdk_display, win->xwindow);
667 if (hints)
669 if (hints->flags & IconPixmapHint)
670 pixmap = hints->icon_pixmap;
671 if (hints->flags & IconMaskHint)
672 mask = hints->icon_mask;
674 XFree(hints);
675 hints = NULL;
678 if (pixmap != None)
680 GdkPixbuf *mask_pb = NULL;
682 retval = pixbuf_from_pixmap(pixmap);
684 if (retval && mask != None)
685 mask_pb = pixbuf_from_pixmap(mask);
687 if (mask_pb)
689 GdkPixbuf *masked;
691 masked = apply_mask(retval, mask_pb);
692 g_object_unref(G_OBJECT(mask_pb));
694 if (masked)
696 g_object_unref(G_OBJECT(retval));
697 retval = masked;
702 gdk_flush();
704 gdk_error_trap_pop();
706 if (!retval)
708 if (!default_icon)
709 default_icon = load_pixmap("images/iconified.png");
711 retval = default_icon->pixbuf;
712 g_object_ref(retval);
715 return retval;
718 /* A window has been iconified -- display it on the screen */
719 static void show_icon(IconWindow *win)
721 GdkPixbuf *pixbuf;
722 GtkWidget *vbox;
724 g_return_if_fail(win->widget == NULL);
725 g_return_if_fail(win->label == NULL);
727 win->widget = gtk_button_new();
728 vbox = gtk_vbox_new(FALSE, 0);
729 gtk_container_add(GTK_CONTAINER(win->widget), vbox);
731 pixbuf = get_image_for(win);
733 gtk_box_pack_start(GTK_BOX(vbox), gtk_image_new_from_pixbuf(pixbuf),
734 FALSE, TRUE, 0);
735 g_object_unref(pixbuf);
737 gtk_button_set_relief(GTK_BUTTON(win->widget), GTK_RELIEF_NONE);
739 win->label = wrapped_label_new(win->text, 180);
741 update_style(NULL, win, NULL);
743 gtk_box_pack_start(GTK_BOX(vbox), win->label, FALSE, TRUE, 0);
745 gtk_widget_add_events(win->widget, GDK_BUTTON1_MOTION_MASK);
746 g_signal_connect(win->widget, "button-press-event",
747 G_CALLBACK(icon_button_press), win);
748 g_signal_connect(win->widget, "motion-notify-event",
749 G_CALLBACK(icon_motion_notify), win);
750 g_signal_connect(win->widget, "released",
751 G_CALLBACK(button_released), win);
753 gtk_widget_show_all(vbox); /* So the size comes out right */
754 pinboard_add_widget(win->widget);
755 gtk_widget_show(win->widget);
758 /* A window has been destroyed/expanded -- remove its icon */
759 static void hide_icon(IconWindow *win)
761 GtkWidget *widget = win->widget;
763 g_return_if_fail(widget != NULL);
765 win->widget = NULL;
766 win->label = NULL;
767 gtk_widget_destroy(widget);
770 static void state_changed(IconWindow *win)
772 if (win->iconified)
774 get_icon_name(win);
775 show_icon(win);
777 else
778 hide_icon(win);
781 #if 0
782 /* Set the _NET_WM_ICON_GEOMETRY property, which indicates where this window
783 * will be iconified to. Should be inside a push/pop.
785 static void set_iconify_pos(IconWindow *win)
787 gint32 data[4];
789 data[0] = iconify_next_x;
790 data[1] = iconify_next_y;
791 data[2] = 100;
792 data[3] = 32;
794 XChangeProperty(gdk_display, win->xwindow,
795 gdk_x11_atom_to_xatom(xa__NET_WM_ICON_GEOMETRY),
796 XA_CARDINAL, 32, PropModeReplace, (guchar *) data, 4);
798 #endif
800 static void update_style(gpointer key, gpointer data, gpointer user_data)
802 IconWindow *win = (IconWindow *) data;
804 if (win->widget)
806 gtk_widget_modify_fg(win->label,
807 GTK_STATE_NORMAL, &pin_text_fg_col);
808 gtk_widget_modify_bg(win->label,
809 GTK_STATE_NORMAL, &pin_text_bg_col);