Merging upstream version 0.5.9.
[debian/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
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <stdlib.h>
28
29 #include <X11/Xlib.h>
30 #include <X11/Xutil.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 #include "icon-grid.h"
39
40 #include "dbg.h"
41
42 struct _task;
43 struct _desk;
44 struct _pager;
45
46 #define ALL_DESKTOPS 0xFFFFFFFF /* 64-bit clean */
47 #define BORDER_WIDTH 2
48
49 /* Structure representing a "task", an open window. */
50 typedef struct _task {
51 struct _task * task_flink; /* Forward link of task list */
52 Window win; /* X window ID */
53 int x; /* Geometry as reported by X server */
54 int y;
55 guint w;
56 guint h;
57 int stacking; /* Stacking order as reported by NET_WM_CLIENT_STACKING */
58 int desktop; /* Desktop that contains task */
59 int ws; /* WM_STATE value */
60 NetWMState nws; /* NET_WM_STATE value */
61 NetWMWindowType nwwt; /* NET_WM_WINDOW_TYPE value */
62 guint focused : 1; /* True if window has focus */
63 guint present_in_client_list : 1; /* State during WM_CLIENT_LIST processing to detect deletions */
64 } PagerTask;
65
66 /* Structure representing a desktop. */
67 typedef struct _desk {
68 struct _pager * pg; /* Back pointer to plugin context */
69 GtkWidget * da; /* Drawing area */
70 GdkPixmap * pixmap; /* Pixmap to be drawn on drawing area */
71 int desktop_number; /* Desktop number */
72 gboolean dirty; /* True if needs to be recomputed */
73 gfloat scale_x; /* Horizontal scale factor */
74 gfloat scale_y; /* Vertical scale factor */
75 } PagerDesk;
76
77 /* Private context for pager plugin. */
78 typedef struct _pager {
79 Plugin * plugin; /* Back pointer to plugin */
80 IconGrid * icon_grid; /* Container widget */
81 int desk_extent; /* Extent of desks vector */
82 PagerDesk * * desks; /* Vector of desktop structures */
83 guint number_of_desktops; /* Number of desktops, from NET_WM_NUMBER_OF_DESKTOPS */
84 guint current_desktop; /* Current desktop, from NET_WM_CURRENT_DESKTOP */
85 gfloat aspect_ratio; /* Aspect ratio of screen image */
86 int client_count; /* Count of tasks in stacking order */
87 PagerTask * * tasks_in_stacking_order; /* Vector of tasks in stacking order */
88 PagerTask * task_list; /* Tasks in window ID order */
89 PagerTask * focused_task; /* Task that has focus */
90 } PagerPlugin;
91
92 static gboolean task_is_visible(PagerTask * tk);
93 static PagerTask * task_lookup(PagerPlugin * pg, Window win);
94 static void task_delete(PagerPlugin * pg, PagerTask * tk, gboolean unlink);
95 static void task_get_geometry(PagerTask * tk);
96 static void task_update_pixmap(PagerTask * tk, PagerDesk * d);
97 static void desk_set_dirty(PagerDesk * d);
98 static void desk_set_dirty_all(PagerPlugin * pg);
99 static void desk_set_dirty_by_win(PagerPlugin * pg, PagerTask * tk);
100 static gboolean desk_configure_event(GtkWidget * widget, GdkEventConfigure * event, PagerDesk * d);
101 static gboolean desk_expose_event(GtkWidget * widget, GdkEventExpose * event, PagerDesk * d);
102 static gboolean desk_scroll_event(GtkWidget * widget, GdkEventScroll * event, PagerDesk * d);
103 static gboolean desk_button_press_event(GtkWidget * widget, GdkEventButton * event, PagerDesk * d);
104 static void desk_new(PagerPlugin * pg, int desktop_number);
105 static void desk_free(PagerPlugin * pg, int desktop_number);
106 static void pager_property_notify_event(PagerPlugin * p, XEvent * ev);
107 static void pager_configure_notify_event(PagerPlugin * pg, XEvent * ev);
108 static GdkFilterReturn pager_event_filter(XEvent * xev, GdkEvent * event, PagerPlugin * pg);
109 static void pager_net_active_window(FbEv * ev, PagerPlugin * pg);
110 static void pager_net_desktop_names(FbEv * ev, PagerPlugin * pg);
111 static void pager_net_number_of_desktops(FbEv * ev, PagerPlugin * pg);
112 static void pager_net_client_list_stacking(FbEv * ev, PagerPlugin * pg);
113 static int pager_constructor(Plugin * plug, char ** fp);
114 static void pager_destructor(Plugin * p);
115 static void pager_panel_configuration_changed(Plugin * p);
116
117 /*****************************************************************
118 * Task Management Routines *
119 *****************************************************************/
120
121 /* Determine if a task is visible. */
122 static gboolean task_is_visible(PagerTask * tk)
123 {
124 return ( ! ((tk->nws.hidden) || (tk->nws.skip_pager) || (tk->nwwt.dock) || (tk->nwwt.desktop)));
125 }
126
127 /* Look up a task in the task list. */
128 static PagerTask * task_lookup(PagerPlugin * pg, Window win)
129 {
130 PagerTask * tk;
131 for (tk = pg->task_list; tk != NULL; tk = tk->task_flink)
132 {
133 if (tk->win == win)
134 return tk;
135 if (tk->win > win)
136 break;
137 }
138 return NULL;
139 }
140
141 /* Delete a task and optionally unlink it from the task list. */
142 static void task_delete(PagerPlugin * pg, PagerTask * tk, gboolean unlink)
143 {
144 /* If we think this task had focus, remove that. */
145 if (pg->focused_task == tk)
146 pg->focused_task = NULL;
147
148 /* If requested, unlink the task from the task list.
149 * If not requested, the caller will do this. */
150 if (unlink)
151 {
152 if (pg->task_list == tk)
153 pg->task_list = tk->task_flink;
154 else
155 {
156 /* Locate the task and its predecessor in the list and then remove it. For safety, ensure it is found. */
157 PagerTask * tk_pred = NULL;
158 PagerTask * tk_cursor;
159 for (
160 tk_cursor = pg->task_list;
161 ((tk_cursor != NULL) && (tk_cursor != tk));
162 tk_pred = tk_cursor, tk_cursor = tk_cursor->task_flink) ;
163 if (tk_cursor == tk)
164 tk_pred->task_flink = tk->task_flink;
165 }
166 }
167
168 /* Deallocate the task structure. */
169 g_free(tk);
170 }
171
172 /* Get the geometry of a task window in screen coordinates. */
173 static void task_get_geometry(PagerTask * tk)
174 {
175 /* Install an error handler that ignores BadWindow and BadDrawable.
176 * We frequently get a ConfigureNotify event on deleted windows. */
177 XErrorHandler previous_error_handler = XSetErrorHandler(panel_handle_x_error_swallow_BadWindow_BadDrawable);
178
179 XWindowAttributes win_attributes;
180 if (XGetWindowAttributes(GDK_DISPLAY(), tk->win, &win_attributes))
181 {
182 Window unused_win;
183 int rx, ry;
184 XTranslateCoordinates(GDK_DISPLAY(), tk->win, win_attributes.root,
185 - win_attributes.border_width,
186 - win_attributes.border_width,
187 &rx, &ry, &unused_win);
188 tk->x = rx;
189 tk->y = ry;
190 tk->w = win_attributes.width;
191 tk->h = win_attributes.height;
192 }
193 else
194 {
195 Window unused_win;
196 guint unused;
197 if ( ! XGetGeometry(GDK_DISPLAY(), tk->win,
198 &unused_win, &tk->x, &tk->y, &tk->w, &tk->h, &unused, &unused))
199 {
200 tk->x = tk->y = tk->w = tk->h = 2;
201 }
202 }
203
204 XSetErrorHandler(previous_error_handler);
205 }
206
207 /* Draw the representation of a task's window on the backing pixmap. */
208 static void task_update_pixmap(PagerTask * tk, PagerDesk * d)
209 {
210 if ((d->pixmap != NULL) && (task_is_visible(tk)))
211 {
212 if ((tk->desktop == ALL_DESKTOPS) || (tk->desktop == d->desktop_number))
213 {
214 /* Scale the representation of the window to the drawing area. */
215 int x = (gfloat) tk->x * d->scale_x;
216 int y = (gfloat) tk->y * d->scale_y;
217 int w = (gfloat) tk->w * d->scale_x;
218 int h = ((tk->nws.shaded) ? 3 : (gfloat) tk->h * d->scale_y);
219 if ((w >= 3) && (h >= 3))
220 {
221 /* Draw the window representation and a border. */
222 GtkWidget * widget = GTK_WIDGET(d->da);
223 GtkStyle * style = gtk_widget_get_style(widget);
224
225 gdk_draw_rectangle(d->pixmap,
226 (d->pg->focused_task == tk) ? style->bg_gc[GTK_STATE_SELECTED] : style->bg_gc[GTK_STATE_NORMAL],
227 TRUE,
228 x + 1, y + 1, w - 1, h - 1);
229 gdk_draw_rectangle(d->pixmap,
230 (d->pg->focused_task == tk) ? style->fg_gc[GTK_STATE_SELECTED] : style->fg_gc[GTK_STATE_NORMAL],
231 FALSE,
232 x, y, w, h);
233 }
234 }
235 }
236 }
237
238 /*****************************************************************
239 * Desk Functions *
240 *****************************************************************/
241
242 /* Mark a specified desktop for redraw. */
243 static void desk_set_dirty(PagerDesk * d)
244 {
245 d->dirty = TRUE;
246 gtk_widget_queue_draw(d->da);
247 }
248
249 /* Mark all desktops for redraw. */
250 static void desk_set_dirty_all(PagerPlugin * pg)
251 {
252 int i;
253 for (i = 0; i < pg->number_of_desktops; i++)
254 desk_set_dirty(pg->desks[i]);
255 }
256
257 /* Mark the desktop on which a specified window resides for redraw. */
258 static void desk_set_dirty_by_win(PagerPlugin * pg, PagerTask * tk)
259 {
260 if (task_is_visible(tk))
261 {
262 if (tk->desktop < pg->number_of_desktops)
263 desk_set_dirty(pg->desks[tk->desktop]);
264 else
265 desk_set_dirty_all(pg);
266 }
267 }
268
269 /* Handler for configure_event on drawing area. */
270 static gboolean desk_configure_event(GtkWidget * widget, GdkEventConfigure * event, PagerDesk * d)
271 {
272 /* Allocate pixmap and statistics buffer without border pixels. */
273 #if GTK_CHECK_VERSION(2,18,0)
274 GtkAllocation *allocation = g_new0 (GtkAllocation, 1);
275 gtk_widget_get_allocation(GTK_WIDGET(widget), allocation);
276 int new_pixmap_width = allocation->width;
277 int new_pixmap_height = allocation->height;
278 #else
279 int new_pixmap_width = widget->allocation.width;
280 int new_pixmap_height = widget->allocation.height;
281 #endif
282 if ((new_pixmap_width > 0) && (new_pixmap_height > 0))
283 {
284 /* Allocate a new pixmap of the allocated size. */
285 if (d->pixmap != NULL)
286 g_object_unref(d->pixmap);
287 #if GTK_CHECK_VERSION(2,14,0)
288 d->pixmap = gdk_pixmap_new(gtk_widget_get_window(widget), new_pixmap_width, new_pixmap_height, -1);
289 #else
290 d->pixmap = gdk_pixmap_new(widget->window, new_pixmap_width, new_pixmap_height, -1);
291 #endif
292
293 /* Compute the horizontal and vertical scale factors, and mark the desktop for redraw. */
294 #if GTK_CHECK_VERSION(2,18,0)
295 d->scale_y = (gfloat) allocation->height / (gfloat) gdk_screen_height();
296 d->scale_x = (gfloat) allocation->width / (gfloat) gdk_screen_width();
297 #else
298 d->scale_y = (gfloat) allocation->height / (gfloat) gdk_screen_height();
299 d->scale_x = (gfloat) allocation->width / (gfloat) gdk_screen_width();
300 #endif
301 desk_set_dirty(d);
302 }
303
304 /* Resize to optimal size. */
305 gtk_widget_set_size_request(widget,
306 (d->pg->plugin->panel->icon_size - BORDER_WIDTH * 2) * d->pg->aspect_ratio,
307 d->pg->plugin->panel->icon_size - BORDER_WIDTH * 2);
308 #if GTK_CHECK_VERSION(2,18,0)
309 g_free (allocation);
310 #endif
311 return FALSE;
312 }
313
314 /* Handler for expose_event on drawing area. */
315 static gboolean desk_expose_event(GtkWidget * widget, GdkEventExpose * event, PagerDesk * d)
316 {
317 GtkStyle * style = gtk_widget_get_style(widget);
318
319 if (d->pixmap != NULL)
320 {
321 /* Recompute the pixmap if needed. */
322 if (d->dirty)
323 {
324 d->dirty = FALSE;
325 PagerPlugin * pg = d->pg;
326
327 /* Erase the pixmap. */
328 if (d->pixmap != NULL)
329 {
330 GtkWidget * widget = GTK_WIDGET(d->da);
331 #if GTK_CHECK_VERSION(2,18,0)
332 GtkAllocation *allocation = g_new0 (GtkAllocation, 1);
333 gtk_widget_get_allocation(GTK_WIDGET(widget), allocation);
334 #endif
335 gdk_draw_rectangle(
336 d->pixmap,
337 ((d->desktop_number == d->pg->current_desktop)
338 ? style->dark_gc[GTK_STATE_SELECTED]
339 : style->dark_gc[GTK_STATE_NORMAL]),
340 TRUE,
341 #if GTK_CHECK_VERSION(2,18,0)
342 0, 0, allocation->width, allocation->height);
343 g_free (allocation);
344 #else
345 0, 0, widget->allocation.width, widget->allocation.height);
346 #endif
347 }
348
349 /* Draw tasks onto the pixmap. */
350 int j;
351 for (j = 0; j < pg->client_count; j++)
352 task_update_pixmap(pg->tasks_in_stacking_order[j], d);
353 }
354
355 /* Draw the requested part of the pixmap onto the drawing area. */
356 #if GTK_CHECK_VERSION(2,14,0)
357 gdk_draw_drawable(gtk_widget_get_window(widget),
358 #else
359 gdk_draw_drawable(widget->window,
360 #endif
361 style->fg_gc[GTK_WIDGET_STATE(widget)],
362 d->pixmap,
363 event->area.x, event->area.y,
364 event->area.x, event->area.y,
365 event->area.width, event->area.height);
366 }
367 return FALSE;
368 }
369
370 /* Handler for "scroll-event" on drawing area. */
371 static gboolean desk_scroll_event(GtkWidget * widget, GdkEventScroll * event, PagerDesk * d)
372 {
373 /* Compute the new desktop from the scroll direction, wrapping at either extreme. */
374 int current_desktop = d->pg->current_desktop;
375 if ((event->direction == GDK_SCROLL_UP) || (event->direction == GDK_SCROLL_RIGHT))
376 {
377 current_desktop += 1;
378 if (current_desktop >= d->pg->number_of_desktops)
379 current_desktop = 0;
380 }
381 else
382 {
383 current_desktop -= 1;
384 if (current_desktop < 0)
385 current_desktop = d->pg->number_of_desktops - 1;
386 }
387
388 /* Ask the window manager to make the new desktop current. */
389 Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, current_desktop, 0, 0, 0, 0);
390 return TRUE;
391 }
392
393 /* Handler for "button-press-event" on drawing area. */
394 static gboolean desk_button_press_event(GtkWidget * widget, GdkEventButton * event, PagerDesk * d)
395 {
396 /* Standard right-click handling. */
397 if (plugin_button_press_event(widget, event, d->pg->plugin))
398 return TRUE;
399
400 /* Ask the window manager to make the new desktop current. */
401 Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, d->desktop_number, 0, 0, 0, 0);
402 return TRUE;
403 }
404
405 /* Allocate the structure and the graphic elements representing a desktop. */
406 static void desk_new(PagerPlugin * pg, int desktop_number)
407 {
408
409 /* Allocate and initialize structure. */
410 PagerDesk * d = pg->desks[desktop_number] = g_new0(PagerDesk, 1);
411 d->pg = pg;
412 d->desktop_number = desktop_number;
413
414 /* Allocate drawing area. */
415 d->da = gtk_drawing_area_new();
416
417 icon_grid_add(pg->icon_grid, d->da, TRUE);
418 gtk_widget_add_events (d->da, GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK);
419
420 /* Connect signals. */
421 g_signal_connect(G_OBJECT(d->da), "expose_event", G_CALLBACK(desk_expose_event), (gpointer) d);
422 g_signal_connect(G_OBJECT(d->da), "configure_event", G_CALLBACK(desk_configure_event), (gpointer) d);
423 g_signal_connect(G_OBJECT(d->da), "scroll-event", G_CALLBACK(desk_scroll_event), (gpointer) d);
424 g_signal_connect(G_OBJECT(d->da), "button_press_event", G_CALLBACK(desk_button_press_event), (gpointer) d);
425
426 /* Show the widget. */
427 gtk_widget_show(d->da);
428 }
429
430 /* Free the structure representing a desktop. */
431 static void desk_free(PagerPlugin * pg, int desktop_number)
432 {
433 PagerDesk * d = pg->desks[desktop_number];
434
435 g_signal_handlers_disconnect_by_func(G_OBJECT(d->da), desk_expose_event, d);
436 g_signal_handlers_disconnect_by_func(G_OBJECT(d->da), desk_configure_event, d);
437 g_signal_handlers_disconnect_by_func(G_OBJECT(d->da), desk_scroll_event, d);
438 g_signal_handlers_disconnect_by_func(G_OBJECT(d->da), desk_button_press_event, d);
439
440 icon_grid_remove(pg->icon_grid, d->da);
441
442 if (d->pixmap != NULL)
443 g_object_unref(d->pixmap);
444
445 g_free(d);
446 }
447
448 /*****************************************************************
449 * Pager Functions *
450 *****************************************************************/
451
452 /* Handle PropertyNotify event.
453 * http://tronche.com/gui/x/icccm/
454 * http://standards.freedesktop.org/wm-spec/wm-spec-1.4.html */
455 static void pager_property_notify_event(PagerPlugin * pg, XEvent * ev)
456 {
457 /* State may be PropertyNewValue, PropertyDeleted. */
458 if (((XPropertyEvent*) ev)->state == PropertyNewValue)
459 {
460 Atom at = ev->xproperty.atom;
461 Window win = ev->xproperty.window;
462 if (win != GDK_ROOT_WINDOW())
463 {
464 /* Look up task structure by X window handle. */
465 PagerTask * tk = task_lookup(pg, win);
466 if (tk != NULL)
467 {
468 /* Install an error handler that ignores BadWindow.
469 * We frequently get a PropertyNotify event on deleted windows. */
470 XErrorHandler previous_error_handler = XSetErrorHandler(panel_handle_x_error_swallow_BadWindow_BadDrawable);
471
472 /* Dispatch on atom. */
473 if (at == a_WM_STATE)
474 {
475 /* Window changed state. */
476 tk->ws = get_wm_state(tk->win);
477 desk_set_dirty_by_win(pg, tk);
478 }
479 else if (at == a_NET_WM_STATE)
480 {
481 /* Window changed EWMH state. */
482 get_net_wm_state(tk->win, &tk->nws);
483 desk_set_dirty_by_win(pg, tk);
484 }
485 else if (at == a_NET_WM_DESKTOP)
486 {
487 /* Window changed desktop.
488 * Mark both old and new desktops for redraw. */
489 desk_set_dirty_by_win(pg, tk);
490 tk->desktop = get_net_wm_desktop(tk->win);
491 desk_set_dirty_by_win(pg, tk);
492
493 XSetErrorHandler(previous_error_handler);
494 }
495 }
496 }
497 }
498 }
499
500 /* Handle ConfigureNotify event. */
501 static void pager_configure_notify_event(PagerPlugin * pg, XEvent * ev)
502 {
503 Window win = ev->xconfigure.window;
504 PagerTask * tk = task_lookup(pg, win);
505 if (tk != NULL)
506 {
507 task_get_geometry(tk);
508 desk_set_dirty_by_win(pg, tk);
509 }
510 }
511
512 /* GDK event filter. */
513 static GdkFilterReturn pager_event_filter(XEvent * xev, GdkEvent * event, PagerPlugin * pg)
514 {
515 /* Look for PropertyNotify and ConfigureNotify events and update state. */
516 if (xev->type == PropertyNotify)
517 pager_property_notify_event(pg, xev);
518 else if (xev->type == ConfigureNotify)
519 pager_configure_notify_event(pg, xev);
520 return GDK_FILTER_CONTINUE;
521 }
522
523 /*****************************************************************
524 * Netwm/WM Interclient Communication *
525 *****************************************************************/
526
527 /* Handler for "active-window" event from root window listener. */
528 static void pager_net_active_window(FbEv * ev, PagerPlugin * pg)
529 {
530 Window * focused_window = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_ACTIVE_WINDOW, XA_WINDOW, 0);
531 if (focused_window != NULL)
532 {
533 PagerTask * tk = task_lookup(pg, *focused_window);
534 if (tk != pg->focused_task)
535 {
536 /* Focused task changed. Redraw both old and new. */
537 if (pg->focused_task != NULL)
538 desk_set_dirty_by_win(pg, pg->focused_task);
539 pg->focused_task = tk;
540 if (tk != NULL)
541 desk_set_dirty_by_win(pg, tk);
542 }
543 XFree(focused_window);
544 }
545 else
546 {
547 /* Focused task disappeared. Redraw old. */
548 if (pg->focused_task != NULL)
549 {
550 desk_set_dirty_by_win(pg, pg->focused_task);
551 pg->focused_task = NULL;
552 }
553 }
554 }
555
556 /* Handler for desktop_name event from window manager. */
557 static void pager_net_desktop_names(FbEv * fbev, PagerPlugin * pg)
558 {
559 /* Get the NET_DESKTOP_NAMES property. */
560 int number_of_desktop_names;
561 char * * desktop_names;
562 desktop_names = get_utf8_property_list(GDK_ROOT_WINDOW(), a_NET_DESKTOP_NAMES, &number_of_desktop_names);
563
564 /* Loop to copy the desktop names to the vector of labels.
565 * If there are more desktops than labels, label the extras with a decimal number. */
566 int i;
567 for (i = 0; ((desktop_names != NULL) && (i < MIN(pg->number_of_desktops, number_of_desktop_names))); i++)
568 gtk_widget_set_tooltip_text(pg->desks[i]->da, desktop_names[i]);
569 for ( ; i < pg->number_of_desktops; i++)
570 {
571 char temp[10];
572 sprintf(temp, "%d", i + 1);
573 gtk_widget_set_tooltip_text(pg->desks[i]->da, temp);
574 }
575
576 /* Free the property. */
577 if (desktop_names != NULL)
578 g_strfreev(desktop_names);
579 }
580
581 /* Handler for "current-desktop" event from root window listener. */
582 static void pager_net_current_desktop(FbEv * ev, PagerPlugin * pg)
583 {
584 desk_set_dirty(pg->desks[pg->current_desktop]);
585 pg->current_desktop = get_net_current_desktop();
586 if (pg->current_desktop >= pg->number_of_desktops)
587 pg->current_desktop = 0;
588 desk_set_dirty(pg->desks[pg->current_desktop]);
589 }
590
591
592 /* Handler for "number-of-desktops" event from root window listener.
593 * Also used to initialize plugin. */
594 static void pager_net_number_of_desktops(FbEv * ev, PagerPlugin * pg)
595 {
596 /* Get existing values. */
597 int number_of_desktops = pg->number_of_desktops;
598
599 /* Get the correct number of desktops. */
600 pg->number_of_desktops = get_net_number_of_desktops();
601 if (pg->number_of_desktops < 1)
602 pg->number_of_desktops = 1;
603
604 /* Reallocate the structure if necessary. */
605 if (pg->number_of_desktops > pg->desk_extent)
606 {
607 PagerDesk * * new_desks = g_new(PagerDesk *, pg->number_of_desktops);
608 if (pg->desks != NULL)
609 {
610 memcpy(new_desks, pg->desks, pg->desk_extent * sizeof(PagerDesk *));
611 g_free(pg->desks);
612 }
613 pg->desks = new_desks;
614 pg->desk_extent = pg->number_of_desktops;
615 }
616
617 /* Reconcile the current desktop number. */
618 pg->current_desktop = get_net_current_desktop();
619 if (pg->current_desktop >= pg->number_of_desktops)
620 pg->current_desktop = 0;
621
622 /* Reconcile the old and new number of desktops. */
623 int difference = pg->number_of_desktops - number_of_desktops;
624 if (difference != 0)
625 {
626 if (difference < 0)
627 {
628 /* If desktops were deleted, then delete their maps also. */
629 int i;
630 for (i = pg->number_of_desktops; i < number_of_desktops; i++)
631 desk_free(pg, i);
632 }
633 else
634 {
635 /* If desktops were added, then create their maps also. */
636 int i;
637 for (i = number_of_desktops; i < pg->number_of_desktops; i++)
638 desk_new(pg, i);
639 }
640 }
641
642 /* Refresh the client list. */
643 pager_net_client_list_stacking(NULL, pg);
644 }
645
646 /* Handler for "net-client-list-stacking" event from root window listener. */
647 static void pager_net_client_list_stacking(FbEv * ev, PagerPlugin * pg)
648 {
649 /* Get the NET_CLIENT_LIST_STACKING property. */
650 Window * client_list = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_CLIENT_LIST_STACKING, XA_WINDOW, &pg->client_count);
651 g_free(pg->tasks_in_stacking_order);
652 /* g_new returns NULL if if n_structs == 0 */
653 pg->tasks_in_stacking_order = g_new(PagerTask *, pg->client_count);
654
655 if (client_list != NULL)
656 {
657 /* Loop over client list, correlating it with task list.
658 * Also generate a vector of task pointers in stacking order. */
659 int i;
660 for (i = 0; i < pg->client_count; i++)
661 {
662 /* Search for the window in the task list. Set up context to do an insert right away if needed. */
663 PagerTask * tk_pred = NULL;
664 PagerTask * tk_cursor;
665 PagerTask * tk = NULL;
666 for (tk_cursor = pg->task_list; tk_cursor != NULL; tk_pred = tk_cursor, tk_cursor = tk_cursor->task_flink)
667 {
668 if (tk_cursor->win == client_list[i])
669 {
670 tk = tk_cursor;
671 break;
672 }
673 if (tk_cursor->win > client_list[i])
674 break;
675 }
676
677 /* Task is already in task list. */
678 if (tk != NULL)
679 {
680 tk->present_in_client_list = TRUE;
681
682 /* If the stacking position changed, redraw the desktop. */
683 if (tk->stacking != i)
684 {
685 tk->stacking = i;
686 desk_set_dirty_by_win(pg, tk);
687 }
688 }
689
690 /* Task is not in task list. */
691 else
692 {
693 /* Allocate and initialize new task structure. */
694 tk = g_new0(PagerTask, 1);
695 tk->present_in_client_list = TRUE;
696 tk->win = client_list[i];
697 tk->ws = get_wm_state(tk->win);
698 tk->desktop = get_net_wm_desktop(tk->win);
699 get_net_wm_state(tk->win, &tk->nws);
700 get_net_wm_window_type(tk->win, &tk->nwwt);
701 task_get_geometry(tk);
702 if ( ! FBPANEL_WIN(tk->win))
703 XSelectInput(GDK_DISPLAY(), tk->win, PropertyChangeMask | StructureNotifyMask);
704 desk_set_dirty_by_win(pg, tk);
705
706 /* Link the task structure into the task list. */
707 if (tk_pred == NULL)
708 {
709 tk->task_flink = pg->task_list;
710 pg->task_list = tk;
711 }
712 else
713 {
714 tk->task_flink = tk_pred->task_flink;
715 tk_pred->task_flink = tk;
716 }
717 }
718 pg->tasks_in_stacking_order[i] = tk;
719 }
720 XFree(client_list);
721 }
722
723 /* Remove windows from the task list that are not present in the NET_CLIENT_LIST_STACKING. */
724 PagerTask * tk_pred = NULL;
725 PagerTask * tk = pg->task_list;
726 while (tk != NULL)
727 {
728 PagerTask * tk_succ = tk->task_flink;
729 if (tk->present_in_client_list)
730 {
731 tk->present_in_client_list = FALSE;
732 tk_pred = tk;
733 }
734 else
735 {
736 if (tk_pred == NULL)
737 pg->task_list = tk_succ;
738 else tk_pred->task_flink = tk_succ;
739 task_delete(pg, tk, FALSE);
740 }
741 tk = tk_succ;
742 }
743 }
744
745 /* Plugin constructor. */
746 static int pager_constructor(Plugin * plug, char ** fp)
747 {
748 /* Allocate plugin context and set into Plugin private data pointer. */
749 PagerPlugin * pg = g_new0(PagerPlugin, 1);
750 plug->priv = pg;
751 pg->plugin = plug;
752
753 /* Compute aspect ratio of screen image. */
754 pg->aspect_ratio = (gfloat) gdk_screen_width() / (gfloat) gdk_screen_height();
755
756 /* Allocate top level widget and set into Plugin widget pointer. */
757 plug->pwid = gtk_event_box_new();
758 #if GTK_CHECK_VERSION(2,18,0)
759 gtk_widget_set_has_window(plug->pwid,FALSE);
760 #else
761 GTK_WIDGET_SET_FLAGS(plug->pwid, GTK_NO_WINDOW);
762 #endif
763 gtk_container_set_border_width(GTK_CONTAINER(plug->pwid), 0);
764
765 /* Create an icon grid manager to manage the drawing areas within the container. */
766 GtkOrientation bo = (plug->panel->orientation == ORIENT_HORIZ) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
767 pg->icon_grid = icon_grid_new(plug->panel, plug->pwid, bo,
768 (plug->panel->icon_size - BORDER_WIDTH * 2) * pg->aspect_ratio,
769 plug->panel->icon_size - BORDER_WIDTH * 2,
770 1, BORDER_WIDTH,
771 plug->panel->height);
772
773 /* Add GDK event filter. */
774 gdk_window_add_filter(NULL, (GdkFilterFunc) pager_event_filter, pg);
775
776 /* Connect signals to receive root window events and initialize root window properties. */
777 g_signal_connect(G_OBJECT(fbev), "current_desktop", G_CALLBACK(pager_net_current_desktop), (gpointer) pg);
778 g_signal_connect(G_OBJECT(fbev), "active_window", G_CALLBACK(pager_net_active_window), (gpointer) pg);
779 g_signal_connect(G_OBJECT(fbev), "desktop_names", G_CALLBACK(pager_net_desktop_names), (gpointer) pg);
780 g_signal_connect(G_OBJECT(fbev), "number_of_desktops", G_CALLBACK(pager_net_number_of_desktops), (gpointer) pg);
781 g_signal_connect(G_OBJECT(fbev), "client_list_stacking", G_CALLBACK(pager_net_client_list_stacking), (gpointer) pg);
782
783 /* Allocate per-desktop structures. */
784 pager_net_number_of_desktops(fbev, pg);
785 pager_net_desktop_names(fbev, pg);
786 return 1;
787 }
788
789 /* Plugin destructor. */
790 static void pager_destructor(Plugin * p)
791 {
792 PagerPlugin * pg = (PagerPlugin *) p->priv;
793
794 /* Remove GDK event filter. */
795 gdk_window_remove_filter(NULL, (GdkFilterFunc) pager_event_filter, pg);
796
797 /* Remove root window signal handlers. */
798 g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), pager_net_current_desktop, pg);
799 g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), pager_net_active_window, pg);
800 g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), pager_net_number_of_desktops, pg);
801 g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), pager_net_client_list_stacking, pg);
802
803 /* Deallocate desktop structures. */
804 int i;
805 for (i = 0; i < pg->number_of_desktops; i += 1)
806 desk_free(pg, i);
807
808 /* Deallocate task list. */
809 while (pg->task_list != NULL)
810 task_delete(pg, pg->task_list, TRUE);
811
812 /* Deallocate all memory. */
813 icon_grid_free(pg->icon_grid);
814 g_free(pg->tasks_in_stacking_order);
815 g_free(pg);
816 }
817
818 /* Callback when panel configuration changes. */
819 static void pager_panel_configuration_changed(Plugin * p)
820 {
821 /* Reset the icon grid orientation. */
822 PagerPlugin * pg = (PagerPlugin *) p->priv;
823 GtkOrientation bo = (p->panel->orientation == ORIENT_HORIZ) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
824 icon_grid_set_geometry(pg->icon_grid, bo,
825 (p->panel->icon_size - BORDER_WIDTH * 2) * pg->aspect_ratio,
826 p->panel->icon_size - BORDER_WIDTH * 2,
827 1, BORDER_WIDTH,
828 p->panel->height);
829 }
830
831 /* Plugin descriptor. */
832 PluginClass pager_plugin_class = {
833
834 PLUGINCLASS_VERSIONING,
835
836 type : "pager",
837 name : N_("Desktop Pager"),
838 version: "1.0",
839 description : N_("Simple pager plugin"),
840
841 constructor : pager_constructor,
842 destructor : pager_destructor,
843 config : NULL,
844 save : NULL,
845 panel_configuration_changed : pager_panel_configuration_changed
846 };