707ba7424fd37a028cf4e9c30b22eb32a1da048e
[lxde/lxpanel.git] / src / plugins / pager.c
1 /* pager.c -- pager module of lxpanel project
2 *
3 * Copyright (C) 2002-2003 Anatoly Asviyan <aanatoly@users.sf.net>
4 * Joe MacDonald <joe@deserted.net>
5 *
6 * This file is part of lxpanel.
7 *
8 * lxpanel is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2, or (at your option)
11 * any later version.
12 *
13 * lxpanel is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with sawfish; see the file COPYING. If not, write to
20 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301 USA.
22 */
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <stdlib.h>
27
28 #include <X11/Xlib.h>
29 #include <X11/Xutil.h>
30 #include <X11/xpm.h>
31
32 #include <gdk-pixbuf/gdk-pixbuf.h>
33 #include <glib/gi18n.h>
34
35 #include "panel.h"
36 #include "misc.h"
37 #include "plugin.h"
38
39 //#define DEBUG
40 #include "dbg.h"
41
42
43 extern panel *p;
44
45 /* managed window: all related info that wm holds about its managed windows */
46 typedef struct task {
47 Window win;
48 int x, y;
49 guint w, h;
50 gint refcount;
51 guint stacking;
52 guint desktop;
53 char *name, *iname;
54 int ws;
55 net_wm_state nws;
56 net_wm_window_type nwwt;
57 guint focused:1;
58 } task;
59
60 typedef struct _desk desk;
61 typedef struct _pager pager;
62
63 #define MAX_DESK_NUM 20
64 /* map of a desktop */
65 struct _desk {
66 GtkWidget *da;
67 GdkPixmap *pix;
68 int no, dirty, first;
69 gfloat scalew, scaleh;
70 pager *pg;
71 };
72
73 struct _pager {
74 GtkWidget *box, *eb;
75 desk *desks[MAX_DESK_NUM];
76 guint desknum;
77 guint curdesk;
78 int dw, dh;
79 gfloat scalex, scaley, ratio;
80 Window *wins;
81 int winnum, dirty;
82 GHashTable* htable;
83 task *focusedtask;
84 };
85
86
87
88 #define TASK_VISIBLE(tk) \
89 (!( ((tk)->ws != NormalState) || (tk)->nws.hidden || (tk)->nws.skip_pager ))
90
91 //if (t->nws.skip_pager || t->nwwt.desktop /*|| t->nwwt.dock || t->nwwt.splash*/ )
92
93 static void pager_rebuild_all(FbEv *ev, pager *pg);
94
95 static inline void desk_set_dirty_by_win(pager *p, task *t);
96 static inline void desk_set_dirty(desk *d);
97 static inline void desk_set_dirty_all(pager *pg);
98 /*
99 static void desk_clear_pixmap(desk *d);
100 static gboolean task_remove_stale(Window *win, task *t, pager *p);
101 static gboolean task_remove_all(Window *win, task *t, pager *p);
102 */
103
104
105
106
107 /*****************************************************************
108 * Task Management Routines *
109 *****************************************************************/
110
111
112 /* tell to remove element with zero refcount */
113 static gboolean
114 task_remove_stale(Window *win, task *t, pager *p)
115 {
116 if (t->refcount-- == 0) {
117 desk_set_dirty_by_win(p, t);
118 if (p->focusedtask == t)
119 p->focusedtask = NULL;
120 DBG("del %x\n", t->win);
121 g_free(t);
122 return TRUE;
123 }
124 return FALSE;
125 }
126
127 /* tell to remove element with zero refcount */
128 static gboolean
129 task_remove_all(Window *win, task *t, pager *p)
130 {
131 g_free(t);
132 return TRUE;
133 }
134
135
136 static void
137 task_get_sizepos(task *t)
138 {
139 Window root, junkwin;
140 int rx, ry;
141 guint dummy;
142 XWindowAttributes win_attributes;
143
144 ENTER;
145 if (!XGetWindowAttributes(GDK_DISPLAY(), t->win, &win_attributes)) {
146 if (!XGetGeometry (GDK_DISPLAY(), t->win, &root, &t->x, &t->y, &t->w, &t->h,
147 &dummy, &dummy)) {
148 t->x = t->y = t->w = t->h = 2;
149 }
150
151 } else {
152 XTranslateCoordinates (GDK_DISPLAY(), t->win, win_attributes.root,
153 -win_attributes.border_width,
154 -win_attributes.border_width,
155 &rx, &ry, &junkwin);
156 t->x = rx;
157 t->y = ry;
158 t->w = win_attributes.width;
159 t->h = win_attributes.height;
160 DBG("win=0x%x WxH=%dx%d\n", t->win,t->w, t->h);
161 }
162 RET();
163 }
164
165
166 static void
167 task_update_pix(task *t, desk *d)
168 {
169 int x, y, w, h;
170 GtkWidget *widget;
171
172 ENTER;
173 g_return_if_fail(d->pix != NULL);
174 if (!TASK_VISIBLE(t))
175 RET();;
176
177 if (t->desktop < p->desknum &&
178 t->desktop != d->no)
179 RET();
180
181 x = (gfloat)t->x * d->scalew;
182 y = (gfloat)t->y * d->scaleh;
183 w = (gfloat)t->w * d->scalew;
184 //h = (gfloat)t->h * d->scaleh;
185 h = (t->nws.shaded) ? 3 : (gfloat)t->h * d->scaleh;
186 if (w < 3 || h < 3)
187 RET();
188 widget = GTK_WIDGET(d->da);
189 gdk_draw_rectangle (d->pix,
190 (d->pg->focusedtask == t) ?
191 widget->style->bg_gc[GTK_STATE_SELECTED] :
192 widget->style->bg_gc[GTK_STATE_NORMAL],
193 TRUE,
194 x+1, y+1, w-1, h-1);
195 gdk_draw_rectangle (d->pix,
196 (d->pg->focusedtask == t) ?
197 widget->style->fg_gc[GTK_STATE_SELECTED] :
198 widget->style->fg_gc[GTK_STATE_NORMAL],
199 FALSE,
200 x, y, w, h);
201 RET();
202 }
203
204
205 /*****************************************************************
206 * Desk Functions *
207 *****************************************************************/
208 static void
209 desk_clear_pixmap(desk *d)
210 {
211 GtkWidget *widget;
212
213 ENTER;
214 DBG("d->no=%d\n", d->no);
215 if (!d->pix)
216 RET();
217 widget = GTK_WIDGET(d->da);
218 gdk_draw_rectangle (d->pix,
219 ((d->no == d->pg->curdesk) ?
220 widget->style->dark_gc[GTK_STATE_SELECTED] :
221 widget->style->dark_gc[GTK_STATE_NORMAL]),
222 TRUE,
223 0, 0,
224 widget->allocation.width,
225 widget->allocation.height);
226
227 RET();
228 }
229
230
231
232 static inline void
233 desk_set_dirty(desk *d)
234 {
235 ENTER;
236 d->dirty = 1;
237 gtk_widget_queue_draw(d->da);
238 RET();
239 }
240
241 static inline void
242 desk_set_dirty_all(pager *pg)
243 {
244 int i;
245 ENTER;
246 for (i = 0; i < pg->desknum; i++)
247 desk_set_dirty(pg->desks[i]);
248 RET();
249 }
250
251 static inline void
252 desk_set_dirty_by_win(pager *p, task *t)
253 {
254 ENTER;
255 if (t->nws.skip_pager || t->nwwt.desktop /*|| t->nwwt.dock || t->nwwt.splash*/ )
256 RET();
257 if (t->desktop < p->desknum)
258 desk_set_dirty(p->desks[t->desktop]);
259 else
260 desk_set_dirty_all(p);
261 RET();
262 }
263
264 /* Redraw the screen from the backing pixmap */
265 static gint
266 desk_expose_event (GtkWidget *widget, GdkEventExpose *event, desk *d)
267 {
268 ENTER;
269 DBG("d->no=%d\n", d->no);
270
271 if (d->dirty) {
272 pager *pg = d->pg;
273 task *t;
274 int j;
275
276 d->dirty = 0;
277 desk_clear_pixmap(d);
278 for (j = 0; j < pg->winnum; j++) {
279 if (!(t = g_hash_table_lookup(pg->htable, &pg->wins[j])))
280 continue;
281 task_update_pix(t, d);
282 }
283 }
284 gdk_draw_drawable(widget->window,
285 widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
286 d->pix,
287 event->area.x, event->area.y,
288 event->area.x, event->area.y,
289 event->area.width, event->area.height);
290 RET(FALSE);
291 }
292
293 /* Upon realize and every resize creates a new backing pixmap of the appropriate size */
294 static gint
295 desk_configure_event (GtkWidget *widget, GdkEventConfigure *event, desk *d)
296 {
297 int w, h;
298 ENTER;
299 DBG("d->no=%d %dx%d\n", d->no, widget->allocation.width, widget->allocation.height);
300 if (d->pix)
301 g_object_unref(d->pix);
302
303 d->pix = gdk_pixmap_new(widget->window,
304 widget->allocation.width,
305 widget->allocation.height,
306 -1);
307
308 d->scalew = (gfloat)widget->allocation.height / (gfloat)gdk_screen_height();
309 d->scaleh = (gfloat)widget->allocation.width / (gfloat)gdk_screen_width();
310 desk_set_dirty(d);
311
312 //request best size
313 if (p->orientation != ORIENT_HORIZ) {
314 w = widget->allocation.width;
315 h = (gfloat) w / d->pg->ratio;
316 } else {
317 h = widget->allocation.height;
318 w = (gfloat) h * d->pg->ratio;
319 }
320 DBG("requesting %dx%d\n", w, h);
321 gtk_widget_set_size_request(widget, w, h);
322
323 RET(FALSE);
324 }
325
326 static gint
327 desk_button_press_event(GtkWidget * widget, GdkEventButton * event, desk *d)
328 {
329 ENTER;
330 DBG("s=%d\n", d->no);
331 Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, d->no, 0, 0, 0, 0);
332 RET(TRUE);
333 }
334
335 /*
336 static gint
337 desk_button_release_event(GtkWidget * widget, GdkEventButton * event, desk *d)
338 {
339 ENTER;
340 DBG("t=%d\n", d->no);
341 Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, d->no, 0, 0, 0, 0);
342 RET(TRUE);
343 }
344 */
345
346 static gint
347 desk_scroll_event (GtkWidget *widget, GdkEventScroll *event, desk *d)
348 {
349 int i;
350
351 ENTER;
352 DBG("scroll direction = %d\n", event->direction);
353 i = d->pg->curdesk;
354 if (event->direction == GDK_SCROLL_UP ||event->direction == GDK_SCROLL_LEFT) {
355 i--;
356 if (i < 0)
357 i = d->pg->desknum - 1;
358 } else {
359 i++;
360 if (i >= d->pg->desknum)
361 i = 0;
362 }
363 Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, i, 0, 0, 0, 0);
364 RET(TRUE);
365 }
366
367 static void
368 desk_new(pager *pg, int i)
369 {
370 desk *d;
371
372 ENTER;
373 g_assert(i < pg->desknum);
374 d = pg->desks[i] = g_new0(desk, 1);
375 d->pg = pg;
376 d->pix = NULL;
377 d->dirty = 0;
378 d->first = 1;
379 d->no = i;
380
381 d->da = gtk_drawing_area_new();
382 //gtk_widget_set_size_request(GTK_WIDGET(d->da), 10, 10);
383 gtk_box_pack_start(GTK_BOX(pg->box), d->da, TRUE, TRUE, 0);
384 gtk_widget_add_events (d->da, GDK_EXPOSURE_MASK
385 | GDK_BUTTON_PRESS_MASK
386 | GDK_BUTTON_RELEASE_MASK);
387 g_signal_connect (G_OBJECT (d->da), "expose_event",
388 (GCallback) desk_expose_event, (gpointer)d);
389 g_signal_connect (G_OBJECT (d->da), "configure_event",
390 (GCallback) desk_configure_event, (gpointer)d);
391 g_signal_connect (G_OBJECT (d->da), "scroll-event",
392 (GCallback) desk_scroll_event, (gpointer)d);
393 g_signal_connect (G_OBJECT (d->da), "button_press_event",
394 (GCallback) desk_button_press_event, (gpointer)d);
395 //g_signal_connect (G_OBJECT (d->da), "button_release_event",
396 // (GCallback) desk_button_release_event, (gpointer)d);
397 gtk_widget_show(d->da);
398 DBG("before pack\n");
399
400 DBG("after show\n");
401 RET();
402 }
403
404 static void
405 desk_free(pager *pg, int i)
406 {
407 desk *d;
408
409 ENTER;
410 d = pg->desks[i];
411 DBG("i=%d d->no=%d d->da=%p d->pix=%p\n",
412 i, d->no, d->da, d->pix);
413 if (d->pix)
414 g_object_unref(d->pix);
415 gtk_widget_destroy(d->da);
416 g_free(d);
417 RET();
418 }
419
420
421 /*****************************************************************
422 * Netwm/WM Interclient Communication *
423 *****************************************************************/
424
425 static void
426 do_net_active_window(FbEv *ev, pager *p)
427 {
428 Window *fwin;
429 task *t;
430
431 ENTER;
432 fwin = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_ACTIVE_WINDOW, XA_WINDOW, 0);
433 DBG("win=%x\n", fwin ? *fwin : 0);
434 if (fwin) {
435 t = g_hash_table_lookup(p->htable, fwin);
436 if (t != p->focusedtask) {
437 if (p->focusedtask)
438 desk_set_dirty_by_win(p, p->focusedtask);
439 p->focusedtask = t;
440 if (t)
441 desk_set_dirty_by_win(p, t);
442 }
443 XFree(fwin);
444 } else {
445 if (p->focusedtask) {
446 desk_set_dirty_by_win(p, p->focusedtask);
447 p->focusedtask = NULL;
448 }
449 }
450 RET();
451 }
452
453 static void
454 do_net_current_desktop(FbEv *ev, pager *p)
455 {
456 ENTER;
457 desk_set_dirty(p->desks[p->curdesk]);
458 p->curdesk = get_net_current_desktop ();
459 if (p->curdesk >= p->desknum)
460 p->curdesk = 0;
461 desk_set_dirty(p->desks[p->curdesk]);
462 RET();
463 }
464
465
466 static void
467 do_net_client_list_stacking(FbEv *ev, pager *p)
468 {
469 int i;
470 task *t;
471
472 ENTER;
473 if (p->wins)
474 XFree(p->wins);
475 p->wins = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_CLIENT_LIST_STACKING,
476 XA_WINDOW, &p->winnum);
477 if (!p->wins || !p->winnum)
478 RET();
479
480 /* refresh existing tasks and add new */
481 for (i = 0; i < p->winnum; i++) {
482 if ((t = g_hash_table_lookup(p->htable, &p->wins[i]))) {
483 t->refcount++;
484 if (t->stacking != i) {
485 t->stacking = i;
486 desk_set_dirty_by_win(p, t);
487 }
488 } else {
489 t = g_new0(task, 1);
490 t->refcount++;
491 t->win = p->wins[i];
492 t->ws = get_wm_state (t->win);
493 t->desktop = get_net_wm_desktop(t->win);
494 get_net_wm_state(t->win, &t->nws);
495 get_net_wm_window_type(t->win, &t->nwwt);
496 task_get_sizepos(t);
497 if (!FBPANEL_WIN(t->win))
498 XSelectInput (GDK_DISPLAY(), t->win, PropertyChangeMask | StructureNotifyMask);
499 g_hash_table_insert(p->htable, &t->win, t);
500 DBG("add %x\n", t->win);
501 desk_set_dirty_by_win(p, t);
502 }
503 }
504 /* pass throu hash table and delete stale windows */
505 g_hash_table_foreach_remove(p->htable, (GHRFunc) task_remove_stale, (gpointer)p);
506 RET();
507 }
508
509
510 /*****************************************************************
511 * Pager Functions *
512 *****************************************************************/
513 /*
514 static void
515 pager_unmapnotify(pager *p, XEvent *ev)
516 {
517 Window win = ev->xunmap.window;
518 task *t;
519 if (!(t = g_hash_table_lookup(p->htable, &win)))
520 RET();
521 DBG("pager_unmapnotify: win=0x%x\n", win);
522 RET();
523 t->ws = WithdrawnState;
524 desk_set_dirty_by_win(p, t);
525 RET();
526 }
527 */
528 static void
529 pager_configurenotify(pager *p, XEvent *ev)
530 {
531 Window win = ev->xconfigure.window;
532 task *t;
533
534 ENTER;
535
536 if (!(t = g_hash_table_lookup(p->htable, &win)))
537 RET();
538 DBG("win=0x%x\n", win);
539 task_get_sizepos(t);
540 desk_set_dirty_by_win(p, t);
541 RET();
542 }
543
544 static void
545 pager_propertynotify(pager *p, XEvent *ev)
546 {
547 Atom at = ev->xproperty.atom;
548 Window win = ev->xproperty.window;
549 task *t;
550
551 ENTER;
552 if ((win == GDK_ROOT_WINDOW()) || !(t = g_hash_table_lookup(p->htable, &win)))
553 RET();
554
555 /* The property is deleted */
556 if( ((XPropertyEvent*)ev)->state == 1 )
557 return;
558
559 DBG("window=0x%x\n", t->win);
560 if (at == a_WM_STATE) {
561 DBG("event=WM_STATE\n");
562 t->ws = get_wm_state (t->win);
563 } else if (at == a_NET_WM_STATE) {
564 DBG("event=NET_WM_STATE\n");
565 get_net_wm_state(t->win, &t->nws);
566 } else if (at == a_NET_WM_DESKTOP) {
567 DBG("event=NET_WM_DESKTOP\n");
568 desk_set_dirty_by_win(p, t); // to clean up desks where this task was
569 t->desktop = get_net_wm_desktop(t->win);
570 } else {
571 RET();
572 }
573 desk_set_dirty_by_win(p, t);
574 RET();
575 }
576
577 static GdkFilterReturn
578 pager_event_filter( XEvent *xev, GdkEvent *event, pager *pg)
579 {
580 ENTER;
581 if (xev->type == PropertyNotify )
582 pager_propertynotify(pg, xev);
583 else if (xev->type == ConfigureNotify )
584 pager_configurenotify(pg, xev);
585 RET(GDK_FILTER_CONTINUE);
586 }
587
588
589
590
591
592 static void
593 pager_rebuild_all(FbEv *ev, pager *pg)
594 {
595 int desknum, curdesk, dif, i;
596
597 ENTER;
598 desknum = pg->desknum;
599 curdesk = pg->curdesk;
600
601 pg->desknum = get_net_number_of_desktops();
602 if (pg->desknum < 1)
603 pg->desknum = 1;
604 else if (pg->desknum > MAX_DESK_NUM) {
605 pg->desknum = MAX_DESK_NUM;
606 ERR("pager: max number of supported desks is %d\n", MAX_DESK_NUM);
607 }
608 pg->curdesk = get_net_current_desktop();
609 if (pg->curdesk >= pg->desknum)
610 pg->curdesk = 0;
611 DBG("desknum=%d curdesk=%d\n", desknum, curdesk);
612 DBG("pg->desknum=%d pg->curdesk=%d\n", pg->desknum, pg->curdesk);
613 dif = pg->desknum - desknum;
614
615 if (dif == 0)
616 RET();
617
618 if (dif < 0) {
619 /* if desktops were deleted then delete their maps also */
620 for (i = pg->desknum; i < desknum; i++)
621 desk_free(pg, i);
622 } else {
623 for (i = desknum; i < pg->desknum; i++)
624 desk_new(pg, i);
625 }
626 do_net_client_list_stacking(NULL, pg);
627 RET();
628 }
629
630
631 static int
632 pager_constructor(plugin *plug, char **fp)
633 {
634 pager *pg;
635
636 ENTER;
637 pg = g_new0(pager, 1);
638 g_return_val_if_fail(pg != NULL, 0);
639 plug->priv = pg;
640
641 plug->pwid = gtk_event_box_new();
642 GTK_WIDGET_SET_FLAGS( plug->pwid, GTK_NO_WINDOW );
643
644 pg->htable = g_hash_table_new (g_int_hash, g_int_equal);
645
646 pg->box = plug->panel->my_box_new(TRUE, 1);
647 gtk_container_set_border_width (GTK_CONTAINER (pg->box), 0);
648 gtk_widget_show(pg->box);
649
650 gtk_container_set_border_width (GTK_CONTAINER (plug->pwid), 1);
651 gtk_container_add(GTK_CONTAINER(plug->pwid), pg->box);
652 pg->eb = pg->box;
653
654 pg->ratio = (gfloat)gdk_screen_width() / (gfloat)gdk_screen_height();
655 pg->scaley = (gfloat)pg->dh / (gfloat)gdk_screen_height();
656 pg->scalex = (gfloat)pg->dw / (gfloat)gdk_screen_width();
657
658 pager_rebuild_all(fbev, pg);
659 //do_net_current_desktop(fbev, pg);
660 //do_net_client_list_stacking(fbev, pg);
661
662 gdk_window_add_filter(NULL, (GdkFilterFunc)pager_event_filter, pg );
663
664 g_signal_connect (G_OBJECT (fbev), "current_desktop",
665 G_CALLBACK (do_net_current_desktop), (gpointer) pg);
666 g_signal_connect (G_OBJECT (fbev), "active_window",
667 G_CALLBACK (do_net_active_window), (gpointer) pg);
668 g_signal_connect (G_OBJECT (fbev), "number_of_desktops",
669 G_CALLBACK (pager_rebuild_all), (gpointer) pg);
670 g_signal_connect (G_OBJECT (fbev), "client_list_stacking",
671 G_CALLBACK (do_net_client_list_stacking), (gpointer) pg);
672 RET(1);
673 }
674
675 static void
676 pager_destructor(plugin *p)
677 {
678 pager *pg = (pager *)p->priv;
679
680 ENTER;
681 g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), do_net_current_desktop, pg);
682 g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), do_net_active_window, pg);
683 g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), pager_rebuild_all, pg);
684 g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), do_net_client_list_stacking, pg);
685 gdk_window_remove_filter(NULL, (GdkFilterFunc)pager_event_filter, pg);
686 while (--pg->desknum) {
687 desk_free(pg, pg->desknum);
688 }
689 g_hash_table_foreach_remove(pg->htable, (GHRFunc) task_remove_all, (gpointer)pg);
690 g_hash_table_destroy(pg->htable);
691 gtk_widget_destroy(pg->eb);
692 g_free(pg);
693 RET();
694 }
695
696
697 plugin_class pager_plugin_class = {
698 fname: NULL,
699 count: 0,
700
701 type : "pager",
702 name : N_("Desktop Pager"),
703 version: "1.0",
704 description : N_("Simple pager plugin"),
705 /* FIXME: orientation should be handled!! */
706 constructor : pager_constructor,
707 destructor : pager_destructor,
708 config : NULL,
709 save : NULL
710 };