4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2003, 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)
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
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.
36 #include <X11/Xatom.h>
43 #include "gui_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
;
55 GtkWidget
*widget
; /* Widget used for icon when iconified */
62 /* If TRUE, only iconfied windows with _NET_WM_STATE_HIDDEN are really icons */
63 static gboolean wm_supports_hidden
= FALSE
;
65 static GdkAtom xa__NET_SUPPORTED
= GDK_NONE
;
66 static GdkAtom xa_WM_STATE
= GDK_NONE
;
67 static GdkAtom xa_WM_NAME
= GDK_NONE
;
68 static GdkAtom xa_WM_ICON_NAME
= GDK_NONE
;
69 static GdkAtom xa_UTF8_STRING
= GDK_NONE
;
70 static GdkAtom xa_TEXT
= GDK_NONE
;
71 static GdkAtom xa__NET_WM_VISIBLE_NAME
= GDK_NONE
;
72 static GdkAtom xa__NET_WM_ICON_NAME
= GDK_NONE
;
73 static GdkAtom xa__NET_CLIENT_LIST
= GDK_NONE
;
74 static GdkAtom xa__NET_WM_ICON_GEOMETRY
= GDK_NONE
;
75 static GdkAtom xa__NET_WM_STATE
= GDK_NONE
;
76 static GdkAtom xa__NET_WM_STATE_HIDDEN
= GDK_NONE
;
78 /* We have selected destroy and property events on every window in
81 static GHashTable
*known
= NULL
; /* XID -> IconWindow */
83 /* Static prototypes */
84 static void remove_window(Window win
);
85 static void tasklist_update(gboolean to_empty
);
86 static GdkFilterReturn
window_filter(GdkXEvent
*xevent
,
89 static guint
xid_hash(XID
*xid
);
90 static gboolean
xid_equal(XID
*a
, XID
*b
);
91 static void state_changed(IconWindow
*win
);
92 static void show_icon(IconWindow
*win
);
93 static void icon_win_free(IconWindow
*win
);
94 static void update_style(gpointer key
, gpointer data
, gpointer user_data
);
95 static void update_supported(void);
97 /****************************************************************
98 * EXTERNAL INTERFACE *
99 ****************************************************************/
101 void tasklist_set_active(gboolean active
)
103 static gboolean need_init
= TRUE
;
104 static gboolean tasklist_active
= FALSE
;
106 if (active
== tasklist_active
)
108 tasklist_active
= active
;
114 root
= gdk_get_default_root_window();
116 xa__NET_SUPPORTED
= gdk_atom_intern("_NET_SUPPORTED", FALSE
);
117 xa_WM_STATE
= gdk_atom_intern("WM_STATE", FALSE
);
118 xa_WM_ICON_NAME
= gdk_atom_intern("WM_ICON_NAME", FALSE
);
119 xa_WM_NAME
= gdk_atom_intern("WM_NAME", FALSE
);
120 xa_UTF8_STRING
= gdk_atom_intern("UTF8_STRING", FALSE
);
121 xa_TEXT
= gdk_atom_intern("TEXT", FALSE
);
122 xa__NET_CLIENT_LIST
=
123 gdk_atom_intern("_NET_CLIENT_LIST", FALSE
);
124 xa__NET_WM_VISIBLE_NAME
=
125 gdk_atom_intern("_NET_WM_VISIBLE_NAME", FALSE
);
126 xa__NET_WM_ICON_NAME
=
127 gdk_atom_intern("_NET_WM_ICON_NAME", FALSE
);
128 xa__NET_WM_ICON_GEOMETRY
=
129 gdk_atom_intern("_NET_WM_ICON_GEOMETRY", FALSE
);
130 xa__NET_WM_STATE
= gdk_atom_intern("_NET_WM_STATE", FALSE
);
131 xa__NET_WM_STATE_HIDDEN
=
132 gdk_atom_intern("_NET_WM_STATE_HIDDEN", FALSE
);
134 known
= g_hash_table_new_full((GHashFunc
) xid_hash
,
135 (GEqualFunc
) xid_equal
,
137 (GDestroyNotify
) icon_win_free
);
138 gdk_window_set_events(root
, gdk_window_get_events(root
) |
139 GDK_PROPERTY_CHANGE_MASK
);
145 gdk_window_add_filter(NULL
, window_filter
, NULL
);
149 gdk_window_remove_filter(NULL
, window_filter
, NULL
);
151 tasklist_update(!active
);
154 /* User has changes the colours in the options box... */
155 void tasklist_style_changed(void)
158 g_hash_table_foreach(known
, update_style
, NULL
);
161 /****************************************************************
162 * INTERNAL FUNCTIONS *
163 ****************************************************************/
165 static void icon_win_free(IconWindow
*win
)
167 g_return_if_fail(win
->widget
== NULL
);
168 g_return_if_fail(win
->label
== NULL
);
175 static guint
xid_hash(XID
*xid
)
181 static gboolean
xid_equal(XID
*a
, XID
*b
)
186 static int wincmp(const void *a
, const void *b
)
188 const Window
*aw
= a
;
189 const Window
*bw
= b
;
199 /* Read the list of WINDOWs from (xwindow,atom), returning them
200 * in a (sorted) Array of Windows. On error, an empty array is
202 * Free the array afterwards.
204 static GArray
*get_window_list(Window xwindow
, GdkAtom atom
)
215 array
= g_array_new(FALSE
, FALSE
, sizeof(Window
));
217 gdk_error_trap_push();
219 result
= XGetWindowProperty(gdk_display
,
221 gdk_x11_atom_to_xatom(atom
),
223 False
, XA_WINDOW
, &type
, &format
, &nitems
,
224 &bytes_after
, (guchar
**)&data
);
225 err
= gdk_error_trap_pop();
227 if (err
!= Success
|| result
!= Success
)
230 if (type
== XA_WINDOW
)
232 for (i
= 0; i
< nitems
; i
++)
233 g_array_append_val(array
, data
[i
]);
236 g_array_sort(array
, wincmp
);
244 static gchar
*get_str(IconWindow
*win
, GdkAtom atom
)
250 char *data
, *str
= NULL
;
252 if (XGetWindowProperty(gdk_display
, win
->xwindow
,
253 gdk_x11_atom_to_xatom(atom
),
256 &rtype
, &format
, &nitems
,
257 &bytes_after
, (guchar
**) &data
) == Success
&& data
)
260 str
= g_strdup(data
);
267 static void get_icon_name(IconWindow
*win
)
269 null_g_free(&win
->text
);
271 win
->text
= get_str(win
, xa__NET_WM_ICON_NAME
);
273 win
->text
= get_str(win
, xa__NET_WM_VISIBLE_NAME
);
275 win
->text
= get_str(win
, xa_WM_ICON_NAME
);
277 win
->text
= get_str(win
, xa_WM_NAME
);
279 win
->text
= g_strdup(_("Window"));
282 /* Call from within error_push/pop */
283 static void window_check_status(IconWindow
*win
)
290 gboolean iconic
= FALSE
;
292 if (wm_supports_hidden
&& XGetWindowProperty(gdk_display
, win
->xwindow
,
293 gdk_x11_atom_to_xatom(xa__NET_WM_STATE
),
296 &type
, &format
, &nitems
,
297 &bytes_after
, (guchar
**) &data
) == Success
&& data
)
302 for (i
= 0; i
< nitems
; i
++)
304 state
= gdk_x11_xatom_to_atom((Atom
) data
[i
]);
305 if (state
== xa__NET_WM_STATE_HIDDEN
)
313 else if (XGetWindowProperty(gdk_display
, win
->xwindow
,
314 gdk_x11_atom_to_xatom(xa_WM_STATE
),
316 gdk_x11_atom_to_xatom(xa_WM_STATE
),
317 &type
, &format
, &nitems
,
318 &bytes_after
, (guchar
**) &data
) == Success
&& data
)
320 iconic
= data
[0] == 3;
326 if (win
->iconified
== iconic
)
329 win
->iconified
= iconic
;
334 /* Called for all events on all windows */
335 static GdkFilterReturn
window_filter(GdkXEvent
*xevent
,
339 XEvent
*xev
= (XEvent
*) xevent
;
342 if (xev
->type
== PropertyNotify
)
344 GdkAtom atom
= gdk_x11_xatom_to_atom(xev
->xproperty
.atom
);
345 Window win
= ((XPropertyEvent
*) xev
)->window
;
347 if (atom
== xa_WM_STATE
|| atom
== xa__NET_WM_STATE
)
349 w
= g_hash_table_lookup(known
, &win
);
353 gdk_error_trap_push();
354 window_check_status(w
);
355 if (gdk_error_trap_pop() != Success
)
356 g_hash_table_remove(known
, &win
);
359 else if (atom
== xa__NET_CLIENT_LIST
)
360 tasklist_update(FALSE
);
361 else if (atom
== xa__NET_SUPPORTED
)
365 return GDK_FILTER_CONTINUE
;
368 /* Window has been added to list of managed windows */
369 static void add_window(Window win
)
373 /* g_print("[ New window %ld ]\n", (long) win); */
375 w
= g_hash_table_lookup(known
, &win
);
379 XWindowAttributes attr
;
381 gdk_error_trap_push();
383 XGetWindowAttributes(gdk_display
, win
, &attr
);
385 if (gdk_error_trap_pop() != Success
)
387 gdk_error_trap_push();
389 XSelectInput(gdk_display
, win
, attr
.your_event_mask
|
394 if (gdk_error_trap_pop() != Success
)
397 w
= g_new(IconWindow
, 1);
402 w
->iconified
= FALSE
;
404 g_hash_table_insert(known
, &w
->xwindow
, w
);
407 gdk_error_trap_push();
409 window_check_status(w
);
415 if (gdk_error_trap_pop() != Success
)
416 g_hash_table_remove(known
, &win
);
419 /* Window is no longer managed, but hasn't been destroyed yet */
420 static void remove_window(Window win
)
424 /* g_print("[ Remove window %ld ]\n", (long) win); */
426 w
= g_hash_table_lookup(known
, &win
);
431 w
->iconified
= FALSE
;
435 g_hash_table_remove(known
, &win
);
439 /* Make sure the window list is up-to-date. Call once to start, and then
440 * everytime _NET_CLIENT_LIST changes.
441 * If 'to_empty' is set them pretend all windows have disappeared.
443 static void tasklist_update(gboolean to_empty
)
445 static GArray
*old_mapping
= NULL
;
446 GArray
*mapping
= NULL
;
451 old_mapping
= g_array_new(FALSE
, FALSE
, sizeof(Window
));
455 mapping
= g_array_new(FALSE
, FALSE
, sizeof(Window
));
457 mapping
= get_window_list(gdk_x11_get_default_root_xwindow(),
458 gdk_atom_intern("_NET_CLIENT_LIST", FALSE
));
462 while (new_i
< mapping
->len
&& old_i
< old_mapping
->len
)
464 Window
new = g_array_index(mapping
, Window
, new_i
);
465 Window old
= g_array_index(old_mapping
, Window
, old_i
);
483 while (new_i
< mapping
->len
)
485 add_window(g_array_index(mapping
, Window
, new_i
));
488 while (old_i
< old_mapping
->len
)
490 remove_window(g_array_index(old_mapping
, Window
, old_i
));
494 g_array_free(old_mapping
, TRUE
);
495 old_mapping
= mapping
;
498 /* Called when the user clicks on the button */
499 static void uniconify(IconWindow
*win
)
501 XClientMessageEvent sev
;
503 sev
.type
= ClientMessage
;
504 sev
.display
= gdk_display
;
506 sev
.window
= win
->xwindow
;
507 sev
.message_type
= gdk_x11_atom_to_xatom(
508 gdk_atom_intern("_NET_ACTIVE_WINDOW", FALSE
));
511 gdk_error_trap_push();
513 XSendEvent(gdk_display
, DefaultRootWindow(gdk_display
), False
,
514 SubstructureNotifyMask
| SubstructureRedirectMask
,
516 XSync(gdk_display
, False
);
518 gdk_error_trap_pop();
521 static gint drag_start_x
= -1;
522 static gint drag_start_y
= -1;
523 static gboolean drag_started
= FALSE
;
524 static gint drag_off_x
= -1;
525 static gint drag_off_y
= -1;
527 static void icon_button_press(GtkWidget
*widget
,
528 GdkEventButton
*event
,
531 if (event
->button
== 1)
533 drag_start_x
= event
->x_root
;
534 drag_start_y
= event
->y_root
;
535 drag_started
= FALSE
;
537 drag_off_x
= event
->x
;
538 drag_off_y
= event
->y
;
542 static gboolean
icon_motion_notify(GtkWidget
*widget
,
543 GdkEventMotion
*event
,
546 if (event
->state
& GDK_BUTTON1_MASK
)
548 int dx
= event
->x_root
- drag_start_x
;
549 int dy
= event
->y_root
- drag_start_y
;
553 if (abs(dx
) < 5 && abs(dy
) < 5)
558 fixed_move_fast(GTK_FIXED(win
->widget
->parent
),
560 event
->x_root
- drag_off_x
,
561 event
->y_root
- drag_off_y
);
567 static void button_released(GtkWidget
*widget
, IconWindow
*win
)
573 static GdkColormap
* get_cmap(GdkPixmap
*pixmap
)
577 cmap
= gdk_drawable_get_colormap(pixmap
);
579 g_object_ref(G_OBJECT(cmap
));
583 if (gdk_drawable_get_depth(pixmap
) == 1)
585 /* Masks don't need colourmaps */
590 /* Try system cmap */
591 cmap
= gdk_colormap_get_system();
592 g_object_ref(G_OBJECT(cmap
));
596 /* Be sure we aren't going to blow up due to visual mismatch */
597 if (cmap
&& (gdk_colormap_get_visual(cmap
)->depth
!=
598 gdk_drawable_get_depth(pixmap
)))
604 /* Copy a pixmap from the server to a client-side pixbuf */
605 static GdkPixbuf
* pixbuf_from_pixmap(Pixmap xpixmap
)
607 GdkDrawable
*drawable
;
614 drawable
= gdk_xid_table_lookup(xpixmap
);
617 g_object_ref(G_OBJECT(drawable
));
619 drawable
= gdk_pixmap_foreign_new(xpixmap
);
621 cmap
= get_cmap(drawable
);
623 /* GDK is supposed to do this but doesn't in GTK 2.0.2,
626 gdk_drawable_get_size(drawable
, &width
, &height
);
628 retval
= gdk_pixbuf_get_from_drawable(NULL
, drawable
, cmap
,
629 0, 0, 0, 0, width
, height
);
632 g_object_unref(G_OBJECT(cmap
));
633 g_object_unref(G_OBJECT(drawable
));
638 /* Creates a new masked pixbuf from a non-masked pixbuf and a mask */
639 static GdkPixbuf
* apply_mask(GdkPixbuf
*pixbuf
, GdkPixbuf
*mask
)
643 GdkPixbuf
*with_alpha
;
649 w
= MIN(gdk_pixbuf_get_width(mask
), gdk_pixbuf_get_width(pixbuf
));
650 h
= MIN(gdk_pixbuf_get_height(mask
), gdk_pixbuf_get_height(pixbuf
));
652 with_alpha
= gdk_pixbuf_add_alpha(pixbuf
, FALSE
, 0, 0, 0);
654 dest
= gdk_pixbuf_get_pixels(with_alpha
);
655 src
= gdk_pixbuf_get_pixels(mask
);
657 dest_stride
= gdk_pixbuf_get_rowstride(with_alpha
);
658 src_stride
= gdk_pixbuf_get_rowstride(mask
);
666 guchar
*s
= src
+ i
* src_stride
+ j
* 3;
667 guchar
*d
= dest
+ i
* dest_stride
+ j
* 4;
669 /* s[0] == s[1] == s[2], they are 255 if the bit was
673 d
[3] = 0; /* transparent */
675 d
[3] = 255; /* opaque */
686 /* Return a suitable icon for this window. unref the result.
687 * Never returns NULL.
689 static GdkPixbuf
*get_image_for(IconWindow
*win
)
691 static MaskedPixmap
*default_icon
= NULL
;
692 Pixmap pixmap
= None
;
695 GdkPixbuf
*retval
= NULL
;
697 /* Try the pixmap and mask in the old WMHints... */
698 gdk_error_trap_push();
699 hints
= XGetWMHints(gdk_display
, win
->xwindow
);
703 if (hints
->flags
& IconPixmapHint
)
704 pixmap
= hints
->icon_pixmap
;
705 if (hints
->flags
& IconMaskHint
)
706 mask
= hints
->icon_mask
;
714 GdkPixbuf
*mask_pb
= NULL
;
716 retval
= pixbuf_from_pixmap(pixmap
);
718 if (retval
&& mask
!= None
)
719 mask_pb
= pixbuf_from_pixmap(mask
);
725 masked
= apply_mask(retval
, mask_pb
);
726 g_object_unref(G_OBJECT(mask_pb
));
730 g_object_unref(G_OBJECT(retval
));
738 gdk_error_trap_pop();
743 default_icon
= load_pixmap("iconified");
745 retval
= default_icon
->pixbuf
;
746 g_object_ref(retval
);
752 /* A window has been iconified -- display it on the screen */
753 static void show_icon(IconWindow
*win
)
758 g_return_if_fail(win
->widget
== NULL
);
759 g_return_if_fail(win
->label
== NULL
);
761 win
->widget
= gtk_button_new();
762 vbox
= gtk_vbox_new(FALSE
, 0);
763 gtk_container_add(GTK_CONTAINER(win
->widget
), vbox
);
765 pixbuf
= get_image_for(win
);
767 gtk_box_pack_start(GTK_BOX(vbox
), gtk_image_new_from_pixbuf(pixbuf
),
769 g_object_unref(pixbuf
);
771 win
->label
= wrapped_label_new(win
->text
, 180);
773 update_style(NULL
, win
, NULL
);
775 gtk_box_pack_start(GTK_BOX(vbox
), win
->label
, FALSE
, TRUE
, 0);
777 gtk_widget_add_events(win
->widget
, GDK_BUTTON1_MOTION_MASK
);
778 g_signal_connect(win
->widget
, "button-press-event",
779 G_CALLBACK(icon_button_press
), win
);
780 g_signal_connect(win
->widget
, "motion-notify-event",
781 G_CALLBACK(icon_motion_notify
), win
);
782 g_signal_connect(win
->widget
, "released",
783 G_CALLBACK(button_released
), win
);
785 gtk_widget_show_all(vbox
); /* So the size comes out right */
786 pinboard_add_widget(win
->widget
);
787 gtk_widget_show(win
->widget
);
790 /* A window has been destroyed/expanded -- remove its icon */
791 static void hide_icon(IconWindow
*win
)
793 g_return_if_fail(win
->widget
!= NULL
);
795 gtk_widget_destroy(win
->widget
);
800 static void state_changed(IconWindow
*win
)
812 /* Set the _NET_WM_ICON_GEOMETRY property, which indicates where this window
813 * will be iconified to. Should be inside a push/pop.
815 static void set_iconify_pos(IconWindow
*win
)
819 data
[0] = iconify_next_x
;
820 data
[1] = iconify_next_y
;
824 XChangeProperty(gdk_display
, win
->xwindow
,
825 gdk_x11_atom_to_xatom(xa__NET_WM_ICON_GEOMETRY
),
826 XA_CARDINAL
, 32, PropModeReplace
, (guchar
*) data
, 4);
830 static void update_style(gpointer key
, gpointer data
, gpointer user_data
)
832 IconWindow
*win
= (IconWindow
*) data
;
835 widget_modify_font(win
->label
, pinboard_font
);
838 /* Find out what the new window manager can do... */
839 static void update_supported(void)
848 gboolean old_supports_hidden
= wm_supports_hidden
;
850 wm_supports_hidden
= FALSE
;
852 gdk_error_trap_push();
854 result
= XGetWindowProperty(gdk_display
,
855 gdk_x11_get_default_root_xwindow(),
856 gdk_x11_atom_to_xatom(xa__NET_SUPPORTED
),
858 False
, XA_ATOM
, &type
, &format
, &nitems
,
859 &bytes_after
, (guchar
**)&data
);
860 err
= gdk_error_trap_pop();
862 if (err
!= Success
|| result
!= Success
)
865 for (i
= 0; i
< nitems
; i
++)
867 GdkAtom atom
= gdk_x11_xatom_to_atom(data
[i
]);
869 if (atom
== xa__NET_WM_STATE_HIDDEN
)
870 wm_supports_hidden
= TRUE
;
876 if (wm_supports_hidden
!= old_supports_hidden
)
878 tasklist_update(TRUE
);
879 tasklist_update(FALSE
);