Initial import.
[lxde/lxpanel.git] / src / plugins / taskbar.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <stdlib.h>
5
6 #include <X11/Xlib.h>
7 #include <X11/Xutil.h>
8 //#include <X11/xpm.h>
9
10 #include <gdk-pixbuf/gdk-pixbuf.h>
11 #include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
12 #include <gdk/gdk.h>
13
14
15
16 #include "panel.h"
17 #include "misc.h"
18 #include "plugin.h"
19 #include "icon.xpm"
20 #include "gtkbar.h"
21
22 /*
23 * 2006.09.10 modified by Hong Jen Yee (PCMan) pcman.tw (AT) gmail.com
24 * Following features are added:
25 * 1. Add XUrgencyHint support. (Flashing task bar buttons, can be disabled)
26 */
27
28 //#define DEBUG
29 #include "dbg.h"
30
31 struct _taskbar;
32 typedef struct _task{
33 struct _taskbar *tb;
34 struct task *next;
35 Window win;
36 char *name, *iname;
37 GtkWidget *button, *label, *eb;
38 GtkWidget *image;
39
40 GdkPixbuf *pixbuf;
41
42 int refcount;
43 XClassHint ch;
44 int pos_x;
45 int width;
46 int desktop;
47 net_wm_state nws;
48 net_wm_window_type nwwt;
49 guint flash_timeout;
50 unsigned int focused:1;
51 unsigned int iconified:1;
52 unsigned int urgency:1;
53 unsigned int using_netwm_icon:1;
54 unsigned int flash:1;
55 unsigned int flash_state:1;
56 } task;
57
58
59
60 typedef struct _taskbar{
61 plugin *plug;
62 Window *wins;
63 Window topxwin;
64 int win_num;
65 GHashTable *task_list;
66 GtkWidget *hbox, *bar, *space, *menu;
67 GtkTooltips *tips;
68 GdkPixbuf *gen_pixbuf;
69 GtkStateType normal_state;
70 GtkStateType focused_state;
71 int num_tasks;
72 int task_width;
73 int vis_task_num;
74 int req_width;
75 int hbox_width;
76 int spacing;
77 int cur_desk;
78 task *focused;
79 task *ptk;
80 task *menutask;
81 char **desk_names;
82 int desk_namesno;
83 int desk_num;
84 guint dnd_activate;
85
86 unsigned int iconsize;
87 unsigned int task_width_max;
88 unsigned int accept_skip_pager : 1;
89 unsigned int show_iconified : 1;
90 unsigned int show_mapped : 1;
91 unsigned int show_all_desks : 1;
92 unsigned int tooltips : 1;
93 unsigned int icons_only : 1;
94 unsigned int use_mouse_wheel : 1;
95 unsigned int use_urgency_hint : 1;
96 } taskbar;
97
98
99 static gchar *taskbar_rc = "style 'taskbar-style'\n"
100 "{\n"
101 "GtkWidget::focus-line-width = 0\n"
102 "GtkWidget::focus-padding = 0\n"
103 "GtkButton::default-border = { 0, 0, 0, 0 }\n"
104 "GtkButton::default-outside-border = { 0, 0, 0, 0 }\n"
105 "GtkButton::default_border = { 0, 0, 0, 0 }\n"
106 "GtkButton::default_outside_border = { 0, 0, 0, 0 }\n"
107 "}\n"
108 "widget '*.taskbar.*' style 'taskbar-style'";
109
110 static gboolean use_net_active=FALSE;
111
112 #define DRAG_ACTIVE_DELAY 1000
113
114
115
116 #define TASK_WIDTH_MAX 200
117 #define TASK_PADDING 4
118 static void tk_display(taskbar *tb, task *tk);
119 static void tb_propertynotify(taskbar *tb, XEvent *ev);
120 static GdkFilterReturn tb_event_filter( XEvent *, GdkEvent *, taskbar *);
121 static void taskbar_destructor(plugin *p);
122
123 static gboolean tk_has_urgency( task* tk );
124
125 static void tk_flash_window( task *tk );
126 static void tk_unflash_window( task *tk );
127 static void tk_raise_window( task *tk, guint32 time );
128
129 #define TASK_VISIBLE(tb, tk) \
130 ((tk)->desktop == (tb)->cur_desk || (tk)->desktop == -1 /* 0xFFFFFFFF */ )
131
132 static int
133 task_visible(taskbar *tb, task *tk)
134 {
135 ENTER;
136 if (tk->desktop != -1 && !tb->show_all_desks && tk->desktop != tb->cur_desk)
137 RET(0);
138 if (tk->iconified) {
139 if (!tb->show_iconified)
140 RET(0);
141 } else {
142 if (!tb->show_mapped)
143 RET(0);
144 }
145 RET(1);
146 }
147
148 static int
149 accept_net_wm_state(net_wm_state *nws, int accept_skip_pager)
150 {
151 ENTER;
152 RET(!(nws->skip_taskbar || (accept_skip_pager && nws->skip_pager)));
153 }
154
155 static int
156 accept_net_wm_window_type(net_wm_window_type *nwwt)
157 {
158 ENTER;
159 RET(!(nwwt->desktop || nwwt->dock || nwwt->splash));
160 }
161
162
163
164 inline static void
165 tk_free_names(task *tk)
166 {
167 ENTER;
168 DBG("tk->name %s\n", tk->name);
169 DBG("tk->iname %s\n", tk->iname);
170 g_free(tk->name);
171 g_free(tk->iname);
172
173 tk->name = tk->iname = NULL;
174 RET();
175 }
176
177 static void
178 tk_set_names(task *tk)
179 {
180 char *name;
181
182 ENTER;
183 tk_free_names(tk);
184
185 /*name = get_utf8_property(tk->win, a_NET_WM_VISIBLE_NAME);
186 DBG2("a_NET_WM_VISIBLE_NAME:%s\n", name);
187 if (!name) {
188 */
189 name = get_utf8_property(tk->win, a_NET_WM_NAME);
190 DBG("a_NET_WM_NAME:%s\n", name);
191 if (!name) {
192 name = get_textproperty(tk->win, XA_WM_NAME);
193 DBG("XA_WM_NAME:%s\n", name);
194 }
195
196 if (name) {
197 tk->name = g_strdup_printf(" %s ", name);
198 tk->iname = g_strdup_printf("[%s]", name);
199 g_free(name);
200 name = tk->iconified ? tk->iname : tk->name;
201 }
202 gtk_label_set_text(GTK_LABEL(tk->label), name);
203 if (tk->tb->tooltips)
204 gtk_tooltips_set_tip(tk->tb->tips, tk->button, tk->name, NULL);
205 RET();
206 }
207
208
209
210 static task *
211 find_task (taskbar * tb, Window win)
212 {
213 ENTER;
214 RET(g_hash_table_lookup(tb->task_list, &win));
215 }
216
217
218 static void
219 del_task (taskbar * tb, task *tk, int hdel)
220 {
221 ENTER;
222 DBG("deleting(%d) %08x %s\n", hdel, tk->win, tk->name);
223 if( tk->flash_timeout )
224 g_source_remove( tk->flash_timeout );
225 gtk_widget_destroy(tk->button);
226 tb->num_tasks--;
227 tk_free_names(tk);
228 if (tb->focused == tk)
229 tb->focused = NULL;
230 if (hdel)
231 g_hash_table_remove(tb->task_list, &tk->win);
232 g_free(tk);
233 RET();
234 }
235
236
237
238 static GdkColormap*
239 get_cmap (GdkPixmap *pixmap)
240 {
241 GdkColormap *cmap;
242
243 ENTER;
244 cmap = gdk_drawable_get_colormap (pixmap);
245 if (cmap)
246 g_object_ref (G_OBJECT (cmap));
247
248 if (cmap == NULL)
249 {
250 if (gdk_drawable_get_depth (pixmap) == 1)
251 {
252 /* try null cmap */
253 cmap = NULL;
254 }
255 else
256 {
257 /* Try system cmap */
258 GdkScreen *screen = gdk_drawable_get_screen (GDK_DRAWABLE (pixmap));
259 cmap = gdk_screen_get_system_colormap (screen);
260 g_object_ref (G_OBJECT (cmap));
261 }
262 }
263
264 /* Be sure we aren't going to blow up due to visual mismatch */
265 if (cmap &&
266 (gdk_colormap_get_visual (cmap)->depth !=
267 gdk_drawable_get_depth (pixmap)))
268 cmap = NULL;
269
270 RET(cmap);
271 }
272
273 static GdkPixbuf*
274 _wnck_gdk_pixbuf_get_from_pixmap (GdkPixbuf *dest,
275 Pixmap xpixmap,
276 int src_x,
277 int src_y,
278 int dest_x,
279 int dest_y,
280 int width,
281 int height)
282 {
283 GdkDrawable *drawable;
284 GdkPixbuf *retval;
285 GdkColormap *cmap;
286
287 ENTER;
288 retval = NULL;
289
290 drawable = gdk_xid_table_lookup (xpixmap);
291
292 if (drawable)
293 g_object_ref (G_OBJECT (drawable));
294 else
295 drawable = gdk_pixmap_foreign_new (xpixmap);
296
297 cmap = get_cmap (drawable);
298
299 /* GDK is supposed to do this but doesn't in GTK 2.0.2,
300 * fixed in 2.0.3
301 */
302 if (width < 0)
303 gdk_drawable_get_size (drawable, &width, NULL);
304 if (height < 0)
305 gdk_drawable_get_size (drawable, NULL, &height);
306
307 retval = gdk_pixbuf_get_from_drawable (dest,
308 drawable,
309 cmap,
310 src_x, src_y,
311 dest_x, dest_y,
312 width, height);
313
314 if (cmap)
315 g_object_unref (G_OBJECT (cmap));
316 g_object_unref (G_OBJECT (drawable));
317
318 RET(retval);
319 }
320
321 static GdkPixbuf*
322 apply_mask (GdkPixbuf *pixbuf,
323 GdkPixbuf *mask)
324 {
325 int w, h;
326 int i, j;
327 GdkPixbuf *with_alpha;
328 guchar *src;
329 guchar *dest;
330 int src_stride;
331 int dest_stride;
332
333 ENTER;
334 w = MIN (gdk_pixbuf_get_width (mask), gdk_pixbuf_get_width (pixbuf));
335 h = MIN (gdk_pixbuf_get_height (mask), gdk_pixbuf_get_height (pixbuf));
336
337 with_alpha = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
338
339 dest = gdk_pixbuf_get_pixels (with_alpha);
340 src = gdk_pixbuf_get_pixels (mask);
341
342 dest_stride = gdk_pixbuf_get_rowstride (with_alpha);
343 src_stride = gdk_pixbuf_get_rowstride (mask);
344
345 i = 0;
346 while (i < h)
347 {
348 j = 0;
349 while (j < w)
350 {
351 guchar *s = src + i * src_stride + j * 3;
352 guchar *d = dest + i * dest_stride + j * 4;
353
354 /* s[0] == s[1] == s[2], they are 255 if the bit was set, 0
355 * otherwise
356 */
357 if (s[0] == 0)
358 d[3] = 0; /* transparent */
359 else
360 d[3] = 255; /* opaque */
361
362 ++j;
363 }
364
365 ++i;
366 }
367
368 RET(with_alpha);
369 }
370
371
372 static void
373 free_pixels (guchar *pixels, gpointer data)
374 {
375 ENTER;
376 g_free (pixels);
377 RET();
378 }
379
380
381 static guchar *
382 argbdata_to_pixdata (gulong *argb_data, int len)
383 {
384 guchar *p, *ret;
385 int i;
386
387 ENTER;
388 ret = p = g_new (guchar, len * 4);
389 if (!ret)
390 RET(NULL);
391 /* One could speed this up a lot. */
392 i = 0;
393 while (i < len) {
394 guint32 argb;
395 guint32 rgba;
396
397 argb = argb_data[i];
398 rgba = (argb << 8) | (argb >> 24);
399
400 *p = rgba >> 24;
401 ++p;
402 *p = (rgba >> 16) & 0xff;
403 ++p;
404 *p = (rgba >> 8) & 0xff;
405 ++p;
406 *p = rgba & 0xff;
407 ++p;
408
409 ++i;
410 }
411 RET(ret);
412 }
413
414
415 static GdkPixbuf *
416 get_netwm_icon(Window tkwin, int iw, int ih)
417 {
418 gulong *data;
419 GdkPixbuf *ret = NULL;
420 int n;
421
422 ENTER;
423 data = get_xaproperty(tkwin, a_NET_WM_ICON, XA_CARDINAL, &n);
424 DBG("icon size = %d data = %p w=%ld h=%ld\n", n, data, data[0], data[1]);
425
426 if (0) {
427 gulong *tmp;
428 int len;
429
430 len = n/sizeof(gulong);
431 tmp = data;
432 while (len > 2) {
433 int size = tmp[0] * tmp[1];
434 DBG("sub-icon: %dx%d %d bytes\n", tmp[0], tmp[1], size * 4);
435 len -= size + 2;
436 tmp += size;
437 }
438 }
439
440 if (data) {
441 if (n > 2*sizeof(guint32)) {
442 guchar *p;
443 GdkPixbuf *src;
444 int w, h;
445
446 w = data[0];
447 h = data[1];
448 p = argbdata_to_pixdata(data + 2, w * h);
449 if (!p)
450 RET(NULL);
451 src = gdk_pixbuf_new_from_data (p, GDK_COLORSPACE_RGB, TRUE,
452 8, w, h, w * 4, free_pixels, NULL);
453 if (src == NULL)
454 RET(NULL);
455 ret = gdk_pixbuf_scale_ratio (src, iw, ih, GDK_INTERP_HYPER, TRUE);
456 g_object_unref(src);
457 }
458 XFree (data);
459 }
460 RET(ret);
461 }
462
463 static GdkPixbuf *
464 get_wm_icon(Window tkwin, int iw, int ih)
465 {
466 XWMHints *hints;
467 Pixmap xpixmap = None, xmask = None;
468 Window win;
469 unsigned int w, h;
470 int sd;
471 GdkPixbuf *ret, *masked, *pixmap, *mask = NULL;
472
473 ENTER;
474 hints = (XWMHints *) get_xaproperty (tkwin, XA_WM_HINTS, XA_WM_HINTS, 0);
475 if (!hints)
476 RET(NULL);
477
478 if ((hints->flags & IconPixmapHint))
479 xpixmap = hints->icon_pixmap;
480 if ((hints->flags & IconMaskHint))
481 xmask = hints->icon_mask;
482
483 XFree(hints);
484 if (xpixmap == None)
485 RET(NULL);
486
487 if (!XGetGeometry (GDK_DISPLAY(), xpixmap, &win, &sd, &sd, &w, &h,
488 (guint *)&sd, (guint *)&sd)) {
489 LOG(LOG_WARN,"XGetGeometry failed for %x pixmap\n", (unsigned int)xpixmap);
490 RET(NULL);
491 }
492 DBG("tkwin=%x icon pixmap w=%d h=%d\n", tkwin, w, h);
493 pixmap = _wnck_gdk_pixbuf_get_from_pixmap (NULL, xpixmap, 0, 0, 0, 0, w, h);
494 if (!pixmap)
495 RET(NULL);
496 if (xmask != None && XGetGeometry (GDK_DISPLAY(), xmask,
497 &win, &sd, &sd, &w, &h, (guint *)&sd, (guint *)&sd)) {
498 mask = _wnck_gdk_pixbuf_get_from_pixmap (NULL, xmask, 0, 0, 0, 0, w, h);
499
500 if (mask) {
501 masked = apply_mask (pixmap, mask);
502 g_object_unref (G_OBJECT (pixmap));
503 g_object_unref (G_OBJECT (mask));
504 pixmap = masked;
505 }
506 }
507 if (!pixmap)
508 RET(NULL);
509 ret = gdk_pixbuf_scale_simple (pixmap, iw, ih, GDK_INTERP_TILES);
510 g_object_unref(pixmap);
511
512 RET(ret);
513 }
514
515 inline static GdkPixbuf*
516 get_generic_icon(taskbar *tb)
517 {
518 ENTER;
519 g_object_ref(tb->gen_pixbuf);
520 RET(tb->gen_pixbuf);
521 }
522
523 static void
524 tk_update_icon (taskbar *tb, task *tk, Atom a)
525 {
526 GdkPixbuf *pixbuf;
527
528 ENTER;
529 g_assert ((tb != NULL) && (tk != NULL));
530 g_return_if_fail(tk != NULL);
531
532 pixbuf = tk->pixbuf;
533 if (a == a_NET_WM_ICON || a == None) {
534 tk->pixbuf = get_netwm_icon(tk->win, tb->iconsize, tb->iconsize);
535 tk->using_netwm_icon = (tk->pixbuf != NULL);
536 }
537 if (!tk->using_netwm_icon)
538 tk->pixbuf = get_wm_icon(tk->win, tb->iconsize, tb->iconsize);
539 if (!tk->pixbuf)
540 tk->pixbuf = get_generic_icon(tb); // always exists
541 if (pixbuf != tk->pixbuf) {
542 if (pixbuf)
543 g_object_unref(pixbuf);
544 }
545 RET();
546 }
547
548 static gboolean on_flash_win( task *tk )
549 {
550 tk->flash_state = !tk->flash_state;
551 gtk_widget_set_state(tk->button,
552 tk->flash_state ? GTK_STATE_SELECTED : tk->tb->normal_state);
553 gtk_widget_queue_draw(tk->button);
554 return TRUE;
555 }
556
557 static void
558 tk_flash_window( task *tk )
559 {
560 gint interval;
561 tk->flash = 1;
562 tk->flash_state = !tk->flash_state;
563 if (tk->flash_timeout)
564 return;
565 g_object_get( gtk_widget_get_settings(tk->button),
566 "gtk-cursor-blink-time", &interval, NULL );
567 tk->flash_timeout = g_timeout_add(interval, (GSourceFunc)on_flash_win, tk);
568 }
569
570 static void
571 tk_unflash_window( task *tk )
572 {
573 tk->flash = tk->flash_state = 0;
574 if (tk->flash_timeout) {
575 g_source_remove(tk->flash_timeout);
576 tk->flash_timeout = 0;
577 }
578 }
579
580 static void
581 tk_raise_window( task *tk, guint32 time )
582 {
583 if (tk->desktop != -1 && tk->desktop != tk->tb->cur_desk){
584 Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, tk->desktop, 0, 0, 0, 0);
585 XSync (gdk_display, False);
586 }
587 if(use_net_active) {
588 Xclimsg(tk->win, a_NET_ACTIVE_WINDOW, 2, time, 0, 0, 0);
589 }
590 else {
591 XRaiseWindow (GDK_DISPLAY(), tk->win);
592 XSetInputFocus (GDK_DISPLAY(), tk->win, RevertToNone, CurrentTime);
593 }
594 DBG("XRaiseWindow %x\n", tk->win);
595 }
596
597 static void
598 tk_callback_leave( GtkWidget *widget, task *tk)
599 {
600 ENTER;
601 gtk_widget_set_state(widget,
602 (tk->focused) ? tk->tb->focused_state : tk->tb->normal_state);
603 RET();
604 }
605
606
607 static void
608 tk_callback_enter( GtkWidget *widget, task *tk )
609 {
610 ENTER;
611 gtk_widget_set_state(widget,
612 (tk->focused) ? tk->tb->focused_state : tk->tb->normal_state);
613 RET();
614 }
615
616 static gboolean delay_active_win(task* tk)
617 {
618 tk_raise_window(tk, CurrentTime);
619 tk->tb->dnd_activate = 0;
620 return FALSE;
621 }
622
623 static gboolean
624 tk_callback_drag_motion( GtkWidget *widget,
625 GdkDragContext *drag_context,
626 gint x, gint y,
627 guint time, task *tk)
628 {
629 /* prevent excessive motion notification */
630 if (!tk->tb->dnd_activate) {
631 tk->tb->dnd_activate = g_timeout_add(DRAG_ACTIVE_DELAY,
632 (GSourceFunc)delay_active_win, tk);
633 }
634 gdk_drag_status (drag_context,0,time);
635 return TRUE;
636 }
637
638 static void
639 tk_callback_drag_leave (GtkWidget *widget,
640 GdkDragContext *drag_context,
641 guint time, task *tk)
642 {
643 if (tk->tb->dnd_activate) {
644 g_source_remove(tk->tb->dnd_activate);
645 tk->tb->dnd_activate = 0;
646 }
647 return;
648 }
649
650 #if 0
651 static gboolean
652 tk_callback_expose(GtkWidget *widget, GdkEventExpose *event, task *tk)
653 {
654 GtkStateType state;
655 ENTER;
656 state = (tk->focused) ? tk->tb->focused_state : tk->tb->normal_state;
657 if (GTK_WIDGET_STATE(widget) != state) {
658 gtk_widget_set_state(widget, state);
659 gtk_widget_queue_draw(widget);
660 } else {
661 if( ! tk->flash || 0 == tk->flash_state ) {
662 gtk_paint_box (widget->style, widget->window,
663 state,
664 (tk->focused) ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
665 &event->area, widget, "button",
666 widget->allocation.x, widget->allocation.y,
667 widget->allocation.width, widget->allocation.height);
668 } else {
669 gdk_draw_rectangle( widget->window,
670 widget->style->bg_gc[GTK_STATE_SELECTED],
671 TRUE, 0, 0,
672 widget->allocation.width,
673 widget->allocation.height );
674 }
675 /*
676 _gtk_button_paint(GTK_BUTTON(widget), &event->area, state,
677 (tk->focused) ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
678 "button", "buttondefault");
679 */
680 gtk_container_propagate_expose(GTK_CONTAINER(widget), GTK_BIN(widget)->child, event);
681 }
682 RET(FALSE);
683 }
684 #endif
685
686 static gint
687 tk_callback_scroll_event (GtkWidget *widget, GdkEventScroll *event, task *tk)
688 {
689 ENTER;
690 if (event->direction == GDK_SCROLL_UP) {
691 GdkWindow *gdkwindow;
692
693 gdkwindow = gdk_xid_table_lookup (tk->win);
694 if (gdkwindow)
695 gdk_window_show (gdkwindow);
696 else
697 XMapRaised (GDK_DISPLAY(), tk->win);
698 XSetInputFocus (GDK_DISPLAY(), tk->win, RevertToNone, CurrentTime);
699 DBG("XMapRaised %x\n", tk->win);
700 } else if (event->direction == GDK_SCROLL_DOWN) {
701 DBG("tb->ptk = %x\n", (tk->tb->ptk) ? tk->tb->ptk->win : 0);
702 XIconifyWindow (GDK_DISPLAY(), tk->win, DefaultScreen(GDK_DISPLAY()));
703 DBG("XIconifyWindow %x\n", tk->win);
704 }
705
706 XSync (gdk_display, False);
707 RET(TRUE);
708 }
709
710 static gboolean
711 tk_callback_button_release_event(GtkWidget *widget, GdkEventButton *event, task *tk)
712 {
713 ENTER;
714 if ((event->type != GDK_BUTTON_RELEASE) || (!GTK_BUTTON(widget)->in_button))
715 RET(FALSE);
716 DBG("win=%x\n", tk->win);
717 if (event->button == 1) {
718 if (tk->iconified) {
719 if(use_net_active) {
720 Xclimsg(tk->win, a_NET_ACTIVE_WINDOW, 2, event->time, 0, 0, 0);
721 } else {
722 GdkWindow *gdkwindow;
723
724 gdkwindow = gdk_xid_table_lookup (tk->win);
725 if (gdkwindow)
726 gdk_window_show (gdkwindow);
727 else
728 XMapRaised (GDK_DISPLAY(), tk->win);
729 XSync (GDK_DISPLAY(), False);
730 DBG("XMapRaised %x\n", tk->win);
731 }
732 } else {
733 DBG("tb->ptk = %x\n", (tk->tb->ptk) ? tk->tb->ptk->win : 0);
734 if (tk->focused || tk == tk->tb->ptk) {
735 //tk->iconified = 1;
736 XIconifyWindow (GDK_DISPLAY(), tk->win, DefaultScreen(GDK_DISPLAY()));
737 DBG("XIconifyWindow %x\n", tk->win);
738 } else {
739 tk_raise_window( tk, event->time );
740 }
741 }
742 } else if (event->button == 2) {
743 Xclimsg(tk->win, a_NET_WM_STATE,
744 2 /*a_NET_WM_STATE_TOGGLE*/,
745 a_NET_WM_STATE_SHADED,
746 0, 0, 0);
747 } else if (event->button == 3) {
748 /*
749 XLowerWindow (GDK_DISPLAY(), tk->win);
750 DBG("XLowerWindow %x\n", tk->win);
751 */
752 tk->tb->menutask = tk;
753 gtk_menu_popup (GTK_MENU (tk->tb->menu), NULL, NULL, NULL, NULL,
754 event->button, event->time);
755
756 }
757 XSync (gdk_display, False);
758 gtk_button_released(GTK_BUTTON(widget));
759 RET(TRUE);
760 }
761
762
763 static void
764 tk_update(gpointer key, task *tk, taskbar *tb)
765 {
766 ENTER;
767 g_assert ((tb != NULL) && (tk != NULL));
768 if (task_visible(tb, tk)) {
769 gtk_widget_set_state (tk->button,
770 (tk->focused) ? tb->focused_state : tb->normal_state);
771 gtk_widget_queue_draw(tk->button);
772 //_gtk_button_set_depressed(GTK_BUTTON(tk->button), tk->focused);
773 gtk_widget_show(tk->button);
774
775 if (tb->tooltips) {
776 //DBG2("tip %x %s\n", tk->win, tk->name);
777 gtk_tooltips_set_tip(tb->tips, tk->button, tk->name, NULL);
778 }
779 RET();
780 }
781 gtk_widget_hide(tk->button);
782 RET();
783 }
784
785 static void
786 tk_display(taskbar *tb, task *tk)
787 {
788 ENTER;
789 tk_update(NULL, tk, tb);
790 RET();
791 }
792
793 static void
794 tb_display(taskbar *tb)
795 {
796 ENTER;
797 if (tb->wins)
798 g_hash_table_foreach(tb->task_list, (GHFunc) tk_update, (gpointer) tb);
799 RET();
800
801 }
802
803 static void
804 tk_build_gui(taskbar *tb, task *tk)
805 {
806 GtkWidget *w1;
807
808 ENTER;
809 g_assert ((tb != NULL) && (tk != NULL));
810
811 /* NOTE
812 * 1. the extended mask is sum of taskbar and pager needs
813 * see bug [ 940441 ] pager loose track of windows
814 *
815 * Do not change event mask to gtk windows spwaned by this gtk client
816 * this breaks gtk internals */
817 if (!FBPANEL_WIN(tk->win))
818 XSelectInput (GDK_DISPLAY(), tk->win, PropertyChangeMask | StructureNotifyMask);
819
820 /* button */
821 //tk->eb = gtk_event_box_new();
822 //gtk_container_set_border_width(GTK_CONTAINER(tk->eb), 0);
823 tk->button = gtk_button_new();
824 gtk_widget_show(tk->button);
825 gtk_container_set_border_width(GTK_CONTAINER(tk->button), 0);
826 gtk_widget_add_events (tk->button, GDK_BUTTON_RELEASE_MASK );
827 g_signal_connect(G_OBJECT(tk->button), "button_release_event",
828 G_CALLBACK(tk_callback_button_release_event), (gpointer)tk);
829 g_signal_connect_after (G_OBJECT (tk->button), "leave",
830 G_CALLBACK (tk_callback_leave), (gpointer) tk);
831 g_signal_connect_after (G_OBJECT (tk->button), "enter",
832 G_CALLBACK (tk_callback_enter), (gpointer) tk);
833 #if 0
834 g_signal_connect_after (G_OBJECT (tk->button), "expose-event",
835 G_CALLBACK (tk_callback_expose), (gpointer) tk);
836 #endif
837 gtk_drag_dest_set( tk->button, 0, NULL, 0, 0);
838 g_signal_connect (G_OBJECT (tk->button), "drag-motion",
839 G_CALLBACK (tk_callback_drag_motion), (gpointer) tk);
840 g_signal_connect (G_OBJECT (tk->button), "drag-leave",
841 G_CALLBACK (tk_callback_drag_leave), (gpointer) tk);
842 if (tb->use_mouse_wheel)
843 g_signal_connect_after(G_OBJECT(tk->button), "scroll-event",
844 G_CALLBACK(tk_callback_scroll_event), (gpointer)tk);
845
846
847 /* pix and name */
848 w1 = tb->plug->panel->my_box_new(FALSE, 1);
849 gtk_container_set_border_width(GTK_CONTAINER(w1), 0);
850
851 /* pix */
852 //get_wmclass(tk);
853 tk_update_icon(tb, tk, None);
854 tk->image = gtk_image_new_from_pixbuf(tk->pixbuf );
855 gtk_widget_show(tk->image);
856 gtk_box_pack_start(GTK_BOX(w1), tk->image, FALSE, FALSE, 0);
857
858 /* name */
859 tk->label = gtk_label_new(tk->iconified ? tk->iname : tk->name);
860 //gtk_label_set_justify(GTK_LABEL(tk->label), GTK_JUSTIFY_LEFT);
861 gtk_label_set_ellipsize(GTK_LABEL(tk->label), PANGO_ELLIPSIZE_END);
862 gtk_misc_set_alignment(GTK_MISC(tk->label), 0.0, 0.5);
863 if (!tb->icons_only)
864 gtk_widget_show(tk->label);
865 gtk_box_pack_start(GTK_BOX(w1), tk->label, TRUE, TRUE, 0);
866 gtk_widget_show(w1);
867 gtk_container_add (GTK_CONTAINER (tk->button), w1);
868
869 //gtk_container_add (GTK_CONTAINER (tk->eb), tk->button);
870 gtk_box_pack_start(GTK_BOX(tb->bar), tk->button, FALSE, TRUE, 0);
871 GTK_WIDGET_UNSET_FLAGS (tk->button, GTK_CAN_FOCUS);
872 GTK_WIDGET_UNSET_FLAGS (tk->button, GTK_CAN_DEFAULT);
873
874 gtk_widget_show(tk->button);
875 if (!task_visible(tb, tk)) {
876 gtk_widget_hide(tk->button);
877 }
878
879 if (tk->urgency) {
880 /* Flash button for window with urgency hint */
881 tk_flash_window(tk);
882 }
883 RET();
884 }
885
886 /* tell to remove element with zero refcount */
887 static gboolean
888 tb_remove_stale_tasks(Window *win, task *tk, gpointer data)
889 {
890 ENTER;
891 if (tk->refcount-- == 0) {
892 //DBG("tb_net_list <del>: 0x%x %s\n", tk->win, tk->name);
893 del_task(tk->tb, tk, 0);
894 RET(TRUE);
895 }
896 RET(FALSE);
897 }
898
899 /*****************************************************
900 * handlers for NET actions *
901 *****************************************************/
902
903
904 static void
905 tb_net_client_list(GtkWidget *widget, taskbar *tb)
906 {
907 int i;
908 task *tk;
909
910 ENTER;
911 if (tb->wins)
912 XFree(tb->wins);
913 tb->wins = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_CLIENT_LIST, XA_WINDOW, &tb->win_num);
914 if (!tb->wins)
915 RET();
916 for (i = 0; i < tb->win_num; i++) {
917 if ((tk = g_hash_table_lookup(tb->task_list, &tb->wins[i]))) {
918 tk->refcount++;
919 } else {
920 net_wm_window_type nwwt;
921 net_wm_state nws;
922
923 get_net_wm_state(tb->wins[i], &nws);
924 if (!accept_net_wm_state(&nws, tb->accept_skip_pager))
925 continue;
926 get_net_wm_window_type(tb->wins[i], &nwwt);
927 if (!accept_net_wm_window_type(&nwwt))
928 continue;
929
930 tk = g_new0(task, 1);
931 tk->refcount = 1;
932 tb->num_tasks++;
933 tk->win = tb->wins[i];
934 tk->tb = tb;
935 tk->iconified = (get_wm_state(tk->win) == IconicState);
936 tk->desktop = get_net_wm_desktop(tk->win);
937 tk->nws = nws;
938 tk->nwwt = nwwt;
939 if( tb->use_urgency_hint && tk_has_urgency(tk)) {
940 tk->urgency = 1;
941 }
942
943 tk_build_gui(tb, tk);
944 tk_set_names(tk);
945 g_hash_table_insert(tb->task_list, &tk->win, tk);
946 DBG("adding %08x(%p) %s\n", tk->win, FBPANEL_WIN(tk->win), tk->name);
947 }
948 }
949
950 /* remove windows that arn't in the NET_CLIENT_LIST anymore */
951 g_hash_table_foreach_remove(tb->task_list, (GHRFunc) tb_remove_stale_tasks, NULL);
952 tb_display(tb);
953 RET();
954 }
955
956
957
958 static void
959 tb_net_current_desktop(GtkWidget *widget, taskbar *tb)
960 {
961 ENTER;
962 tb->cur_desk = get_net_current_desktop();
963 tb_display(tb);
964 RET();
965 }
966
967
968 static void
969 tb_net_number_of_desktops(GtkWidget *widget, taskbar *tb)
970 {
971 ENTER;
972 tb->desk_num = get_net_number_of_desktops();
973 tb_display(tb);
974 RET();
975 }
976
977
978 /* set new active window. if that happens to be us, then remeber
979 * current focus to use it for iconify command */
980 static void
981 tb_net_active_window(GtkWidget *widget, taskbar *tb)
982 {
983 Window *f;
984 task *ntk, *ctk;
985 int drop_old, make_new;
986
987 ENTER;
988 g_assert (tb != NULL);
989 drop_old = make_new = 0;
990 ctk = tb->focused;
991 ntk = NULL;
992 f = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_ACTIVE_WINDOW, XA_WINDOW, 0);
993 DBG("FOCUS=%x\n", f ? *f : 0);
994 if (!f) {
995 drop_old = 1;
996 tb->ptk = NULL;
997 } else {
998 if (*f == tb->topxwin) {
999 if (ctk) {
1000 tb->ptk = ctk;
1001 drop_old = 1;
1002 }
1003 } else {
1004 tb->ptk = NULL;
1005 ntk = find_task(tb, *f);
1006 if (ntk != ctk) {
1007 drop_old = 1;
1008 make_new = 1;
1009 }
1010 }
1011 XFree(f);
1012 }
1013 if (ctk && drop_old) {
1014 ctk->focused = 0;
1015 tb->focused = NULL;
1016 tk_display(tb, ctk);
1017 DBG("old focus was dropped\n");
1018 }
1019 if (ntk && make_new) {
1020 ntk->focused = 1;
1021 tb->focused = ntk;
1022 tk_display(tb, ntk);
1023 DBG("new focus was set\n");
1024 }
1025 RET();
1026 }
1027
1028 /* For older Xlib headers */
1029 #ifndef XUrgencyHint
1030 #define XUrgencyHint (1 << 8)
1031 #endif
1032
1033 static gboolean
1034 tk_has_urgency( task* tk )
1035 {
1036 XWMHints* hints;
1037
1038 tk->urgency = 0;
1039 hints = (XWMHints *) get_xaproperty (tk->win, XA_WM_HINTS, XA_WM_HINTS, 0);
1040 if (hints) {
1041 if (hints->flags & XUrgencyHint) /* Got urgency hint */
1042 tk->urgency = 1;
1043 XFree( hints );
1044 }
1045 return tk->urgency;
1046 }
1047
1048 static void
1049 tb_propertynotify(taskbar *tb, XEvent *ev)
1050 {
1051 Atom at;
1052 Window win;
1053
1054 ENTER;
1055 DBG("win=%x\n", ev->xproperty.window);
1056 at = ev->xproperty.atom;
1057 win = ev->xproperty.window;
1058 if (win != GDK_ROOT_WINDOW()) {
1059 task *tk = find_task(tb, win);
1060
1061 if (!tk) RET();
1062 DBG("win=%x\n", ev->xproperty.window);
1063 if (at == a_NET_WM_DESKTOP) {
1064 DBG("NET_WM_DESKTOP\n");
1065 tk->desktop = get_net_wm_desktop(win);
1066 tb_display(tb);
1067 } else if (at == XA_WM_NAME) {
1068 DBG("WM_NAME\n");
1069 tk_set_names(tk);
1070 //tk_display(tb, tk);
1071 } else if (at == XA_WM_CLASS) {
1072 DBG("WM_CLASS\n");
1073
1074 //get_wmclass(tk);
1075 } else if (at == a_WM_STATE) {
1076 DBG("WM_STATE\n");
1077 /* iconified state changed? */
1078 tk->iconified = (get_wm_state (tk->win) == IconicState);
1079 tk_set_names(tk);
1080 //tk_display(tb, tk);
1081 } else if (at == XA_WM_HINTS) {
1082 /* some windows set their WM_HINTS icon after mapping */
1083 DBG("XA_WM_HINTS\n");
1084 //get_wmclass(tk);
1085 tk_update_icon (tb, tk, XA_WM_HINTS);
1086 gtk_image_set_from_pixbuf (GTK_IMAGE(tk->image), tk->pixbuf);
1087 if (tb->use_urgency_hint) {
1088 if (tk_has_urgency(tk)) {
1089 //tk->urgency = 1;
1090 tk_flash_window(tk);
1091 } else {
1092 //tk->urgency = 0;
1093 tk_unflash_window(tk);
1094 }
1095 }
1096 } else if (at == a_NET_WM_STATE) {
1097 net_wm_state nws;
1098
1099 DBG("_NET_WM_STATE\n");
1100 get_net_wm_state(tk->win, &nws);
1101 if (!accept_net_wm_state(&nws, tb->accept_skip_pager)) {
1102 del_task(tb, tk, 1);
1103 tb_display(tb);
1104 }
1105 } else if (at == a_NET_WM_ICON) {
1106 DBG("_NET_WM_ICON\n");
1107 DBG("#0 %d\n", GDK_IS_PIXBUF (tk->pixbuf));
1108 tk_update_icon (tb, tk, a_NET_WM_ICON);
1109 DBG("#1 %d\n", GDK_IS_PIXBUF (tk->pixbuf));
1110 gtk_image_set_from_pixbuf (GTK_IMAGE(tk->image), tk->pixbuf);
1111 DBG("#2 %d\n", GDK_IS_PIXBUF (tk->pixbuf));
1112 } else if (at == a_NET_WM_WINDOW_TYPE) {
1113 net_wm_window_type nwwt;
1114
1115 DBG("_NET_WM_WINDOW_TYPE\n");
1116 get_net_wm_window_type(tk->win, &nwwt);
1117 if (!accept_net_wm_window_type(&nwwt)) {
1118 del_task(tb, tk, 1);
1119 tb_display(tb);
1120 }
1121 } else {
1122 DBG("at = %d\n", at);
1123 }
1124 }
1125 RET();
1126 }
1127
1128 static GdkFilterReturn
1129 tb_event_filter( XEvent *xev, GdkEvent *event, taskbar *tb)
1130 {
1131
1132 ENTER;
1133 //RET(GDK_FILTER_CONTINUE);
1134 g_assert(tb != NULL);
1135 if (xev->type == PropertyNotify )
1136 tb_propertynotify(tb, xev);
1137 RET(GDK_FILTER_CONTINUE);
1138 }
1139
1140 static void
1141 menu_close_window(GtkWidget *widget, taskbar *tb)
1142 {
1143 ENTER;
1144 DBG("win %x\n", tb->menutask->win);
1145 XSync (GDK_DISPLAY(), 0);
1146 //XKillClient(GDK_DISPLAY(), tb->menutask->win);
1147 Xclimsgwm(tb->menutask->win, a_WM_PROTOCOLS, a_WM_DELETE_WINDOW);
1148 XSync (GDK_DISPLAY(), 0);
1149 RET();
1150 }
1151
1152
1153 static void
1154 menu_raise_window(GtkWidget *widget, taskbar *tb)
1155 {
1156 ENTER;
1157 DBG("win %x\n", tb->menutask->win);
1158 XMapRaised(GDK_DISPLAY(), tb->menutask->win);
1159 RET();
1160 }
1161
1162
1163 static void
1164 menu_iconify_window(GtkWidget *widget, taskbar *tb)
1165 {
1166 ENTER;
1167 DBG("win %x\n", tb->menutask->win);
1168 XIconifyWindow (GDK_DISPLAY(), tb->menutask->win, DefaultScreen(GDK_DISPLAY()));
1169 RET();
1170 }
1171
1172
1173 static GtkWidget *
1174 taskbar_make_menu(taskbar *tb)
1175 {
1176 GtkWidget *mi, *menu;
1177
1178 ENTER;
1179 menu = gtk_menu_new ();
1180
1181 mi = gtk_menu_item_new_with_label ("Raise");
1182 gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
1183 g_signal_connect(G_OBJECT(mi), "activate", (GCallback)menu_raise_window, tb);
1184 gtk_widget_show (mi);
1185
1186 mi = gtk_menu_item_new_with_label ("Iconify");
1187 gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
1188 g_signal_connect(G_OBJECT(mi), "activate", (GCallback)menu_iconify_window, tb);
1189 gtk_widget_show (mi);
1190
1191 /* we want this item to be farest from mouse pointer */
1192 mi = gtk_menu_item_new_with_label ("Close Window");
1193 if (tb->plug->panel->edge == EDGE_BOTTOM)
1194 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi);
1195 else
1196 gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
1197 g_signal_connect(G_OBJECT(mi), "activate", (GCallback)menu_close_window, tb);
1198 gtk_widget_show (mi);
1199
1200 RET(menu);
1201 }
1202
1203
1204 static void
1205 taskbar_build_gui(plugin *p)
1206 {
1207 taskbar *tb = (taskbar *)p->priv;
1208 GtkBarOrientation bo;
1209
1210 ENTER;
1211 bo = (tb->plug->panel->orientation == ORIENT_HORIZ) ? GTK_BAR_HORIZ : GTK_BAR_VERTICAL;
1212 tb->bar = gtk_bar_new(bo, tb->spacing);
1213 if (tb->icons_only) {
1214 gtk_bar_set_max_child_size(GTK_BAR(tb->bar),
1215 GTK_WIDGET(p->panel->box)->allocation.height -2);
1216 } else
1217 gtk_bar_set_max_child_size(GTK_BAR(tb->bar), tb->task_width_max);
1218 gtk_container_add (GTK_CONTAINER (p->pwid), tb->bar);
1219 gtk_widget_show(tb->bar);
1220
1221 tb->gen_pixbuf = gdk_pixbuf_new_from_xpm_data((const char **)icon_xpm);
1222
1223 gdk_window_add_filter(NULL, (GdkFilterFunc)tb_event_filter, tb );
1224
1225 g_signal_connect (G_OBJECT (fbev), "current_desktop",
1226 G_CALLBACK (tb_net_current_desktop), (gpointer) tb);
1227 g_signal_connect (G_OBJECT (fbev), "active_window",
1228 G_CALLBACK (tb_net_active_window), (gpointer) tb);
1229 g_signal_connect (G_OBJECT (fbev), "number_of_desktops",
1230 G_CALLBACK (tb_net_number_of_desktops), (gpointer) tb);
1231 g_signal_connect (G_OBJECT (fbev), "client_list",
1232 G_CALLBACK (tb_net_client_list), (gpointer) tb);
1233
1234 tb->desk_num = get_net_number_of_desktops();
1235 tb->cur_desk = get_net_current_desktop();
1236 tb->focused = NULL;
1237 if (tb->tooltips)
1238 tb->tips = gtk_tooltips_new();
1239
1240 tb->menu = taskbar_make_menu(tb);
1241 gtk_container_set_border_width(GTK_CONTAINER(p->pwid), 0);
1242 gtk_widget_show_all(tb->bar);
1243 RET();
1244 }
1245
1246 void net_active_detect()
1247 {
1248 int nitens;
1249 Atom *data;
1250
1251 data = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_SUPPORTED, XA_ATOM, &nitens);
1252 if (!data)
1253 return;
1254
1255 while (nitens > 0)
1256 if(data[--nitens]==a_NET_ACTIVE_WINDOW) {
1257 use_net_active = TRUE;
1258 break;
1259 }
1260
1261 XFree(data);
1262 }
1263
1264 static int
1265 taskbar_constructor(plugin *p)
1266 {
1267 taskbar *tb;
1268 line s;
1269 GtkRequisition req;
1270
1271
1272 ENTER;
1273 gtk_widget_set_name(p->pwid, "taskbar");
1274 gtk_rc_parse_string(taskbar_rc);
1275 get_button_spacing(&req, GTK_CONTAINER(p->pwid), "");
1276
1277 net_active_detect();
1278
1279 tb = g_new0(taskbar, 1);
1280 tb->plug = p;
1281 p->priv = tb;
1282
1283 if (p->panel->orientation == ORIENT_HORIZ) {
1284 tb->iconsize = GTK_WIDGET(p->panel->box)->allocation.height - req.height;
1285 DBG("pwid height = %d\n", GTK_WIDGET(p->pwid)->allocation.height);
1286 } else
1287 tb->iconsize = 24;
1288 tb->topxwin = p->panel->topxwin;
1289 tb->tooltips = 1;
1290 tb->icons_only = 0;
1291 tb->accept_skip_pager = 1;
1292 tb->show_iconified = 1;
1293 tb->show_mapped = 1;
1294 tb->show_all_desks = 0;
1295 tb->task_width_max = TASK_WIDTH_MAX;
1296 tb->task_list = g_hash_table_new(g_int_hash, g_int_equal);
1297 tb->focused_state = GTK_STATE_ACTIVE;
1298 tb->normal_state = GTK_STATE_NORMAL;
1299 tb->spacing = 1;
1300 tb->use_mouse_wheel = 1;
1301 tb->use_urgency_hint = 1;
1302 s.len = 256;
1303 while (get_line(p->fp, &s) != LINE_BLOCK_END) {
1304 if (s.type == LINE_NONE) {
1305 ERR( "taskbar: illegal token %s\n", s.str);
1306 goto error;
1307 }
1308 if (s.type == LINE_VAR) {
1309 if (!g_ascii_strcasecmp(s.t[0], "tooltips")) {
1310 tb->tooltips = str2num(bool_pair, s.t[1], 1);
1311 } else if (!g_ascii_strcasecmp(s.t[0], "IconsOnly")) {
1312 tb->icons_only = str2num(bool_pair, s.t[1], 0);
1313 } else if (!g_ascii_strcasecmp(s.t[0], "AcceptSkipPager")) {
1314 tb->accept_skip_pager = str2num(bool_pair, s.t[1], 1);
1315 } else if (!g_ascii_strcasecmp(s.t[0], "ShowIconified")) {
1316 tb->show_iconified = str2num(bool_pair, s.t[1], 1);
1317 } else if (!g_ascii_strcasecmp(s.t[0], "ShowMapped")) {
1318 tb->show_mapped = str2num(bool_pair, s.t[1], 1);
1319 } else if (!g_ascii_strcasecmp(s.t[0], "ShowAllDesks")) {
1320 tb->show_all_desks = str2num(bool_pair, s.t[1], 0);
1321 } else if (!g_ascii_strcasecmp(s.t[0], "MaxTaskWidth")) {
1322 tb->task_width_max = atoi(s.t[1]);
1323 DBG("task_width_max = %d\n", tb->task_width_max);
1324 } else if (!g_ascii_strcasecmp(s.t[0], "spacing")) {
1325 tb->spacing = atoi(s.t[1]);
1326 } else if (!g_ascii_strcasecmp(s.t[0], "UseMouseWheel")) {
1327 tb->use_mouse_wheel = str2num(bool_pair, s.t[1], 1);
1328 } else if (!g_ascii_strcasecmp(s.t[0], "UseUrgencyHint")) {
1329 tb->use_urgency_hint = str2num(bool_pair, s.t[1], 1);
1330 } else {
1331 ERR( "taskbar: unknown var %s\n", s.t[0]);
1332 goto error;
1333 }
1334 } else {
1335 ERR( "taskbar: illegal in this context %s\n", s.str);
1336 goto error;
1337 }
1338 }
1339 taskbar_build_gui(p);
1340 tb_net_client_list(NULL, tb);
1341 tb_display(tb);
1342 tb_net_active_window(NULL, tb);
1343 RET(1);
1344
1345 error:
1346 taskbar_destructor(p);
1347 RET(0);
1348 }
1349
1350
1351 static void
1352 taskbar_destructor(plugin *p)
1353 {
1354 taskbar *tb = (taskbar *)p->priv;
1355
1356 ENTER;
1357 g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), tb_net_current_desktop, tb);
1358 g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), tb_net_active_window, tb);
1359 g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), tb_net_number_of_desktops, tb);
1360 g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), tb_net_client_list, tb);
1361 gdk_window_remove_filter(NULL, (GdkFilterFunc)tb_event_filter, tb );
1362 g_hash_table_destroy(tb->task_list);
1363 gtk_widget_destroy(tb->bar);
1364 gtk_widget_destroy(tb->menu);
1365
1366 RET();
1367 }
1368
1369 plugin_class taskbar_plugin_class = {
1370 fname: NULL,
1371 count: 0,
1372
1373 type : "taskbar",
1374 name : "taskbar",
1375 version: "1.0",
1376 description : "Taskbar shows all opened windows and allow to iconify them, shade or get focus",
1377
1378 constructor : taskbar_constructor,
1379 destructor : taskbar_destructor,
1380 };
1381