Enabling multithreaded compilation.
[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 cairo_surface_t * 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 gfloat x = (gfloat) tk->x * d->scale_x;
216 gfloat y = (gfloat) tk->y * d->scale_y;
217 gfloat w = (gfloat) tk->w * d->scale_x;
218 gfloat 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 GdkColor * color;
225
226 cairo_t * cr = cairo_create(d->pixmap);
227 cairo_set_line_width (cr, 1.0);
228 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
229 cairo_rectangle(cr, x + 0.5, y + 0.5, w - 0.5, h - 0.5);
230
231 color =
232 (d->pg->focused_task == tk) ? &style->bg[GTK_STATE_SELECTED] : &style->bg[GTK_STATE_NORMAL];
233 cairo_set_source_rgb(cr, (double)color->red/65535, (double)color->green/65535, (double)color->blue/65535);
234 cairo_fill_preserve(cr);
235
236 color =
237 (d->pg->focused_task == tk) ? &style->fg[GTK_STATE_SELECTED] : &style->fg[GTK_STATE_NORMAL];
238 cairo_set_source_rgb(cr, (double)color->red/65535, (double)color->green/65535, (double)color->blue/65535);
239 cairo_stroke(cr);
240
241 check_cairo_status(cr);
242 cairo_destroy(cr);
243 }
244 }
245 }
246 }
247
248 /*****************************************************************
249 * Desk Functions *
250 *****************************************************************/
251
252 /* Mark a specified desktop for redraw. */
253 static void desk_set_dirty(PagerDesk * d)
254 {
255 d->dirty = TRUE;
256 gtk_widget_queue_draw(d->da);
257 }
258
259 /* Mark all desktops for redraw. */
260 static void desk_set_dirty_all(PagerPlugin * pg)
261 {
262 int i;
263 for (i = 0; i < pg->number_of_desktops; i++)
264 desk_set_dirty(pg->desks[i]);
265 }
266
267 /* Mark the desktop on which a specified window resides for redraw. */
268 static void desk_set_dirty_by_win(PagerPlugin * pg, PagerTask * tk)
269 {
270 if (task_is_visible(tk))
271 {
272 if (tk->desktop < pg->number_of_desktops)
273 desk_set_dirty(pg->desks[tk->desktop]);
274 else
275 desk_set_dirty_all(pg);
276 }
277 }
278
279 /* Handler for configure_event on drawing area. */
280 static gboolean desk_configure_event(GtkWidget * widget, GdkEventConfigure * event, PagerDesk * d)
281 {
282 /* Allocate pixmap and statistics buffer without border pixels. */
283 #if GTK_CHECK_VERSION(2,18,0)
284 GtkAllocation *allocation = g_new0 (GtkAllocation, 1);
285 gtk_widget_get_allocation(GTK_WIDGET(widget), allocation);
286 int new_pixmap_width = allocation->width;
287 int new_pixmap_height = allocation->height;
288 #else
289 int new_pixmap_width = widget->allocation.width;
290 int new_pixmap_height = widget->allocation.height;
291 #endif
292 if ((new_pixmap_width > 0) && (new_pixmap_height > 0))
293 {
294 /* Allocate a new pixmap of the allocated size. */
295 if (d->pixmap != NULL)
296 cairo_surface_destroy(d->pixmap);
297 d->pixmap = cairo_image_surface_create(CAIRO_FORMAT_RGB24, new_pixmap_width, new_pixmap_height);
298 cairo_t *cr = cairo_create(d->pixmap);
299 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
300 cairo_paint(cr);
301 check_cairo_status(cr);
302 cairo_destroy(cr);
303 check_cairo_surface_status(&d->pixmap);
304
305 /* Compute the horizontal and vertical scale factors, and mark the desktop for redraw. */
306 #if GTK_CHECK_VERSION(2,18,0)
307 d->scale_y = (gfloat) allocation->height / (gfloat) gdk_screen_height();
308 d->scale_x = (gfloat) allocation->width / (gfloat) gdk_screen_width();
309 #else
310 d->scale_y = (gfloat) allocation->height / (gfloat) gdk_screen_height();
311 d->scale_x = (gfloat) allocation->width / (gfloat) gdk_screen_width();
312 #endif
313 desk_set_dirty(d);
314 }
315
316 /* Resize to optimal size. */
317 gtk_widget_set_size_request(widget,
318 (d->pg->plugin->panel->icon_size - BORDER_WIDTH * 2) * d->pg->aspect_ratio,
319 d->pg->plugin->panel->icon_size - BORDER_WIDTH * 2);
320 #if GTK_CHECK_VERSION(2,18,0)
321 g_free (allocation);
322 #endif
323 return FALSE;
324 }
325
326 /* Handler for expose_event on drawing area. */
327 static gboolean desk_expose_event(GtkWidget * widget, GdkEventExpose * event, PagerDesk * d)
328 {
329 GtkStyle * style = gtk_widget_get_style(widget);
330
331 if (d->pixmap != NULL)
332 {
333 cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(widget));
334 gdk_cairo_region(cr, event->region);
335 cairo_clip(cr);
336 /* Recompute the pixmap if needed. */
337 if (d->dirty)
338 {
339 d->dirty = FALSE;
340 PagerPlugin * pg = d->pg;
341
342 /* Erase the pixmap. */
343 if (d->pixmap != NULL)
344 {
345 //GtkWidget * widget = GTK_WIDGET(d->da);
346 cairo_t *cr0 = cairo_create(d->pixmap);
347 gdk_cairo_set_source_color(cr0,
348 ((d->desktop_number == d->pg->current_desktop)
349 ? &style->dark[GTK_STATE_SELECTED]
350 : &style->dark[GTK_STATE_NORMAL]));
351 cairo_paint(cr0);
352 check_cairo_status(cr0);
353 cairo_destroy(cr0);
354 }
355
356 /* Draw tasks onto the pixmap. */
357 int j;
358 for (j = 0; j < pg->client_count; j++)
359 task_update_pixmap(pg->tasks_in_stacking_order[j], d);
360 }
361
362 /* Draw the requested part of the pixmap onto the drawing area. */
363 GtkAllocation allocation;
364 gtk_widget_get_allocation(GTK_WIDGET(widget), &allocation);
365 gdk_cairo_set_source_color(cr,
366 &style->fg[GTK_WIDGET_STATE(widget)]);
367 cairo_set_source_surface(cr, d->pixmap, 0, 0);
368 cairo_paint(cr);
369 check_cairo_status(cr);
370 cairo_destroy(cr);
371 }
372 return FALSE;
373 }
374
375 /* Handler for "scroll-event" on drawing area. */
376 static gboolean desk_scroll_event(GtkWidget * widget, GdkEventScroll * event, PagerDesk * d)
377 {
378 /* Compute the new desktop from the scroll direction, wrapping at either extreme. */
379 int current_desktop = d->pg->current_desktop;
380 if ((event->direction == GDK_SCROLL_UP) || (event->direction == GDK_SCROLL_RIGHT))
381 {
382 current_desktop += 1;
383 if (current_desktop >= d->pg->number_of_desktops)
384 current_desktop = 0;
385 }
386 else
387 {
388 current_desktop -= 1;
389 if (current_desktop < 0)
390 current_desktop = d->pg->number_of_desktops - 1;
391 }
392
393 /* Ask the window manager to make the new desktop current. */
394 Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, current_desktop, 0, 0, 0, 0);
395 return TRUE;
396 }
397
398 /* Handler for "button-press-event" on drawing area. */
399 static gboolean desk_button_press_event(GtkWidget * widget, GdkEventButton * event, PagerDesk * d)
400 {
401 /* Standard right-click handling. */
402 if (plugin_button_press_event(widget, event, d->pg->plugin))
403 return TRUE;
404
405 /* Ask the window manager to make the new desktop current. */
406 Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, d->desktop_number, 0, 0, 0, 0);
407 return TRUE;
408 }
409
410 /* Allocate the structure and the graphic elements representing a desktop. */
411 static void desk_new(PagerPlugin * pg, int desktop_number)
412 {
413
414 /* Allocate and initialize structure. */
415 PagerDesk * d = pg->desks[desktop_number] = g_new0(PagerDesk, 1);
416 d->pg = pg;
417 d->desktop_number = desktop_number;
418
419 /* Allocate drawing area. */
420 d->da = gtk_drawing_area_new();
421
422 icon_grid_add(pg->icon_grid, d->da, TRUE);
423 gtk_widget_add_events (d->da, GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK);
424
425 /* Connect signals. */
426 g_signal_connect(G_OBJECT(d->da), "expose_event", G_CALLBACK(desk_expose_event), (gpointer) d);
427 g_signal_connect(G_OBJECT(d->da), "configure_event", G_CALLBACK(desk_configure_event), (gpointer) d);
428 g_signal_connect(G_OBJECT(d->da), "scroll-event", G_CALLBACK(desk_scroll_event), (gpointer) d);
429 g_signal_connect(G_OBJECT(d->da), "button_press_event", G_CALLBACK(desk_button_press_event), (gpointer) d);
430
431 /* Show the widget. */
432 gtk_widget_show(d->da);
433 }
434
435 /* Free the structure representing a desktop. */
436 static void desk_free(PagerPlugin * pg, int desktop_number)
437 {
438 PagerDesk * d = pg->desks[desktop_number];
439
440 g_signal_handlers_disconnect_by_func(G_OBJECT(d->da), desk_expose_event, d);
441 g_signal_handlers_disconnect_by_func(G_OBJECT(d->da), desk_configure_event, d);
442 g_signal_handlers_disconnect_by_func(G_OBJECT(d->da), desk_scroll_event, d);
443 g_signal_handlers_disconnect_by_func(G_OBJECT(d->da), desk_button_press_event, d);
444
445 icon_grid_remove(pg->icon_grid, d->da);
446
447 if (d->pixmap != NULL)
448 cairo_surface_destroy(d->pixmap);
449
450 g_free(d);
451 }
452
453 /*****************************************************************
454 * Pager Functions *
455 *****************************************************************/
456
457 /* Handle PropertyNotify event.
458 * http://tronche.com/gui/x/icccm/
459 * http://standards.freedesktop.org/wm-spec/wm-spec-1.4.html */
460 static void pager_property_notify_event(PagerPlugin * pg, XEvent * ev)
461 {
462 /* State may be PropertyNewValue, PropertyDeleted. */
463 if (((XPropertyEvent*) ev)->state == PropertyNewValue)
464 {
465 Atom at = ev->xproperty.atom;
466 Window win = ev->xproperty.window;
467 if (win != GDK_ROOT_WINDOW())
468 {
469 /* Look up task structure by X window handle. */
470 PagerTask * tk = task_lookup(pg, win);
471 if (tk != NULL)
472 {
473 /* Install an error handler that ignores BadWindow.
474 * We frequently get a PropertyNotify event on deleted windows. */
475 XErrorHandler previous_error_handler = XSetErrorHandler(panel_handle_x_error_swallow_BadWindow_BadDrawable);
476
477 /* Dispatch on atom. */
478 if (at == a_WM_STATE)
479 {
480 /* Window changed state. */
481 tk->ws = get_wm_state(tk->win);
482 desk_set_dirty_by_win(pg, tk);
483 }
484 else if (at == a_NET_WM_STATE)
485 {
486 /* Window changed EWMH state. */
487 get_net_wm_state(tk->win, &tk->nws);
488 desk_set_dirty_by_win(pg, tk);
489 }
490 else if (at == a_NET_WM_DESKTOP)
491 {
492 /* Window changed desktop.
493 * Mark both old and new desktops for redraw. */
494 desk_set_dirty_by_win(pg, tk);
495 tk->desktop = get_net_wm_desktop(tk->win);
496 desk_set_dirty_by_win(pg, tk);
497
498 XSetErrorHandler(previous_error_handler);
499 }
500 }
501 }
502 }
503 }
504
505 /* Handle ConfigureNotify event. */
506 static void pager_configure_notify_event(PagerPlugin * pg, XEvent * ev)
507 {
508 Window win = ev->xconfigure.window;
509 PagerTask * tk = task_lookup(pg, win);
510 if (tk != NULL)
511 {
512 task_get_geometry(tk);
513 desk_set_dirty_by_win(pg, tk);
514 }
515 }
516
517 /* GDK event filter. */
518 static GdkFilterReturn pager_event_filter(XEvent * xev, GdkEvent * event, PagerPlugin * pg)
519 {
520 /* Look for PropertyNotify and ConfigureNotify events and update state. */
521 if (xev->type == PropertyNotify)
522 pager_property_notify_event(pg, xev);
523 else if (xev->type == ConfigureNotify)
524 pager_configure_notify_event(pg, xev);
525 return GDK_FILTER_CONTINUE;
526 }
527
528 /*****************************************************************
529 * Netwm/WM Interclient Communication *
530 *****************************************************************/
531
532 /* Handler for "active-window" event from root window listener. */
533 static void pager_net_active_window(FbEv * ev, PagerPlugin * pg)
534 {
535 Window * focused_window = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_ACTIVE_WINDOW, XA_WINDOW, 0);
536 if (focused_window != NULL)
537 {
538 PagerTask * tk = task_lookup(pg, *focused_window);
539 if (tk != pg->focused_task)
540 {
541 /* Focused task changed. Redraw both old and new. */
542 if (pg->focused_task != NULL)
543 desk_set_dirty_by_win(pg, pg->focused_task);
544 pg->focused_task = tk;
545 if (tk != NULL)
546 desk_set_dirty_by_win(pg, tk);
547 }
548 XFree(focused_window);
549 }
550 else
551 {
552 /* Focused task disappeared. Redraw old. */
553 if (pg->focused_task != NULL)
554 {
555 desk_set_dirty_by_win(pg, pg->focused_task);
556 pg->focused_task = NULL;
557 }
558 }
559 }
560
561 /* Handler for desktop_name event from window manager. */
562 static void pager_net_desktop_names(FbEv * fbev, PagerPlugin * pg)
563 {
564 /* Get the NET_DESKTOP_NAMES property. */
565 int number_of_desktop_names;
566 char * * desktop_names;
567 desktop_names = get_utf8_property_list(GDK_ROOT_WINDOW(), a_NET_DESKTOP_NAMES, &number_of_desktop_names);
568
569 /* Loop to copy the desktop names to the vector of labels.
570 * If there are more desktops than labels, label the extras with a decimal number. */
571 int i;
572 for (i = 0; ((desktop_names != NULL) && (i < MIN(pg->number_of_desktops, number_of_desktop_names))); i++)
573 gtk_widget_set_tooltip_text(pg->desks[i]->da, desktop_names[i]);
574 for ( ; i < pg->number_of_desktops; i++)
575 {
576 char temp[10];
577 sprintf(temp, "%d", i + 1);
578 gtk_widget_set_tooltip_text(pg->desks[i]->da, temp);
579 }
580
581 /* Free the property. */
582 if (desktop_names != NULL)
583 g_strfreev(desktop_names);
584 }
585
586 /* Handler for "current-desktop" event from root window listener. */
587 static void pager_net_current_desktop(FbEv * ev, PagerPlugin * pg)
588 {
589 desk_set_dirty(pg->desks[pg->current_desktop]);
590 pg->current_desktop = get_net_current_desktop();
591 if (pg->current_desktop >= pg->number_of_desktops)
592 pg->current_desktop = 0;
593 desk_set_dirty(pg->desks[pg->current_desktop]);
594 }
595
596
597 /* Handler for "number-of-desktops" event from root window listener.
598 * Also used to initialize plugin. */
599 static void pager_net_number_of_desktops(FbEv * ev, PagerPlugin * pg)
600 {
601 /* Get existing values. */
602 int number_of_desktops = pg->number_of_desktops;
603
604 /* Get the correct number of desktops. */
605 pg->number_of_desktops = get_net_number_of_desktops();
606 if (pg->number_of_desktops < 1)
607 pg->number_of_desktops = 1;
608
609 /* Reallocate the structure if necessary. */
610 if (pg->number_of_desktops > pg->desk_extent)
611 {
612 PagerDesk * * new_desks = g_new(PagerDesk *, pg->number_of_desktops);
613 if (pg->desks != NULL)
614 {
615 memcpy(new_desks, pg->desks, pg->desk_extent * sizeof(PagerDesk *));
616 g_free(pg->desks);
617 }
618 pg->desks = new_desks;
619 pg->desk_extent = pg->number_of_desktops;
620 }
621
622 /* Reconcile the current desktop number. */
623 pg->current_desktop = get_net_current_desktop();
624 if (pg->current_desktop >= pg->number_of_desktops)
625 pg->current_desktop = 0;
626
627 /* Reconcile the old and new number of desktops. */
628 int difference = pg->number_of_desktops - number_of_desktops;
629 if (difference != 0)
630 {
631 if (difference < 0)
632 {
633 /* If desktops were deleted, then delete their maps also. */
634 int i;
635 for (i = pg->number_of_desktops; i < number_of_desktops; i++)
636 desk_free(pg, i);
637 }
638 else
639 {
640 /* If desktops were added, then create their maps also. */
641 int i;
642 for (i = number_of_desktops; i < pg->number_of_desktops; i++)
643 desk_new(pg, i);
644 }
645 }
646
647 /* Refresh the client list. */
648 pager_net_client_list_stacking(NULL, pg);
649 }
650
651 /* Handler for "net-client-list-stacking" event from root window listener. */
652 static void pager_net_client_list_stacking(FbEv * ev, PagerPlugin * pg)
653 {
654 /* Get the NET_CLIENT_LIST_STACKING property. */
655 Window * client_list = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_CLIENT_LIST_STACKING, XA_WINDOW, &pg->client_count);
656 g_free(pg->tasks_in_stacking_order);
657 /* g_new returns NULL if if n_structs == 0 */
658 pg->tasks_in_stacking_order = g_new(PagerTask *, pg->client_count);
659
660 if (client_list != NULL)
661 {
662 /* Loop over client list, correlating it with task list.
663 * Also generate a vector of task pointers in stacking order. */
664 int i;
665 for (i = 0; i < pg->client_count; i++)
666 {
667 /* Search for the window in the task list. Set up context to do an insert right away if needed. */
668 PagerTask * tk_pred = NULL;
669 PagerTask * tk_cursor;
670 PagerTask * tk = NULL;
671 for (tk_cursor = pg->task_list; tk_cursor != NULL; tk_pred = tk_cursor, tk_cursor = tk_cursor->task_flink)
672 {
673 if (tk_cursor->win == client_list[i])
674 {
675 tk = tk_cursor;
676 break;
677 }
678 if (tk_cursor->win > client_list[i])
679 break;
680 }
681
682 /* Task is already in task list. */
683 if (tk != NULL)
684 {
685 tk->present_in_client_list = TRUE;
686
687 /* If the stacking position changed, redraw the desktop. */
688 if (tk->stacking != i)
689 {
690 tk->stacking = i;
691 desk_set_dirty_by_win(pg, tk);
692 }
693 }
694
695 /* Task is not in task list. */
696 else
697 {
698 /* Allocate and initialize new task structure. */
699 tk = g_new0(PagerTask, 1);
700 tk->present_in_client_list = TRUE;
701 tk->win = client_list[i];
702 tk->ws = get_wm_state(tk->win);
703 tk->desktop = get_net_wm_desktop(tk->win);
704 get_net_wm_state(tk->win, &tk->nws);
705 get_net_wm_window_type(tk->win, &tk->nwwt);
706 task_get_geometry(tk);
707 if ( ! FBPANEL_WIN(tk->win))
708 XSelectInput(GDK_DISPLAY(), tk->win, PropertyChangeMask | StructureNotifyMask);
709 desk_set_dirty_by_win(pg, tk);
710
711 /* Link the task structure into the task list. */
712 if (tk_pred == NULL)
713 {
714 tk->task_flink = pg->task_list;
715 pg->task_list = tk;
716 }
717 else
718 {
719 tk->task_flink = tk_pred->task_flink;
720 tk_pred->task_flink = tk;
721 }
722 }
723 pg->tasks_in_stacking_order[i] = tk;
724 }
725 XFree(client_list);
726 }
727
728 /* Remove windows from the task list that are not present in the NET_CLIENT_LIST_STACKING. */
729 PagerTask * tk_pred = NULL;
730 PagerTask * tk = pg->task_list;
731 while (tk != NULL)
732 {
733 PagerTask * tk_succ = tk->task_flink;
734 if (tk->present_in_client_list)
735 {
736 tk->present_in_client_list = FALSE;
737 tk_pred = tk;
738 }
739 else
740 {
741 if (tk_pred == NULL)
742 pg->task_list = tk_succ;
743 else tk_pred->task_flink = tk_succ;
744 task_delete(pg, tk, FALSE);
745 }
746 tk = tk_succ;
747 }
748 }
749
750 /* Plugin constructor. */
751 static int pager_constructor(Plugin * plug, char ** fp)
752 {
753 /* Allocate plugin context and set into Plugin private data pointer. */
754 PagerPlugin * pg = g_new0(PagerPlugin, 1);
755 plug->priv = pg;
756 pg->plugin = plug;
757
758 /* Compute aspect ratio of screen image. */
759 pg->aspect_ratio = (gfloat) gdk_screen_width() / (gfloat) gdk_screen_height();
760
761 /* Allocate top level widget and set into Plugin widget pointer. */
762 plug->pwid = gtk_event_box_new();
763 #if GTK_CHECK_VERSION(2,18,0)
764 gtk_widget_set_has_window(plug->pwid,FALSE);
765 #else
766 GTK_WIDGET_SET_FLAGS(plug->pwid, GTK_NO_WINDOW);
767 #endif
768 gtk_container_set_border_width(GTK_CONTAINER(plug->pwid), 0);
769
770 /* Create an icon grid manager to manage the drawing areas within the container. */
771 GtkOrientation bo = (plug->panel->orientation == ORIENT_HORIZ) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
772 pg->icon_grid = icon_grid_new(plug->panel, plug->pwid, bo,
773 (plug->panel->icon_size - BORDER_WIDTH * 2) * pg->aspect_ratio,
774 plug->panel->icon_size - BORDER_WIDTH * 2,
775 1, BORDER_WIDTH,
776 plug->panel->height);
777
778 /* Add GDK event filter. */
779 gdk_window_add_filter(NULL, (GdkFilterFunc) pager_event_filter, pg);
780
781 /* Connect signals to receive root window events and initialize root window properties. */
782 g_signal_connect(G_OBJECT(fbev), "current_desktop", G_CALLBACK(pager_net_current_desktop), (gpointer) pg);
783 g_signal_connect(G_OBJECT(fbev), "active_window", G_CALLBACK(pager_net_active_window), (gpointer) pg);
784 g_signal_connect(G_OBJECT(fbev), "desktop_names", G_CALLBACK(pager_net_desktop_names), (gpointer) pg);
785 g_signal_connect(G_OBJECT(fbev), "number_of_desktops", G_CALLBACK(pager_net_number_of_desktops), (gpointer) pg);
786 g_signal_connect(G_OBJECT(fbev), "client_list_stacking", G_CALLBACK(pager_net_client_list_stacking), (gpointer) pg);
787
788 /* Allocate per-desktop structures. */
789 pager_net_number_of_desktops(fbev, pg);
790 pager_net_desktop_names(fbev, pg);
791 return 1;
792 }
793
794 /* Plugin destructor. */
795 static void pager_destructor(Plugin * p)
796 {
797 PagerPlugin * pg = (PagerPlugin *) p->priv;
798
799 /* Remove GDK event filter. */
800 gdk_window_remove_filter(NULL, (GdkFilterFunc) pager_event_filter, pg);
801
802 /* Remove root window signal handlers. */
803 g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), pager_net_current_desktop, pg);
804 g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), pager_net_active_window, pg);
805 g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), pager_net_number_of_desktops, pg);
806 g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), pager_net_client_list_stacking, pg);
807
808 /* Deallocate desktop structures. */
809 int i;
810 for (i = 0; i < pg->number_of_desktops; i += 1)
811 desk_free(pg, i);
812
813 /* Deallocate task list. */
814 while (pg->task_list != NULL)
815 task_delete(pg, pg->task_list, TRUE);
816
817 /* Deallocate all memory. */
818 icon_grid_free(pg->icon_grid);
819 g_free(pg->tasks_in_stacking_order);
820 g_free(pg);
821 }
822
823 /* Callback when panel configuration changes. */
824 static void pager_panel_configuration_changed(Plugin * p)
825 {
826 /* Reset the icon grid orientation. */
827 PagerPlugin * pg = (PagerPlugin *) p->priv;
828 GtkOrientation bo = (p->panel->orientation == ORIENT_HORIZ) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
829 icon_grid_set_geometry(pg->icon_grid, bo,
830 (p->panel->icon_size - BORDER_WIDTH * 2) * pg->aspect_ratio,
831 p->panel->icon_size - BORDER_WIDTH * 2,
832 1, BORDER_WIDTH,
833 p->panel->height);
834 }
835
836 /* Plugin descriptor. */
837 PluginClass pager_plugin_class = {
838
839 PLUGINCLASS_VERSIONING,
840
841 type : "pager",
842 name : N_("Desktop Pager"),
843 version: "1.0",
844 description : N_("Simple pager plugin"),
845
846 constructor : pager_constructor,
847 destructor : pager_destructor,
848 config : NULL,
849 save : NULL,
850 panel_configuration_changed : pager_panel_configuration_changed
851 };