Adding upstream version 0.5.6.
[debian/lxpanel.git] / src / plugins / taskbar.c
CommitLineData
6cc5e1a6
DB
1/**
2 * Copyright (c) 2006 LxDE Developers, see the file AUTHORS for details.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19#include <stdlib.h>
20#include <stdio.h>
21#include <string.h>
22#include <stdlib.h>
23
24#include <X11/Xlib.h>
25#include <X11/Xutil.h>
6cc5e1a6
DB
26
27#include <gdk-pixbuf/gdk-pixbuf.h>
28#include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
29#include <gdk/gdk.h>
30#include <glib/gi18n.h>
31
32#include "panel.h"
33#include "misc.h"
34#include "plugin.h"
35#include "icon.xpm"
36#include "gtkbar.h"
2ba86315 37#include "icon-grid.h"
6cc5e1a6
DB
38
39/*
40 * 2006.09.10 modified by Hong Jen Yee (PCMan) pcman.tw (AT) gmail.com
41 * Following features are added:
42 * 1. Add XUrgencyHint support. (Flashing task bar buttons, can be disabled)
43 * 2. Raise window when files get dragged over taskbar buttons.
44 * 3. Add Restore & Maximize menu items to popup menu of task bar buttons.
45 */
46
47#include "dbg.h"
48
49struct _taskbar;
2ba86315
DB
50struct _task_class;
51struct _task;
52
53/* Structure representing a class. This comes from WM_CLASS, and should identify windows that come from an application. */
54typedef struct _task_class {
55 struct _task_class * res_class_flink; /* Forward link */
56 char * res_class; /* Class name */
57 struct _task * res_class_head; /* Head of list of tasks with this class */
58 struct _task * visible_task; /* Task that is visible in current desktop, if any */
59 char * visible_name; /* Name that will be visible for grouped tasks */
60 int visible_count; /* Count of tasks that are visible in current desktop */
61} TaskClass;
62
63/* Structure representing a "task", an open window. */
64typedef struct _task {
65 struct _task * task_flink; /* Forward link to next task in X window ID order */
66 struct _taskbar * tb; /* Back pointer to taskbar */
67 Window win; /* X window ID */
68 char * name; /* Taskbar label when normal, from WM_NAME or NET_WM_NAME */
69 char * name_iconified; /* Taskbar label when iconified */
70 Atom name_source; /* Atom that is the source of taskbar label */
71 TaskClass * res_class; /* Class, from WM_CLASS */
72 struct _task * res_class_flink; /* Forward link to task in same class */
73 GtkWidget * button; /* Button representing task in taskbar */
74 GtkWidget * image; /* Icon for task, child of button */
75 Atom image_source; /* Atom that is the source of taskbar icon */
76 GtkWidget * label; /* Label for task, child of button */
77 int desktop; /* Desktop that contains task, needed to switch to it on Raise */
78 guint flash_timeout; /* Timer for urgency notification */
79 unsigned int focused : 1; /* True if window has focus */
80 unsigned int iconified : 1; /* True if window is iconified, from WM_STATE */
81 unsigned int urgency : 1; /* True if window has an urgency hint, from WM_HINTS */
82 unsigned int flash_state : 1; /* One-bit counter to flash taskbar */
83 unsigned int entered_state : 1; /* True if cursor is inside taskbar button */
84 unsigned int present_in_client_list : 1; /* State during WM_CLIENT_LIST processing to detect deletions */
85} Task;
86
87/* Private context for taskbar plugin. */
88typedef struct _taskbar {
89 Plugin * plug; /* Back pointer to Plugin */
90 Task * task_list; /* List of tasks to be displayed in taskbar */
91 TaskClass * res_class_list; /* Window class list */
92 IconGrid * icon_grid; /* Manager for taskbar buttons */
93 GtkWidget * menu; /* Popup menu for task control (Close, Raise, etc.) */
94 GtkWidget * group_menu; /* Popup menu for grouping selection */
95 GdkPixbuf * fallback_pixbuf; /* Fallback task icon when none is available */
96 int number_of_desktops; /* Number of desktops, from NET_WM_NUMBER_OF_DESKTOPS */
97 int current_desktop; /* Current desktop, from NET_WM_CURRENT_DESKTOP */
98 Task * focused; /* Task that has focus */
99 Task * focused_previous; /* Task that had focus just before panel got it */
100 Task * menutask; /* Task for which popup menu is open */
101 guint dnd_delay_timer; /* Timer for drag and drop delay */
102 int icon_size; /* Size of task icons */
103 gboolean show_all_desks; /* User preference: show windows from all desktops */
104 gboolean tooltips; /* User preference: show tooltips */
105 gboolean icons_only; /* User preference: show icons only, omit name */
106 gboolean use_mouse_wheel; /* User preference: scroll wheel does iconify and raise */
107 gboolean use_urgency_hint; /* User preference: windows with urgency will flash */
108 gboolean flat_button; /* User preference: taskbar buttons have visible background */
109 gboolean grouped_tasks; /* User preference: windows from same task are grouped onto a single button */
110 int task_width_max; /* Maximum width of a taskbar button in horizontal orientation */
111 int spacing; /* Spacing between taskbar buttons */
112 gboolean use_net_active; /* NET_WM_ACTIVE_WINDOW is supported by the window manager */
32a67dc7 113 gboolean net_active_checked; /* True if use_net_active is valid */
2ba86315 114} TaskbarPlugin;
6cc5e1a6
DB
115
116static gchar *taskbar_rc = "style 'taskbar-style'\n"
117"{\n"
2ba86315
DB
118"GtkWidget::focus-padding=0\n" /* FIXME: seem to fix #2821771, not sure if this is ok. */
119"GtkWidget::focus-line-width=0\n"
120"GtkWidget::focus-padding=0\n"
121"GtkButton::default-border={0,0,0,0}\n"
122"GtkButton::default-outside-border={0,0,0,0}\n"
123"GtkButton::inner-border={0,0,0,0}\n" /* added in gtk+ 2.10 */
6cc5e1a6
DB
124"}\n"
125"widget '*.taskbar.*' style 'taskbar-style'";
126
2ba86315
DB
127#define DRAG_ACTIVE_DELAY 1000
128#define TASK_WIDTH_MAX 200
129#define TASK_PADDING 4
4652f59b 130#define ALL_WORKSPACES 0xFFFFFFFF /* 64-bit clean */
2ba86315
DB
131#define ICON_ONLY_EXTRA 6 /* Amount needed to have button lay out symmetrically */
132#define BUTTON_HEIGHT_EXTRA 4 /* Amount needed to have button not clip icon */
133
134static void set_timer_on_task(Task * tk);
135static gboolean task_is_visible_on_current_desktop(TaskbarPlugin * tb, Task * tk);
136static void recompute_group_visibility_for_class(TaskbarPlugin * tb, TaskClass * tc);
137static void recompute_group_visibility_on_current_desktop(TaskbarPlugin * tb);
138static void task_draw_label(Task * tk);
139static gboolean task_is_visible(TaskbarPlugin * tb, Task * tk);
140static void task_button_redraw(Task * tk, TaskbarPlugin * tb);
141static void taskbar_redraw(TaskbarPlugin * tb);
142static gboolean accept_net_wm_state(NetWMState * nws);
143static gboolean accept_net_wm_window_type(NetWMWindowType * nwwt);
144static void task_free_names(Task * tk);
145static void task_set_names(Task * tk, Atom source);
146static void task_unlink_class(Task * tk);
147static TaskClass * taskbar_enter_res_class(TaskbarPlugin * tb, char * res_class, gboolean * name_consumed);
148static void task_set_class(Task * tk);
149static Task * task_lookup(TaskbarPlugin * tb, Window win);
150static void task_delete(TaskbarPlugin * tb, Task * tk, gboolean unlink);
32a67dc7 151static GdkPixbuf * _wnck_gdk_pixbuf_get_from_pixmap(Pixmap xpixmap, int width, int height);
2ba86315
DB
152static GdkPixbuf * apply_mask(GdkPixbuf * pixbuf, GdkPixbuf * mask);
153static GdkPixbuf * get_wm_icon(Window task_win, int required_width, int required_height, Atom source, Atom * current_source);
154static GdkPixbuf * task_update_icon(TaskbarPlugin * tb, Task * tk, Atom source);
155static gboolean flash_window_timeout(Task * tk);
156static void task_set_urgency(Task * tk);
157static void task_clear_urgency(Task * tk);
158static void task_raise_window(Task * tk, guint32 time);
159static void taskbar_popup_set_position(GtkWidget * menu, gint * px, gint * py, gboolean * push_in, gpointer data);
160static void task_group_menu_destroy(TaskbarPlugin * tb);
161static gboolean taskbar_task_control_event(GtkWidget * widget, GdkEventButton * event, Task * tk, gboolean popup_menu);
162static gboolean taskbar_button_press_event(GtkWidget * widget, GdkEventButton * event, Task * tk);
163static gboolean taskbar_popup_activate_event(GtkWidget * widget, GdkEventButton * event, Task * tk);
164static gboolean taskbar_button_drag_motion_timeout(Task * tk);
165static gboolean taskbar_button_drag_motion(GtkWidget * widget, GdkDragContext * drag_context, gint x, gint y, guint time, Task * tk);
4652f59b 166static void taskbar_button_drag_leave(GtkWidget * widget, GdkDragContext * drag_context, guint time, Task * tk);
2ba86315
DB
167static void taskbar_button_enter(GtkWidget * widget, Task * tk);
168static void taskbar_button_leave(GtkWidget * widget, Task * tk);
169static gboolean taskbar_button_scroll_event(GtkWidget * widget, GdkEventScroll * event, Task * tk);
170static void taskbar_button_size_allocate(GtkWidget * btn, GtkAllocation * alloc, Task * tk);
171static void taskbar_update_style(TaskbarPlugin * tb);
172static void task_update_style(Task * tk, TaskbarPlugin * tb);
173static void task_build_gui(TaskbarPlugin * tb, Task * tk);
174static void taskbar_net_client_list(GtkWidget * widget, TaskbarPlugin * tb);
175static void taskbar_net_current_desktop(GtkWidget * widget, TaskbarPlugin * tb);
176static void taskbar_net_number_of_desktops(GtkWidget * widget, TaskbarPlugin * tb);
177static void taskbar_net_active_window(GtkWidget * widget, TaskbarPlugin * tb);
178static gboolean task_has_urgency(Task * tk);
179static void taskbar_property_notify_event(TaskbarPlugin * tb, XEvent *ev);
180static GdkFilterReturn taskbar_event_filter(XEvent * xev, GdkEvent * event, TaskbarPlugin * tb);
181static void menu_raise_window(GtkWidget * widget, TaskbarPlugin * tb);
182static void menu_restore_window(GtkWidget * widget, TaskbarPlugin * tb);
183static void menu_maximize_window(GtkWidget * widget, TaskbarPlugin * tb);
184static void menu_iconify_window(GtkWidget * widget, TaskbarPlugin * tb);
185static void menu_move_to_workspace(GtkWidget * widget, TaskbarPlugin * tb);
186static void menu_close_window(GtkWidget * widget, TaskbarPlugin * tb);
187static void taskbar_make_menu(TaskbarPlugin * tb);
32a67dc7 188static void taskbar_window_manager_changed(GdkScreen * screen, TaskbarPlugin * tb);
2ba86315 189static void taskbar_build_gui(Plugin * p);
2ba86315
DB
190static int taskbar_constructor(Plugin * p, char ** fp);
191static void taskbar_destructor(Plugin * p);
192static void taskbar_apply_configuration(Plugin * p);
193static void taskbar_configure(Plugin * p, GtkWindow * parent);
194static void taskbar_save_configuration(Plugin * p, FILE * fp);
195static void taskbar_panel_configuration_changed(Plugin * p);
196
197/* Set an urgency timer on a task. */
198static void set_timer_on_task(Task * tk)
199{
200 gint interval;
201 g_object_get(gtk_widget_get_settings(tk->button), "gtk-cursor-blink-time", &interval, NULL);
202 tk->flash_timeout = g_timeout_add(interval, (GSourceFunc) flash_window_timeout, tk);
203}
6cc5e1a6 204
2ba86315
DB
205/* Determine if a task is visible considering only its desktop placement. */
206static gboolean task_is_visible_on_current_desktop(TaskbarPlugin * tb, Task * tk)
207{
208 return ((tk->desktop == ALL_WORKSPACES) || (tk->desktop == tb->current_desktop) || (tb->show_all_desks));
209}
6cc5e1a6 210
2ba86315
DB
211/* Recompute the visible task for a class when the class membership changes.
212 * Also transfer the urgency state to the visible task if necessary. */
213static void recompute_group_visibility_for_class(TaskbarPlugin * tb, TaskClass * tc)
214{
215 tc->visible_count = 0;
216 tc->visible_task = NULL;
217 tc->visible_name = NULL;
218 Task * flashing_task = NULL;
219 gboolean class_has_urgency = FALSE;
220 Task * tk;
221 for (tk = tc->res_class_head; tk != NULL; tk = tk->res_class_flink)
222 {
223 if (task_is_visible_on_current_desktop(tb, tk))
224 {
225 /* Count visible tasks and make the first visible task the one that is used for display. */
226 if (tc->visible_count == 0)
227 tc->visible_task = tk;
228 tc->visible_count += 1;
229
230 /* Compute summary bit for urgency anywhere in the class. */
231 if (tk->urgency)
232 class_has_urgency = TRUE;
233
234 /* If there is urgency, record the currently flashing task. */
235 if (tk->flash_timeout != 0)
236 flashing_task = tk;
237
238 /* Compute the visible name. If all visible windows have the same title, use that.
239 * Otherwise, use the class name. This follows WNCK.
240 * Note that the visible name is not a separate string, but is set to point to one of the others. */
241 if (tc->visible_name == NULL)
242 tc->visible_name = tk->name;
243 else if ((tc->visible_name != tc->res_class)
244 && (tc->visible_name != NULL) && (tk->name != NULL)
245 && (strcmp(tc->visible_name, tk->name) != 0))
246 tc->visible_name = tc->res_class;
247 }
248 }
6cc5e1a6 249
2ba86315
DB
250 /* Transfer the flash timeout to the visible task. */
251 if (class_has_urgency)
252 {
253 if (flashing_task == NULL)
254 {
255 /* Set the flashing context and flash the window immediately. */
256 tc->visible_task->flash_state = TRUE;
257 flash_window_timeout(tc->visible_task);
6cc5e1a6 258
2ba86315
DB
259 /* Set the timer, since none is set. */
260 set_timer_on_task(tc->visible_task);
261 }
262 else if (flashing_task != tc->visible_task)
263 {
264 /* Reset the timer on the new representative.
265 * There will be a slight hiccup on the flash cadence. */
266 g_source_remove(flashing_task->flash_timeout);
267 flashing_task->flash_timeout = 0;
268 tc->visible_task->flash_state = flashing_task->flash_state;
269 flashing_task->flash_state = FALSE;
270 set_timer_on_task(tc->visible_task);
271 }
272 }
273 else
274 {
275 /* No task has urgency. Cancel the timer if one is set. */
276 if (flashing_task != NULL)
277 {
278 g_source_remove(flashing_task->flash_timeout);
279 flashing_task->flash_state = FALSE;
4652f59b 280 flashing_task->flash_timeout = 0;
2ba86315
DB
281 }
282 }
283}
6cc5e1a6 284
2ba86315
DB
285/* Recompute the visible task for all classes when the desktop changes. */
286static void recompute_group_visibility_on_current_desktop(TaskbarPlugin * tb)
287{
288 TaskClass * tc;
289 for (tc = tb->res_class_list; tc != NULL; tc = tc->res_class_flink)
290 {
291 recompute_group_visibility_for_class(tb, tc);
292 }
293}
6cc5e1a6 294
2ba86315
DB
295/* Draw the label and tooltip on a taskbar button. */
296static void task_draw_label(Task * tk)
297{
298 TaskClass * tc = tk->res_class;
4652f59b 299 gboolean bold_style = (((tk->entered_state) || (tk->flash_state)) && (tk->tb->flat_button));
2ba86315
DB
300 if ((tk->tb->grouped_tasks) && (tc != NULL) && (tc->visible_task == tk) && (tc->visible_count > 1))
301 {
302 char * label = g_strdup_printf("(%d) %s", tc->visible_count, tc->visible_name);
303 gtk_widget_set_tooltip_text(tk->button, label);
304 panel_draw_label_text(tk->tb->plug->panel, tk->label, label, bold_style, tk->tb->flat_button);
305 g_free(label);
306 }
307 else
308 {
309 char * name = tk->iconified ? tk->name_iconified : tk->name;
310 if (tk->tb->tooltips)
311 gtk_widget_set_tooltip_text(tk->button, name);
312 panel_draw_label_text(tk->tb->plug->panel, tk->label, name, bold_style, tk->tb->flat_button);
313 }
314}
6cc5e1a6 315
2ba86315
DB
316/* Determine if a task is visible. */
317static gboolean task_is_visible(TaskbarPlugin * tb, Task * tk)
318{
319 /* Not visible due to grouping. */
320 if ((tb->grouped_tasks) && (tk->res_class != NULL) && (tk->res_class->visible_task != tk))
321 return FALSE;
6cc5e1a6 322
2ba86315
DB
323 /* Desktop placement. */
324 return task_is_visible_on_current_desktop(tb, tk);
325}
6cc5e1a6 326
2ba86315
DB
327/* Redraw a task button. */
328static void task_button_redraw(Task * tk, TaskbarPlugin * tb)
6cc5e1a6 329{
2ba86315
DB
330 if (task_is_visible(tb, tk))
331 {
332 task_draw_label(tk);
333 icon_grid_set_visible(tb->icon_grid, tk->button, TRUE);
6cc5e1a6 334 }
2ba86315
DB
335 else
336 icon_grid_set_visible(tb->icon_grid, tk->button, FALSE);
6cc5e1a6
DB
337}
338
2ba86315
DB
339/* Redraw all tasks in the taskbar. */
340static void taskbar_redraw(TaskbarPlugin * tb)
6cc5e1a6 341{
2ba86315
DB
342 Task * tk;
343 for (tk = tb->task_list; tk != NULL; tk = tk->task_flink)
344 task_button_redraw(tk, tb);
6cc5e1a6
DB
345}
346
2ba86315
DB
347/* Determine if a task should be visible given its NET_WM_STATE. */
348static gboolean accept_net_wm_state(NetWMState * nws)
6cc5e1a6 349{
2ba86315 350 return ( ! (nws->skip_taskbar));
6cc5e1a6
DB
351}
352
2ba86315
DB
353/* Determine if a task should be visible given its NET_WM_WINDOW_TYPE. */
354static gboolean accept_net_wm_window_type(NetWMWindowType * nwwt)
355{
356 return ( ! ((nwwt->desktop) || (nwwt->dock) || (nwwt->splash)));
357}
6cc5e1a6 358
2ba86315
DB
359/* Free the names associated with a task. */
360static void task_free_names(Task * tk)
6cc5e1a6 361{
6cc5e1a6 362 g_free(tk->name);
2ba86315
DB
363 g_free(tk->name_iconified);
364 tk->name = tk->name_iconified = NULL;
6cc5e1a6
DB
365}
366
2ba86315
DB
367/* Set the names associated with a task.
368 * This is expected to be the same as the title the window manager is displaying. */
369static void task_set_names(Task * tk, Atom source)
6cc5e1a6 370{
2ba86315 371 char * name = NULL;
6cc5e1a6 372
2ba86315
DB
373 /* Try _NET_WM_VISIBLE_NAME, which supports UTF-8.
374 * If it is set, the window manager is displaying it as the window title. */
375 if ((source == None) || (source == a_NET_WM_VISIBLE_NAME))
376 {
377 name = get_utf8_property(tk->win, a_NET_WM_VISIBLE_NAME);
378 if (name != NULL)
379 tk->name_source = a_NET_WM_VISIBLE_NAME;
6cc5e1a6 380 }
6cc5e1a6 381
2ba86315
DB
382 /* Try _NET_WM_NAME, which supports UTF-8, but do not overwrite _NET_WM_VISIBLE_NAME. */
383 if ((name == NULL)
384 && ((source == None) || (source == a_NET_WM_NAME))
385 && ((tk->name_source == None) || (tk->name_source == a_NET_WM_NAME) || (tk->name_source == XA_WM_NAME)))
386 {
387 name = get_utf8_property(tk->win, a_NET_WM_NAME);
388 if (name != NULL)
389 tk->name_source = a_NET_WM_NAME;
390 }
6cc5e1a6 391
2ba86315
DB
392 /* Try WM_NAME, which supports only ISO-8859-1, but do not overwrite _NET_WM_VISIBLE_NAME or _NET_WM_NAME. */
393 if ((name == NULL)
394 && ((source == None) || (source == XA_WM_NAME))
395 && ((tk->name_source == None) || (tk->name_source == XA_WM_NAME)))
396 {
397 name = get_textproperty(tk->win, XA_WM_NAME);
398 if (name != NULL)
399 tk->name_source = XA_WM_NAME;
400 }
6cc5e1a6 401
2ba86315
DB
402 /* Set the name into the task context, and also on the tooltip. */
403 if (name != NULL)
404 {
405 task_free_names(tk);
406 tk->name = g_strdup(name);
407 tk->name_iconified = g_strdup_printf("[%s]", name);
408 g_free(name);
6cc5e1a6 409
2ba86315
DB
410 /* Redraw the button. */
411 task_button_redraw(tk, tk->tb);
412 }
6cc5e1a6
DB
413}
414
2ba86315
DB
415/* Unlink a task from the class list because its class changed or it was deleted. */
416static void task_unlink_class(Task * tk)
6cc5e1a6 417{
2ba86315
DB
418 TaskClass * tc = tk->res_class;
419 if (tc != NULL)
6cc5e1a6 420 {
2ba86315
DB
421 /* Remove from per-class task list. */
422 if (tc->res_class_head == tk)
6cc5e1a6 423 {
2ba86315
DB
424 /* Removing the head of the list. This causes a new task to be the visible task, so we redraw. */
425 tc->res_class_head = tk->res_class_flink;
426 if (tc->res_class_head != NULL)
427 task_button_redraw(tc->res_class_head, tk->tb);
6cc5e1a6 428 }
2ba86315 429 else
6cc5e1a6 430 {
2ba86315
DB
431 /* Locate the task and its predecessor in the list and then remove it. For safety, ensure it is found. */
432 Task * tk_pred = NULL;
433 Task * tk_cursor;
434 for (
435 tk_cursor = tc->res_class_head;
436 ((tk_cursor != NULL) && (tk_cursor != tk));
437 tk_pred = tk_cursor, tk_cursor = tk_cursor->res_class_flink) ;
438 if (tk_cursor == tk)
439 tk_pred->res_class_flink = tk->res_class_flink;
6cc5e1a6 440 }
2ba86315
DB
441
442 /* Recompute group visibility. */
443 recompute_group_visibility_for_class(tk->tb, tc);
6cc5e1a6 444 }
2ba86315 445}
6cc5e1a6 446
2ba86315
DB
447/* Enter class with specified name. */
448static TaskClass * taskbar_enter_res_class(TaskbarPlugin * tb, char * res_class, gboolean * name_consumed)
449 {
450 /* Find existing entry or insertion point. */
451 *name_consumed = FALSE;
452 TaskClass * tc_pred = NULL;
453 TaskClass * tc;
454 for (tc = tb->res_class_list; tc != NULL; tc_pred = tc, tc = tc->res_class_flink)
455 {
456 int status = strcmp(res_class, tc->res_class);
457 if (status == 0)
458 return tc;
459 if (status < 0)
460 break;
461 }
6cc5e1a6 462
2ba86315
DB
463 /* Insert new entry. */
464 tc = g_new0(TaskClass, 1);
465 tc->res_class = res_class;
466 *name_consumed = TRUE;
467 if (tc_pred == NULL)
468 {
469 tc->res_class_flink = tb->res_class_list;
470 tb->res_class_list = tc;
471 }
472 else
473 {
474 tc->res_class_flink = tc_pred->res_class_flink;
475 tc_pred->res_class_flink = tc;
476 }
477 return tc;
6cc5e1a6
DB
478}
479
2ba86315
DB
480/* Set the class associated with a task. */
481static void task_set_class(Task * tk)
6cc5e1a6 482{
2ba86315
DB
483 /* Read the WM_CLASS property. */
484 XClassHint ch;
485 ch.res_name = NULL;
486 ch.res_class = NULL;
487 XGetClassHint(GDK_DISPLAY(), tk->win, &ch);
6cc5e1a6 488
2ba86315
DB
489 /* If the res_name was returned, free it. We make no use of it at this time. */
490 if (ch.res_name != NULL)
491 {
492 XFree(ch.res_name);
493 }
6cc5e1a6 494
2ba86315
DB
495 /* If the res_class was returned, process it.
496 * This identifies the application that created the window and is the basis for taskbar grouping. */
497 if (ch.res_class != NULL)
498 {
499 /* Convert the class to UTF-8 and enter it in the class table. */
500 gchar * res_class = g_locale_to_utf8(ch.res_class, -1, NULL, NULL, NULL);
4652f59b 501 if (res_class != NULL)
2ba86315 502 {
4652f59b
DB
503 gboolean name_consumed;
504 TaskClass * tc = taskbar_enter_res_class(tk->tb, res_class, &name_consumed);
505 if ( ! name_consumed) g_free(res_class);
6cc5e1a6 506
4652f59b
DB
507 /* If the task changed class, update data structures. */
508 TaskClass * old_tc = tk->res_class;
509 if (old_tc != tc)
2ba86315 510 {
4652f59b
DB
511 /* Unlink from previous class, if any. */
512 task_unlink_class(tk);
6cc5e1a6 513
4652f59b
DB
514 /* Add to end of per-class task list. Do this to keep the popup menu in order of creation. */
515 if (tc->res_class_head == NULL)
516 tc->res_class_head = tk;
517 else
518 {
519 Task * tk_pred;
520 for (tk_pred = tc->res_class_head; tk_pred->res_class_flink != NULL; tk_pred = tk_pred->res_class_flink) ;
521 tk_pred->res_class_flink = tk;
522 task_button_redraw(tk, tk->tb);
523 }
524 tk->res_class = tc;
525
526 /* Recompute group visibility. */
527 recompute_group_visibility_for_class(tk->tb, tc);
528 }
2ba86315
DB
529 }
530 XFree(ch.res_class);
531 }
6cc5e1a6
DB
532}
533
2ba86315
DB
534/* Look up a task in the task list. */
535static Task * task_lookup(TaskbarPlugin * tb, Window win)
6cc5e1a6 536{
2ba86315
DB
537 Task * tk;
538 for (tk = tb->task_list; tk != NULL; tk = tk->task_flink)
539 {
540 if (tk->win == win)
541 return tk;
542 if (tk->win > win)
543 break;
544 }
545 return NULL;
546}
6cc5e1a6 547
2ba86315
DB
548/* Delete a task and optionally unlink it from the task list. */
549static void task_delete(TaskbarPlugin * tb, Task * tk, gboolean unlink)
550{
551 /* If we think this task had focus, remove that. */
552 if (tb->focused == tk)
553 tb->focused = NULL;
6cc5e1a6 554
4652f59b
DB
555 /* If there is an urgency timeout, remove it. */
556 if (tk->flash_timeout != 0)
557 g_source_remove(tk->flash_timeout);
558
2ba86315
DB
559 /* Deallocate structures. */
560 icon_grid_remove(tb->icon_grid, tk->button);
561 task_free_names(tk);
562 task_unlink_class(tk);
6cc5e1a6 563
2ba86315
DB
564 /* If requested, unlink the task from the task list.
565 * If not requested, the caller will do this. */
566 if (unlink)
6cc5e1a6 567 {
2ba86315
DB
568 if (tb->task_list == tk)
569 tb->task_list = tk->task_flink;
570 else
6cc5e1a6 571 {
2ba86315
DB
572 /* Locate the task and its predecessor in the list and then remove it. For safety, ensure it is found. */
573 Task * tk_pred = NULL;
574 Task * tk_cursor;
575 for (
576 tk_cursor = tb->task_list;
577 ((tk_cursor != NULL) && (tk_cursor != tk));
578 tk_pred = tk_cursor, tk_cursor = tk_cursor->task_flink) ;
579 if (tk_cursor == tk)
580 tk_pred->task_flink = tk->task_flink;
6cc5e1a6 581 }
6cc5e1a6
DB
582 }
583
2ba86315
DB
584 /* Deallocate the task structure. */
585 g_free(tk);
7486d297 586}
6cc5e1a6 587
32a67dc7
DB
588/* Get a pixbuf from a pixmap.
589 * Originally from libwnck, Copyright (C) 2001 Havoc Pennington. */
590static GdkPixbuf * _wnck_gdk_pixbuf_get_from_pixmap(Pixmap xpixmap, int width, int height)
6cc5e1a6 591{
32a67dc7
DB
592 /* Get the drawable. */
593 GdkDrawable * drawable = gdk_xid_table_lookup(xpixmap);
594 if (drawable != NULL)
595 g_object_ref(G_OBJECT(drawable));
2ba86315 596 else
32a67dc7
DB
597 drawable = gdk_pixmap_foreign_new(xpixmap);
598
599 GdkColormap * colormap = NULL;
600 GdkPixbuf * retval = NULL;
601 if (drawable != NULL)
2ba86315 602 {
32a67dc7
DB
603 /* Get the colormap.
604 * If the drawable has no colormap, use no colormap or the system colormap as recommended in the documentation of gdk_drawable_get_colormap. */
605 colormap = gdk_drawable_get_colormap(drawable);
606 gint depth = gdk_drawable_get_depth(drawable);
607 if (colormap != NULL)
608 g_object_ref(G_OBJECT(colormap));
609 else if (depth == 1)
2ba86315 610 colormap = NULL;
2ba86315
DB
611 else
612 {
32a67dc7 613 colormap = gdk_screen_get_system_colormap(gdk_drawable_get_screen(drawable));
2ba86315
DB
614 g_object_ref(G_OBJECT(colormap));
615 }
2ba86315 616
32a67dc7
DB
617 /* Be sure we aren't going to fail due to visual mismatch. */
618 if ((colormap != NULL) && (gdk_colormap_get_visual(colormap)->depth != depth))
619 {
620 g_object_unref(G_OBJECT(colormap));
621 colormap = NULL;
622 }
2ba86315 623
32a67dc7
DB
624 /* Do the major work. */
625 retval = gdk_pixbuf_get_from_drawable(NULL, drawable, colormap, 0, 0, 0, 0, width, height);
626 }
2ba86315
DB
627
628 /* Clean up and return. */
629 if (colormap != NULL)
630 g_object_unref(G_OBJECT(colormap));
32a67dc7
DB
631 if (drawable != NULL)
632 g_object_unref(G_OBJECT(drawable));
2ba86315 633 return retval;
7486d297
DB
634}
635
2ba86315 636/* Apply a mask to a pixbuf.
32a67dc7 637 * Originally from libwnck, Copyright (C) 2001 Havoc Pennington. */
2ba86315 638static GdkPixbuf * apply_mask(GdkPixbuf * pixbuf, GdkPixbuf * mask)
6cc5e1a6 639{
2ba86315
DB
640 /* Initialize. */
641 int w = MIN(gdk_pixbuf_get_width(mask), gdk_pixbuf_get_width(pixbuf));
642 int h = MIN(gdk_pixbuf_get_height(mask), gdk_pixbuf_get_height(pixbuf));
643 GdkPixbuf * with_alpha = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
644 guchar * dst = gdk_pixbuf_get_pixels(with_alpha);
645 guchar * src = gdk_pixbuf_get_pixels(mask);
646 int dst_stride = gdk_pixbuf_get_rowstride(with_alpha);
647 int src_stride = gdk_pixbuf_get_rowstride(mask);
648
649 /* Loop to do the work. */
650 int i;
651 for (i = 0; i < h; i += 1)
7486d297 652 {
2ba86315
DB
653 int j;
654 for (j = 0; j < w; j += 1)
7486d297 655 {
2ba86315
DB
656 guchar * s = src + i * src_stride + j * 3;
657 guchar * d = dst + i * dst_stride + j * 4;
6cc5e1a6 658
2ba86315
DB
659 /* s[0] == s[1] == s[2], they are 255 if the bit was set, 0 otherwise. */
660 d[3] = ((s[0] == 0) ? 0 : 255); /* 0 = transparent, 255 = opaque */
661 }
662 }
6cc5e1a6 663
2ba86315
DB
664 return with_alpha;
665}
7486d297 666
2ba86315
DB
667/* Get an icon from the window manager for a task, and scale it to a specified size. */
668static GdkPixbuf * get_wm_icon(Window task_win, int required_width, int required_height, Atom source, Atom * current_source)
669{
670 /* The result. */
671 GdkPixbuf * pixmap = NULL;
672 Atom possible_source = None;
673 int result = -1;
7486d297 674
2ba86315
DB
675 if ((source == None) || (source == a_NET_WM_ICON))
676 {
677 /* Important Notes:
678 * According to freedesktop.org document:
679 * http://standards.freedesktop.org/wm-spec/wm-spec-1.4.html#id2552223
680 * _NET_WM_ICON contains an array of 32-bit packed CARDINAL ARGB.
681 * However, this is incorrect. Actually it's an array of long integers.
682 * Toolkits like gtk+ use unsigned long here to store icons.
683 * Besides, according to manpage of XGetWindowProperty, when returned format,
684 * is 32, the property data will be stored as an array of longs
685 * (which in a 64-bit application will be 64-bit values that are
686 * padded in the upper 4 bytes).
687 */
688
689 /* Get the window property _NET_WM_ICON, if possible. */
690 Atom type = None;
691 int format;
692 gulong nitems;
693 gulong bytes_after;
694 gulong * data = NULL;
695 result = XGetWindowProperty(
696 GDK_DISPLAY(),
697 task_win,
698 a_NET_WM_ICON,
699 0, G_MAXLONG,
700 False, XA_CARDINAL,
701 &type, &format, &nitems, &bytes_after, (void *) &data);
702
703 /* Inspect the result to see if it is usable. If not, and we got data, free it. */
704 if ((type != XA_CARDINAL) || (nitems <= 0))
705 {
706 if (data != NULL)
707 XFree(data);
708 result = -1;
7486d297
DB
709 }
710
2ba86315
DB
711 /* If the result is usable, extract the icon from it. */
712 if (result == Success)
7486d297 713 {
2ba86315
DB
714 /* Get the largest icon available, unless there is one that is the desired size. */
715 /* FIXME: should we try to find an icon whose size is closest to
716 * required_width and required_height to reduce unnecessary resizing? */
717 gulong * pdata = data;
718 gulong * pdata_end = data + nitems;
719 gulong * max_icon = NULL;
720 gulong max_w = 0;
721 gulong max_h = 0;
722 while ((pdata + 2) < pdata_end)
7486d297 723 {
2ba86315
DB
724 /* Extract the width and height. */
725 gulong w = pdata[0];
726 gulong h = pdata[1];
727 gulong size = w * h;
728 pdata += 2;
729
730 /* Bounds check the icon. */
731 if (pdata + size > pdata_end)
732 break;
733
734 /* Rare special case: the desired size is the same as icon size. */
735 if ((required_width == w) && (required_height == h))
736 {
737 max_icon = pdata;
738 max_w = w;
739 max_h = h;
740 break;
741 }
742
743 /* If the icon is the largest so far, capture it. */
744 if ((w > max_w) && (h > max_h))
745 {
746 max_icon = pdata;
747 max_w = w;
748 max_h = h;
749 }
750 pdata += size;
7486d297 751 }
2ba86315
DB
752
753 /* If an icon was extracted, convert it to a pixbuf.
754 * Its size is max_w and max_h. */
755 if (max_icon != NULL)
756 {
757 /* Allocate enough space for the pixel data. */
758 gulong len = max_w * max_h;
759 guchar * pixdata = g_new(guchar, len * 4);
760
761 /* Loop to convert the pixel data. */
762 guchar * p = pixdata;
763 int i;
764 for (i = 0; i < len; p += 4, i += 1)
765 {
766 guint argb = max_icon[i];
767 guint rgba = (argb << 8) | (argb >> 24);
768 p[0] = rgba >> 24;
769 p[1] = (rgba >> 16) & 0xff;
770 p[2] = (rgba >> 8) & 0xff;
771 p[3] = rgba & 0xff;
772 }
7486d297 773
2ba86315
DB
774 /* Initialize a pixmap with the pixel data. */
775 pixmap = gdk_pixbuf_new_from_data(
776 pixdata,
777 GDK_COLORSPACE_RGB,
778 TRUE, 8, /* has_alpha, bits_per_sample */
779 max_w, max_h, max_w * 4,
780 (GdkPixbufDestroyNotify) g_free,
781 NULL);
782 possible_source = a_NET_WM_ICON;
783 }
784 else
785 result = -1;
786
787 /* Free the X property data. */
788 XFree(data);
7486d297 789 }
6cc5e1a6 790 }
7486d297 791
2ba86315
DB
792 /* No icon available from _NET_WM_ICON. Next try WM_HINTS, but do not overwrite _NET_WM_ICON. */
793 if ((result != Success) && (*current_source != a_NET_WM_ICON)
794 && ((source == None) || (source != a_NET_WM_ICON)))
7486d297 795 {
2ba86315 796 XWMHints * hints = XGetWMHints(GDK_DISPLAY(), task_win);
7486d297 797 result = (hints != NULL) ? Success : -1;
2ba86315
DB
798 Pixmap xpixmap = None;
799 Pixmap xmask = None;
7486d297 800
2ba86315 801 if (result == Success)
7486d297 802 {
2ba86315 803 /* WM_HINTS is available. Extract the X pixmap and mask. */
7486d297
DB
804 if ((hints->flags & IconPixmapHint))
805 xpixmap = hints->icon_pixmap;
806 if ((hints->flags & IconMaskHint))
807 xmask = hints->icon_mask;
808 XFree(hints);
2ba86315
DB
809 if (xpixmap != None)
810 {
811 result = Success;
812 possible_source = XA_WM_HINTS;
813 }
814 else
815 result = -1;
7486d297
DB
816 }
817
2ba86315 818 if (result != Success)
7486d297 819 {
2ba86315
DB
820 /* No icon available from _NET_WM_ICON or WM_HINTS. Next try KWM_WIN_ICON. */
821 Atom type = None;
822 int format;
823 gulong nitems;
824 gulong bytes_after;
7486d297 825 Pixmap *icons = NULL;
7486d297 826 Atom kwin_win_icon_atom = gdk_x11_get_xatom_by_name("KWM_WIN_ICON");
2ba86315
DB
827 result = XGetWindowProperty(
828 GDK_DISPLAY(),
829 task_win,
830 kwin_win_icon_atom,
831 0, G_MAXLONG,
832 False, kwin_win_icon_atom,
833 &type, &format, &nitems, &bytes_after, (void *) &icons);
834
835 /* Inspect the result to see if it is usable. If not, and we got data, free it. */
836 if (type != kwin_win_icon_atom)
7486d297 837 {
2ba86315 838 if (icons != NULL)
7486d297 839 XFree(icons);
7486d297
DB
840 result = -1;
841 }
2ba86315
DB
842
843 /* If the result is usable, extract the X pixmap and mask from it. */
844 if (result == Success)
7486d297
DB
845 {
846 xpixmap = icons[0];
2ba86315
DB
847 xmask = icons[1];
848 if (xpixmap != None)
849 {
850 result = Success;
851 possible_source = kwin_win_icon_atom;
852 }
853 else
854 result = -1;
7486d297
DB
855 }
856 }
857
2ba86315
DB
858 /* If we have an X pixmap, get its geometry.*/
859 unsigned int w, h;
860 if (result == Success)
7486d297 861 {
2ba86315
DB
862 Window unused_win;
863 int unused;
864 unsigned int unused_2;
865 result = XGetGeometry(
866 GDK_DISPLAY(), xpixmap,
867 &unused_win, &unused, &unused, &w, &h, &unused_2, &unused_2) ? Success : -1;
7486d297
DB
868 }
869
2ba86315
DB
870 /* If we have an X pixmap and its geometry, convert it to a GDK pixmap. */
871 if (result == Success)
7486d297 872 {
32a67dc7 873 pixmap = _wnck_gdk_pixbuf_get_from_pixmap(xpixmap, w, h);
2ba86315 874 result = ((pixmap != NULL) ? Success : -1);
7486d297
DB
875 }
876
2ba86315
DB
877 /* If we have success, see if the result needs to be masked.
878 * Failures here are implemented as nonfatal. */
879 if ((result == Success) && (xmask != None))
7486d297 880 {
2ba86315
DB
881 Window unused_win;
882 int unused;
883 unsigned int unused_2;
884 if (XGetGeometry(
885 GDK_DISPLAY(), xmask,
886 &unused_win, &unused, &unused, &w, &h, &unused_2, &unused_2))
7486d297 887 {
2ba86315 888 /* Convert the X mask to a GDK pixmap. */
32a67dc7 889 GdkPixbuf * mask = _wnck_gdk_pixbuf_get_from_pixmap(xmask, w, h);
2ba86315 890 if (mask != NULL)
7486d297 891 {
2ba86315
DB
892 /* Apply the mask. */
893 GdkPixbuf * masked_pixmap = apply_mask(pixmap, mask);
894 g_object_unref(G_OBJECT(pixmap));
895 g_object_unref(G_OBJECT(mask));
896 pixmap = masked_pixmap;
7486d297
DB
897 }
898 }
6cc5e1a6
DB
899 }
900 }
7486d297 901
2ba86315
DB
902 /* If we got a pixmap, scale it and return it. */
903 if (pixmap == NULL)
904 return NULL;
905 else
906 {
907 GdkPixbuf * ret = gdk_pixbuf_scale_simple(pixmap, required_width, required_height, GDK_INTERP_TILES);
908 g_object_unref(pixmap);
909 *current_source = possible_source;
910 return ret;
911 }
6cc5e1a6
DB
912}
913
2ba86315
DB
914/* Update the icon of a task. */
915static GdkPixbuf * task_update_icon(TaskbarPlugin * tb, Task * tk, Atom source)
6cc5e1a6 916{
2ba86315
DB
917 /* Get the icon from the window's hints. */
918 GdkPixbuf * pixbuf = get_wm_icon(tk->win, tb->icon_size, tb->icon_size, source, &tk->image_source);
6cc5e1a6 919
2ba86315
DB
920 /* If that fails, and we have no other icon yet, return the fallback icon. */
921 if ((pixbuf == NULL)
922 && ((source == None) || (tk->image_source == None)))
923 {
924 /* Establish the fallback task icon. This is used when no other icon is available. */
925 if (tb->fallback_pixbuf == NULL)
926 tb->fallback_pixbuf = gdk_pixbuf_new_from_xpm_data((const char **) icon_xpm);
927 g_object_ref(tb->fallback_pixbuf);
928 pixbuf = tb->fallback_pixbuf;
6cc5e1a6 929 }
2ba86315
DB
930
931 /* Return what we have. This may be NULL to indicate that no change should be made to the icon. */
932 return pixbuf;
6cc5e1a6
DB
933}
934
2ba86315
DB
935/* Timer expiration for urgency notification. Also used to draw the button in setting and clearing urgency. */
936static gboolean flash_window_timeout(Task * tk)
6cc5e1a6 937{
2ba86315 938 /* Set state on the button and redraw. */
4652f59b 939 if ( ! tk->tb->flat_button)
2ba86315 940 gtk_widget_set_state(tk->button, tk->flash_state ? GTK_STATE_SELECTED : GTK_STATE_NORMAL);
4652f59b 941 task_draw_label(tk);
2ba86315
DB
942
943 /* Complement the flashing context. */
944 tk->flash_state = ! tk->flash_state;
6cc5e1a6
DB
945 return TRUE;
946}
947
2ba86315
DB
948/* Set urgency notification. */
949static void task_set_urgency(Task * tk)
6cc5e1a6 950{
4652f59b
DB
951 TaskbarPlugin * tb = tk->tb;
952 TaskClass * tc = tk->res_class;
953 if ((tb->grouped_tasks) && (tc != NULL) && (tc->visible_count > 1))
954 recompute_group_visibility_for_class(tk->tb, tk->res_class);
955 else
2ba86315
DB
956 {
957 /* Set the flashing context and flash the window immediately. */
958 tk->flash_state = TRUE;
959 flash_window_timeout(tk);
960
961 /* Set the timer if none is set. */
962 if (tk->flash_timeout == 0)
963 set_timer_on_task(tk);
964 }
6cc5e1a6
DB
965}
966
2ba86315
DB
967/* Clear urgency notification. */
968static void task_clear_urgency(Task * tk)
6cc5e1a6 969{
4652f59b
DB
970 TaskbarPlugin * tb = tk->tb;
971 TaskClass * tc = tk->res_class;
972 if ((tb->grouped_tasks) && (tc != NULL) && (tc->visible_count > 1))
973 recompute_group_visibility_for_class(tk->tb, tk->res_class);
974 else
2ba86315
DB
975 {
976 /* Remove the timer if one is set. */
977 if (tk->flash_timeout != 0)
978 {
979 g_source_remove(tk->flash_timeout);
980 tk->flash_timeout = 0;
981 }
982
983 /* Clear the flashing context and unflash the window immediately. */
984 tk->flash_state = FALSE;
985 flash_window_timeout(tk);
986 tk->flash_state = FALSE;
6cc5e1a6
DB
987 }
988}
989
2ba86315
DB
990/* Do the proper steps to raise a window.
991 * This means removing it from iconified state and bringing it to the front.
992 * We also switch the active desktop and viewport if needed. */
993static void task_raise_window(Task * tk, guint32 time)
6cc5e1a6 994{
2ba86315 995 /* Change desktop if needed. */
4652f59b 996 if ((tk->desktop != ALL_WORKSPACES) && (tk->desktop != tk->tb->current_desktop))
6cc5e1a6 997 Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, tk->desktop, 0, 0, 0, 0);
2ba86315 998
32a67dc7
DB
999 /* Evaluate use_net_active if not yet done. */
1000 if ( ! tk->tb->net_active_checked)
1001 {
1002 TaskbarPlugin * tb = tk->tb;
1003 GdkAtom net_active_atom = gdk_x11_xatom_to_atom(a_NET_ACTIVE_WINDOW);
1004 tb->use_net_active = gdk_x11_screen_supports_net_wm_hint(gtk_widget_get_screen(tb->plug->pwid), net_active_atom);
1005 tb->net_active_checked = TRUE;
1006 }
1007
2ba86315
DB
1008 /* Raise the window. We can use NET_ACTIVE_WINDOW if the window manager supports it.
1009 * Otherwise, do it the old way with XMapRaised and XSetInputFocus. */
1010 if (tk->tb->use_net_active)
1011 Xclimsg(tk->win, a_NET_ACTIVE_WINDOW, 2, time, 0, 0, 0);
1012 else
1013 {
1014 GdkWindow * gdkwindow = gdk_xid_table_lookup(tk->win);
1015 if (gdkwindow != NULL)
1016 gdk_window_show(gdkwindow);
1017 else
1018 XMapRaised(GDK_DISPLAY(), tk->win);
32a67dc7
DB
1019
1020 /* There is a race condition between the X server actually executing the XMapRaised and this code executing XSetInputFocus.
1021 * If the window is not viewable, the XSetInputFocus will fail with BadMatch. */
1022 XWindowAttributes attr;
1023 XGetWindowAttributes(GDK_DISPLAY(), tk->win, &attr);
1024 if (attr.map_state == IsViewable)
1025 XSetInputFocus(GDK_DISPLAY(), tk->win, RevertToNone, time);
6cc5e1a6 1026 }
2ba86315
DB
1027
1028 /* Change viewport if needed. */
1029 XWindowAttributes xwa;
1030 XGetWindowAttributes(GDK_DISPLAY(), tk->win, &xwa);
1031 Xclimsg(tk->win, a_NET_DESKTOP_VIEWPORT, xwa.x, xwa.y, 0, 0, 0);
6cc5e1a6
DB
1032}
1033
2ba86315
DB
1034/* Position-calculation callback for grouped-task and window-management popup menu. */
1035static void taskbar_popup_set_position(GtkWidget * menu, gint * px, gint * py, gboolean * push_in, gpointer data)
6cc5e1a6 1036{
2ba86315 1037 Task * tk = (Task *) data;
6cc5e1a6 1038
2ba86315
DB
1039 /* Get the allocation of the popup menu. */
1040 GtkRequisition popup_req;
1041 gtk_widget_size_request(menu, &popup_req);
6cc5e1a6 1042
2ba86315
DB
1043 /* Determine the coordinates. */
1044 plugin_popup_set_position_helper(tk->tb->plug, tk->button, menu, &popup_req, px, py);
1045 *push_in = TRUE;
6cc5e1a6
DB
1046}
1047
2ba86315
DB
1048/* Remove the grouped-task popup menu from the screen. */
1049static void task_group_menu_destroy(TaskbarPlugin * tb)
6cc5e1a6 1050{
2ba86315
DB
1051 if (tb->group_menu != NULL)
1052 {
1053 gtk_widget_destroy(tb->group_menu);
1054 tb->group_menu = NULL;
1055 }
6cc5e1a6
DB
1056}
1057
2ba86315
DB
1058/* Handler for "button-press-event" event from taskbar button,
1059 * or "activate" event from grouped-task popup menu item. */
1060static gboolean taskbar_task_control_event(GtkWidget * widget, GdkEventButton * event, Task * tk, gboolean popup_menu)
6cc5e1a6 1061{
2ba86315
DB
1062 TaskbarPlugin * tb = tk->tb;
1063 TaskClass * tc = tk->res_class;
1064 if ((tb->grouped_tasks) && (tc != NULL) && (tc->visible_count > 1) && (GTK_IS_BUTTON(widget)))
1065 {
1066 /* If this is a grouped-task representative, meaning that there is a class with at least two windows,
1067 * bring up a popup menu listing all the class members. */
1068 GtkWidget * menu = gtk_menu_new();
1069 Task * tk_cursor;
1070 for (tk_cursor = tc->res_class_head; tk_cursor != NULL; tk_cursor = tk_cursor->res_class_flink)
1071 {
1072 if (task_is_visible_on_current_desktop(tb, tk_cursor))
1073 {
1074 /* The menu item has the name, or the iconified name, and the icon of the application window. */
1075 GtkWidget * mi = gtk_image_menu_item_new_with_label(((tk_cursor->iconified) ? tk_cursor->name_iconified : tk_cursor->name));
1076 GtkWidget * im = gtk_image_new_from_pixbuf(gtk_image_get_pixbuf(GTK_IMAGE(tk_cursor->image)));
1077 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), im);
1078 g_signal_connect(mi, "button_press_event", G_CALLBACK(taskbar_popup_activate_event), (gpointer) tk_cursor);
1079 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
1080 }
1081 }
1082
1083 /* Show the menu. Set context so we can find the menu later to dismiss it.
1084 * Use a position-calculation callback to get the menu nicely positioned with respect to the button. */
1085 gtk_widget_show_all(menu);
1086 tb->group_menu = menu;
1087 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, (GtkMenuPositionFunc) taskbar_popup_set_position, (gpointer) tk, event->button, event->time);
1088 }
1089 else
1090 {
1091 /* Not a grouped-task representative, or entered from the grouped-task popup menu. */
1092 Task * visible_task = (((tk->res_class == NULL) || ( ! tk->tb->grouped_tasks)) ? tk : tk->res_class->visible_task);
1093 task_group_menu_destroy(tb);
1094
1095 if (event->button == 1)
1096 {
1097 /* Left button.
1098 * If the task is iconified, raise it.
1099 * If the task is not iconified and has focus, iconify it.
1100 * If the task is not iconified and does not have focus, raise it. */
1101 if (tk->iconified)
1102 task_raise_window(tk, event->time);
1103 else if ((tk->focused) || (tk == tb->focused_previous))
1104 XIconifyWindow(GDK_DISPLAY(), tk->win, DefaultScreen(GDK_DISPLAY()));
1105 else
1106 task_raise_window(tk, event->time);
1107 }
1108 else if (event->button == 2)
1109 {
1110 /* Middle button. Toggle the shaded state of the window. */
1111 Xclimsg(tk->win, a_NET_WM_STATE,
1112 2, /* a_NET_WM_STATE_TOGGLE */
1113 a_NET_WM_STATE_SHADED,
1114 0, 0, 0);
1115 }
1116 else if (event->button == 3)
1117 {
1118 /* Right button. Bring up the window state popup menu. */
1119 tk->tb->menutask = tk;
1120 gtk_menu_popup(
1121 GTK_MENU(tb->menu),
1122 NULL, NULL,
1123 (GtkMenuPositionFunc) taskbar_popup_set_position, (gpointer) visible_task,
1124 event->button, event->time);
1125 }
6cc5e1a6 1126 }
2ba86315
DB
1127
1128 /* As a matter of policy, avoid showing selected or prelight states on flat buttons. */
1129 if (tb->flat_button)
1130 gtk_widget_set_state(widget, GTK_STATE_NORMAL);
6cc5e1a6
DB
1131 return TRUE;
1132}
1133
2ba86315
DB
1134/* Handler for "button-press-event" event from taskbar button. */
1135static gboolean taskbar_button_press_event(GtkWidget * widget, GdkEventButton * event, Task * tk)
6cc5e1a6 1136{
2ba86315 1137 return taskbar_task_control_event(widget, event, tk, FALSE);
6cc5e1a6
DB
1138}
1139
2ba86315
DB
1140/* Handler for "activate" event from grouped-task popup menu item. */
1141static gboolean taskbar_popup_activate_event(GtkWidget * widget, GdkEventButton * event, Task * tk)
6cc5e1a6 1142{
2ba86315 1143 return taskbar_task_control_event(widget, event, tk, TRUE);
6cc5e1a6 1144}
6cc5e1a6 1145
2ba86315
DB
1146/* Handler for "drag-motion" timeout. */
1147static gboolean taskbar_button_drag_motion_timeout(Task * tk)
6cc5e1a6 1148{
2ba86315
DB
1149 guint time = gtk_get_current_event_time();
1150 task_raise_window(tk, ((time != 0) ? time : CurrentTime));
1151 tk->tb->dnd_delay_timer = 0;
1152 return FALSE;
1153}
6cc5e1a6 1154
2ba86315
DB
1155/* Handler for "drag-motion" event from taskbar button. */
1156static gboolean taskbar_button_drag_motion(GtkWidget * widget, GdkDragContext * drag_context, gint x, gint y, guint time, Task * tk)
1157{
1158 /* Prevent excessive motion notification. */
1159 if (tk->tb->dnd_delay_timer == 0)
1160 tk->tb->dnd_delay_timer = g_timeout_add(DRAG_ACTIVE_DELAY, (GSourceFunc) taskbar_button_drag_motion_timeout, tk);
1161 gdk_drag_status(drag_context, 0, time);
1162 return TRUE;
6cc5e1a6
DB
1163}
1164
2ba86315
DB
1165/* Handler for "drag-leave" event from taskbar button. */
1166static void taskbar_button_drag_leave(GtkWidget * widget, GdkDragContext * drag_context, guint time, Task * tk)
6cc5e1a6 1167{
2ba86315
DB
1168 /* Cancel the timer if set. */
1169 if (tk->tb->dnd_delay_timer != 0)
6cc5e1a6 1170 {
2ba86315
DB
1171 g_source_remove(tk->tb->dnd_delay_timer);
1172 tk->tb->dnd_delay_timer = 0;
6cc5e1a6 1173 }
2ba86315 1174 return;
6cc5e1a6
DB
1175}
1176
2ba86315
DB
1177/* Handler for "enter" event from taskbar button. This indicates that the cursor position has entered the button. */
1178static void taskbar_button_enter(GtkWidget * widget, Task * tk)
6cc5e1a6 1179{
2ba86315
DB
1180 tk->entered_state = TRUE;
1181 if (tk->tb->flat_button)
1182 gtk_widget_set_state(widget, GTK_STATE_NORMAL);
1183 task_draw_label(tk);
1184}
6cc5e1a6 1185
2ba86315
DB
1186/* Handler for "leave" event from taskbar button. This indicates that the cursor position has left the button. */
1187static void taskbar_button_leave(GtkWidget * widget, Task * tk)
1188{
1189 tk->entered_state = FALSE;
1190 task_draw_label(tk);
1191}
6cc5e1a6 1192
2ba86315
DB
1193/* Handler for "scroll-event" event from taskbar button. */
1194static gboolean taskbar_button_scroll_event(GtkWidget * widget, GdkEventScroll * event, Task * tk)
1195{
1196 TaskbarPlugin * tb = tk->tb;
1197 TaskClass * tc = tk->res_class;
1198 if ((tb->use_mouse_wheel)
1199 && (( ! tb->grouped_tasks) || (tc == NULL) || (tc->visible_count == 1)))
6cc5e1a6 1200 {
2ba86315
DB
1201 if ((event->direction == GDK_SCROLL_UP) || (event->direction == GDK_SCROLL_LEFT))
1202 task_raise_window(tk, event->time);
6cc5e1a6 1203 else
2ba86315 1204 XIconifyWindow(GDK_DISPLAY(), tk->win, DefaultScreen(GDK_DISPLAY()));
6cc5e1a6 1205 }
2ba86315 1206 return TRUE;
6cc5e1a6
DB
1207}
1208
2ba86315
DB
1209/* Handler for "size-allocate" event from taskbar button. */
1210static void taskbar_button_size_allocate(GtkWidget * btn, GtkAllocation * alloc, Task * tk)
6cc5e1a6 1211{
2ba86315
DB
1212 if (GTK_WIDGET_REALIZED(btn))
1213 {
1214 /* Get the coordinates of the button. */
1215 int x, y;
1216 gdk_window_get_origin(GTK_BUTTON(btn)->event_window, &x, &y);
1217
1218 /* Send a NET_WM_ICON_GEOMETRY property change on the window. */
1219 guint32 data[4];
1220 data[0] = x;
1221 data[1] = y;
1222 data[2] = alloc->width;
1223 data[3] = alloc->height;
1224 XChangeProperty(GDK_DISPLAY(), tk->win,
1225 gdk_x11_get_xatom_by_name("_NET_WM_ICON_GEOMETRY"),
1226 XA_CARDINAL, 32, PropModeReplace, (guchar *) &data, 4);
6cc5e1a6 1227 }
6cc5e1a6
DB
1228}
1229
2ba86315
DB
1230/* Update style on the taskbar when created or after a configuration change. */
1231static void taskbar_update_style(TaskbarPlugin * tb)
6cc5e1a6 1232{
2ba86315
DB
1233 GtkOrientation bo = (tb->plug->panel->orientation == ORIENT_HORIZ) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
1234 icon_grid_set_geometry(tb->icon_grid, bo,
1235 ((tb->icons_only) ? tb->icon_size + ICON_ONLY_EXTRA : tb->task_width_max), tb->icon_size + BUTTON_HEIGHT_EXTRA,
1236 tb->spacing, 0, tb->plug->panel->height);
6cc5e1a6
DB
1237}
1238
2ba86315
DB
1239/* Update style on a task button when created or after a configuration change. */
1240static void task_update_style(Task * tk, TaskbarPlugin * tb)
6cc5e1a6 1241{
2ba86315
DB
1242 if (tb->icons_only)
1243 gtk_widget_hide(tk->label);
1244 else
1245 gtk_widget_show(tk->label);
6cc5e1a6 1246
2ba86315
DB
1247 if( tb->flat_button )
1248 {
1249 gtk_toggle_button_set_active((GtkToggleButton*)tk->button, FALSE);
1250 gtk_button_set_relief(GTK_BUTTON(tk->button), GTK_RELIEF_NONE);
1251 }
1252 else
1253 {
1254 gtk_toggle_button_set_active((GtkToggleButton*)tk->button, tk->focused);
1255 gtk_button_set_relief(GTK_BUTTON(tk->button), GTK_RELIEF_NORMAL);
1256 }
6cc5e1a6 1257
2ba86315 1258 task_draw_label(tk);
7486d297
DB
1259}
1260
2ba86315
DB
1261/* Build graphic elements needed for a task button. */
1262static void task_build_gui(TaskbarPlugin * tb, Task * tk)
6cc5e1a6 1263{
6cc5e1a6
DB
1264 /* NOTE
1265 * 1. the extended mask is sum of taskbar and pager needs
1266 * see bug [ 940441 ] pager loose track of windows
1267 *
2ba86315 1268 * Do not change event mask to gtk windows spawned by this gtk client
6cc5e1a6 1269 * this breaks gtk internals */
2ba86315
DB
1270 if ( ! FBPANEL_WIN(tk->win))
1271 XSelectInput(GDK_DISPLAY(), tk->win, PropertyChangeMask | StructureNotifyMask);
6cc5e1a6 1272
2ba86315 1273 /* Allocate a toggle button as the top level widget. */
6cc5e1a6 1274 tk->button = gtk_toggle_button_new();
6cc5e1a6 1275 gtk_container_set_border_width(GTK_CONTAINER(tk->button), 0);
2ba86315
DB
1276 gtk_drag_dest_set(tk->button, 0, NULL, 0, 0);
1277
1278 /* Connect signals to the button. */
1279 g_signal_connect(tk->button, "button_press_event", G_CALLBACK(taskbar_button_press_event), (gpointer) tk);
1280 g_signal_connect(G_OBJECT(tk->button), "drag-motion", G_CALLBACK(taskbar_button_drag_motion), (gpointer) tk);
1281 g_signal_connect(G_OBJECT(tk->button), "drag-leave", G_CALLBACK(taskbar_button_drag_leave), (gpointer) tk);
1282 g_signal_connect_after(G_OBJECT (tk->button), "enter", G_CALLBACK(taskbar_button_enter), (gpointer) tk);
1283 g_signal_connect_after(G_OBJECT (tk->button), "leave", G_CALLBACK(taskbar_button_leave), (gpointer) tk);
1284 g_signal_connect_after(G_OBJECT(tk->button), "scroll-event", G_CALLBACK(taskbar_button_scroll_event), (gpointer) tk);
1285 g_signal_connect(tk->button, "size-allocate", G_CALLBACK(taskbar_button_size_allocate), (gpointer) tk);
1286
1287 /* Create a box to contain the application icon and window title. */
1288 GtkWidget * container = gtk_hbox_new(FALSE, 1);
1289 gtk_container_set_border_width(GTK_CONTAINER(container), 0);
1290
1291 /* Create an image to contain the application icon and add it to the box. */
1292 GdkPixbuf* pixbuf = task_update_icon(tb, tk, None);
1293 tk->image = gtk_image_new_from_pixbuf(pixbuf);
1294 gtk_misc_set_padding(GTK_MISC(tk->image), 0, 0);
1295 g_object_unref(pixbuf);
6cc5e1a6 1296 gtk_widget_show(tk->image);
2ba86315 1297 gtk_box_pack_start(GTK_BOX(container), tk->image, FALSE, FALSE, 0);
6cc5e1a6 1298
2ba86315
DB
1299 /* Create a label to contain the window title and add it to the box. */
1300 tk->label = gtk_label_new(NULL);
1301 gtk_misc_set_alignment(GTK_MISC(tk->label), 0.0, 0.5);
1302 gtk_label_set_ellipsize(GTK_LABEL(tk->label), PANGO_ELLIPSIZE_END);
1303 gtk_box_pack_start(GTK_BOX(container), tk->label, TRUE, TRUE, 0);
6cc5e1a6 1304
2ba86315
DB
1305 /* Add the box to the button. */
1306 gtk_widget_show(container);
1307 gtk_container_add(GTK_CONTAINER(tk->button), container);
1308 gtk_container_set_border_width(GTK_CONTAINER(tk->button), 0);
6cc5e1a6 1309
2ba86315
DB
1310 /* Add the button to the taskbar. */
1311 icon_grid_add(tb->icon_grid, tk->button, TRUE);
1312 GTK_WIDGET_UNSET_FLAGS(tk->button, GTK_CAN_FOCUS);
1313 GTK_WIDGET_UNSET_FLAGS(tk->button, GTK_CAN_DEFAULT);
1314
1315 /* Update styles on the button. */
1316 task_update_style(tk, tb);
1317
1318 /* Flash button for window with urgency hint. */
1319 if (tk->urgency)
1320 task_set_urgency(tk);
6cc5e1a6
DB
1321}
1322
1323/*****************************************************
1324 * handlers for NET actions *
1325 *****************************************************/
1326
2ba86315
DB
1327/* Handler for "client-list" event from root window listener. */
1328static void taskbar_net_client_list(GtkWidget * widget, TaskbarPlugin * tb)
6cc5e1a6 1329{
2ba86315
DB
1330 /* Get the NET_CLIENT_LIST property. */
1331 int client_count;
1332 Window * client_list = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_CLIENT_LIST, XA_WINDOW, &client_count);
1333 if (client_list != NULL)
1334 {
1335 /* Loop over client list, correlating it with task list. */
1336 int i;
1337 for (i = 0; i < client_count; i++)
1338 {
1339 /* Search for the window in the task list. Set up context to do an insert right away if needed. */
1340 Task * tk_pred = NULL;
1341 Task * tk_cursor;
1342 Task * tk = NULL;
1343 for (tk_cursor = tb->task_list; tk_cursor != NULL; tk_pred = tk_cursor, tk_cursor = tk_cursor->task_flink)
1344 {
1345 if (tk_cursor->win == client_list[i])
1346 {
1347 tk = tk_cursor;
1348 break;
1349 }
1350 if (tk_cursor->win > client_list[i])
1351 break;
6cc5e1a6
DB
1352 }
1353
2ba86315
DB
1354 /* Task is already in task list. */
1355 if (tk != NULL)
1356 tk->present_in_client_list = TRUE;
1357
1358 /* Task is not in task list. */
1359 else
1360 {
1361 /* Evaluate window state and window type to see if it should be in task list. */
1362 NetWMWindowType nwwt;
1363 NetWMState nws;
1364 get_net_wm_state(client_list[i], &nws);
1365 get_net_wm_window_type(client_list[i], &nwwt);
1366 if ((accept_net_wm_state(&nws))
1367 && (accept_net_wm_window_type(&nwwt)))
1368 {
1369 /* Allocate and initialize new task structure. */
1370 tk = g_new0(Task, 1);
1371 tk->present_in_client_list = TRUE;
1372 tk->win = client_list[i];
1373 tk->tb = tb;
1374 tk->name_source = None;
1375 tk->image_source = None;
1376 tk->iconified = (get_wm_state(tk->win) == IconicState);
1377 tk->desktop = get_net_wm_desktop(tk->win);
1378 if (tb->use_urgency_hint)
1379 tk->urgency = task_has_urgency(tk);
1380 task_build_gui(tb, tk);
1381 task_set_names(tk, None);
1382 task_set_class(tk);
1383
1384 /* Link the task structure into the task list. */
1385 if (tk_pred == NULL)
1386 {
1387 tk->task_flink = tb->task_list;
1388 tb->task_list = tk;
1389 }
1390 else
1391 {
1392 tk->task_flink = tk_pred->task_flink;
1393 tk_pred->task_flink = tk;
1394 }
1395 }
1396 }
6cc5e1a6 1397 }
2ba86315 1398 XFree(client_list);
6cc5e1a6
DB
1399 }
1400
2ba86315
DB
1401 /* Remove windows from the task list that are not present in the NET_CLIENT_LIST. */
1402 Task * tk_pred = NULL;
1403 Task * tk = tb->task_list;
1404 while (tk != NULL)
1405 {
1406 Task * tk_succ = tk->task_flink;
1407 if (tk->present_in_client_list)
1408 {
1409 tk->present_in_client_list = FALSE;
1410 tk_pred = tk;
1411 }
1412 else
1413 {
1414 if (tk_pred == NULL)
1415 tb->task_list = tk_succ;
1416 else tk_pred->task_flink = tk_succ;
1417 task_delete(tb, tk, FALSE);
1418 }
1419 tk = tk_succ;
1420 }
1421
1422 /* Redraw the taskbar. */
1423 taskbar_redraw(tb);
6cc5e1a6
DB
1424}
1425
2ba86315
DB
1426/* Handler for "current-desktop" event from root window listener. */
1427static void taskbar_net_current_desktop(GtkWidget * widget, TaskbarPlugin * tb)
6cc5e1a6 1428{
2ba86315
DB
1429 /* Store the local copy of current desktops. Redisplay the taskbar. */
1430 tb->current_desktop = get_net_current_desktop();
1431 recompute_group_visibility_on_current_desktop(tb);
1432 taskbar_redraw(tb);
6cc5e1a6
DB
1433}
1434
2ba86315
DB
1435/* Handler for "number-of-desktops" event from root window listener. */
1436static void taskbar_net_number_of_desktops(GtkWidget * widget, TaskbarPlugin * tb)
6cc5e1a6 1437{
2ba86315
DB
1438 /* Store the local copy of number of desktops. Recompute the popup menu and redisplay the taskbar. */
1439 tb->number_of_desktops = get_net_number_of_desktops();
1440 taskbar_make_menu(tb);
1441 taskbar_redraw(tb);
6cc5e1a6
DB
1442}
1443
2ba86315
DB
1444/* Handler for "active-window" event from root window listener. */
1445static void taskbar_net_active_window(GtkWidget * widget, TaskbarPlugin * tb)
6cc5e1a6 1446{
2ba86315
DB
1447 gboolean drop_old = FALSE;
1448 gboolean make_new = FALSE;
1449 Task * ctk = tb->focused;
1450 Task * ntk = NULL;
1451
1452 /* Get the window that has focus. */
1453 Window * f = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_ACTIVE_WINDOW, XA_WINDOW, 0);
1454 if (f == NULL)
1455 {
1456 /* No window has focus. */
1457 drop_old = TRUE;
1458 tb->focused_previous = NULL;
1459 }
1460 else
1461 {
1462 if (*f == tb->plug->panel->topxwin)
1463 {
1464 /* Taskbar window gained focus (this isn't supposed to be able to happen). Remember current focus. */
1465 if (ctk != NULL)
1466 {
1467 tb->focused_previous = ctk;
1468 drop_old = TRUE;
6cc5e1a6 1469 }
2ba86315
DB
1470 }
1471 else
1472 {
1473 /* Identify task that gained focus. */
1474 tb->focused_previous = NULL;
1475 ntk = task_lookup(tb, *f);
1476 if (ntk != ctk)
1477 {
1478 drop_old = TRUE;
1479 make_new = TRUE;
6cc5e1a6
DB
1480 }
1481 }
1482 XFree(f);
1483 }
2ba86315
DB
1484
1485 /* If our idea of the current task lost focus, update data structures. */
1486 if ((ctk != NULL) && (drop_old))
1487 {
1488 ctk->focused = FALSE;
6cc5e1a6 1489 tb->focused = NULL;
2ba86315
DB
1490 if(!tb->flat_button) /* relieve the button if flat buttons is not used. */
1491 gtk_toggle_button_set_active((GtkToggleButton*)ctk->button, FALSE);
1492
1493 task_button_redraw(ctk, tb);
6cc5e1a6 1494 }
2ba86315
DB
1495
1496 /* If a task gained focus, update data structures. */
1497 if ((ntk != NULL) && (make_new))
1498 {
1499 if(!tb->flat_button) /* depress the button if flat buttons is not used. */
1500 gtk_toggle_button_set_active((GtkToggleButton*)ntk->button, TRUE);
1501 ntk->focused = TRUE;
6cc5e1a6 1502 tb->focused = ntk;
2ba86315 1503 task_button_redraw(ntk, tb);
6cc5e1a6 1504 }
6cc5e1a6
DB
1505}
1506
2ba86315
DB
1507/* Determine if the "urgency" hint is set on a window. */
1508static gboolean task_has_urgency(Task * tk)
6cc5e1a6 1509{
2ba86315
DB
1510 gboolean result = FALSE;
1511 XWMHints * hints = (XWMHints *) get_xaproperty(tk->win, XA_WM_HINTS, XA_WM_HINTS, 0);
1512 if (hints != NULL)
1513 {
1514 if (hints->flags & XUrgencyHint)
1515 result = TRUE;
1516 XFree(hints);
6cc5e1a6 1517 }
2ba86315 1518 return result;
6cc5e1a6
DB
1519}
1520
2ba86315
DB
1521/* Handle PropertyNotify event.
1522 * http://tronche.com/gui/x/icccm/
1523 * http://standards.freedesktop.org/wm-spec/wm-spec-1.4.html */
1524static void taskbar_property_notify_event(TaskbarPlugin *tb, XEvent *ev)
6cc5e1a6 1525{
2ba86315
DB
1526 /* State may be PropertyNewValue, PropertyDeleted. */
1527 if (((XPropertyEvent*) ev)->state == PropertyNewValue)
1528 {
1529 Atom at = ev->xproperty.atom;
1530 Window win = ev->xproperty.window;
1531 if (win != GDK_ROOT_WINDOW())
1532 {
1533 /* Look up task structure by X window handle. */
1534 Task * tk = task_lookup(tb, win);
1535 if (tk != NULL)
1536 {
1537 /* Install an error handler that ignores BadWindow.
1538 * We frequently get a PropertyNotify event on deleted windows. */
1539 XErrorHandler previous_error_handler = XSetErrorHandler(panel_handle_x_error_swallow_BadWindow_BadDrawable);
1540
1541 /* Dispatch on atom. */
1542 if (at == a_NET_WM_DESKTOP)
1543 {
1544 /* Window changed desktop. */
1545 tk->desktop = get_net_wm_desktop(win);
1546 taskbar_redraw(tb);
1547 }
1548 else if ((at == XA_WM_NAME) || (at == a_NET_WM_NAME) || (at == a_NET_WM_VISIBLE_NAME))
1549 {
1550 /* Window changed name. */
1551 task_set_names(tk, at);
1552 if (tk->res_class != NULL)
1553 {
1554 /* A change to the window name may change the visible name of the class. */
1555 recompute_group_visibility_for_class(tb, tk->res_class);
1556 if (tk->res_class->visible_task != NULL)
1557 task_draw_label(tk->res_class->visible_task);
6cc5e1a6
DB
1558 }
1559 }
2ba86315
DB
1560 else if (at == XA_WM_CLASS)
1561 {
1562 /* Window changed class. */
1563 task_set_class(tk);
1564 taskbar_redraw(tb);
1565 }
1566 else if (at == a_WM_STATE)
1567 {
1568 /* Window changed state. */
1569 tk->iconified = (get_wm_state(win) == IconicState);
1570 task_draw_label(tk);
1571 }
1572 else if (at == XA_WM_HINTS)
1573 {
1574 /* Window changed "window manager hints".
1575 * Some windows set their WM_HINTS icon after mapping. */
1576 GdkPixbuf * pixbuf = task_update_icon(tb, tk, XA_WM_HINTS);
1577 if (pixbuf != NULL)
1578 {
1579 gtk_image_set_from_pixbuf(GTK_IMAGE(tk->image), pixbuf);
1580 g_object_unref(pixbuf);
1581 }
6cc5e1a6 1582
2ba86315
DB
1583 if (tb->use_urgency_hint)
1584 {
1585 tk->urgency = task_has_urgency(tk);
1586 if (tk->urgency)
1587 task_set_urgency(tk);
1588 else
1589 task_clear_urgency(tk);
1590 }
1591 }
1592 else if (at == a_NET_WM_STATE)
1593 {
1594 /* Window changed EWMH state. */
1595 NetWMState nws;
1596 get_net_wm_state(tk->win, &nws);
1597 if ( ! accept_net_wm_state(&nws))
1598 {
1599 task_delete(tb, tk, TRUE);
1600 taskbar_redraw(tb);
1601 }
1602 }
1603 else if (at == a_NET_WM_ICON)
1604 {
1605 /* Window changed EWMH icon. */
1606 GdkPixbuf * pixbuf = task_update_icon(tb, tk, a_NET_WM_ICON);
1607 if (pixbuf != NULL)
1608 {
1609 gtk_image_set_from_pixbuf(GTK_IMAGE(tk->image), pixbuf);
1610 g_object_unref(pixbuf);
1611 }
1612 }
1613 else if (at == a_NET_WM_WINDOW_TYPE)
1614 {
1615 /* Window changed EWMH window type. */
1616 NetWMWindowType nwwt;
1617 get_net_wm_window_type(tk->win, &nwwt);
1618 if ( ! accept_net_wm_window_type(&nwwt))
1619 {
1620 task_delete(tb, tk, TRUE);
1621 taskbar_redraw(tb);
1622 }
1623 }
1624 XSetErrorHandler(previous_error_handler);
6cc5e1a6 1625 }
6cc5e1a6
DB
1626 }
1627 }
6cc5e1a6
DB
1628}
1629
2ba86315
DB
1630/* GDK event filter. */
1631static GdkFilterReturn taskbar_event_filter(XEvent * xev, GdkEvent * event, TaskbarPlugin * tb)
6cc5e1a6 1632{
2ba86315
DB
1633 /* Look for PropertyNotify events and update state. */
1634 if (xev->type == PropertyNotify)
1635 taskbar_property_notify_event(tb, xev);
1636 return GDK_FILTER_CONTINUE;
6cc5e1a6
DB
1637}
1638
2ba86315
DB
1639/* Handler for "activate" event on Raise item of right-click menu for task buttons. */
1640static void menu_raise_window(GtkWidget * widget, TaskbarPlugin * tb)
6cc5e1a6 1641{
4652f59b 1642 if ((tb->menutask->desktop != ALL_WORKSPACES) && (tb->menutask->desktop != tb->current_desktop))
2ba86315
DB
1643 Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, tb->menutask->desktop, 0, 0, 0, 0);
1644 XMapRaised(GDK_DISPLAY(), tb->menutask->win);
1645 task_group_menu_destroy(tb);
6cc5e1a6
DB
1646}
1647
2ba86315
DB
1648/* Handler for "activate" event on Restore item of right-click menu for task buttons. */
1649static void menu_restore_window(GtkWidget * widget, TaskbarPlugin * tb)
6cc5e1a6 1650{
2ba86315
DB
1651 GdkWindow * win = gdk_window_foreign_new(tb->menutask->win);
1652 gdk_window_unmaximize(win);
1653 gdk_window_unref(win);
1654 task_group_menu_destroy(tb);
6cc5e1a6
DB
1655}
1656
2ba86315
DB
1657/* Handler for "activate" event on Maximize item of right-click menu for task buttons. */
1658static void menu_maximize_window(GtkWidget * widget, TaskbarPlugin * tb)
6cc5e1a6 1659{
2ba86315
DB
1660 GdkWindow * win = gdk_window_foreign_new(tb->menutask->win);
1661 gdk_window_maximize(win);
1662 gdk_window_unref(win);
1663 task_group_menu_destroy(tb);
6cc5e1a6
DB
1664}
1665
2ba86315
DB
1666/* Handler for "activate" event on Iconify item of right-click menu for task buttons. */
1667static void menu_iconify_window(GtkWidget * widget, TaskbarPlugin * tb)
6cc5e1a6 1668{
2ba86315
DB
1669 XIconifyWindow(GDK_DISPLAY(), tb->menutask->win, DefaultScreen(GDK_DISPLAY()));
1670 task_group_menu_destroy(tb);
6cc5e1a6
DB
1671}
1672
2ba86315
DB
1673/* Handler for "activate" event on Move to Workspace item of right-click menu for task buttons. */
1674static void menu_move_to_workspace(GtkWidget * widget, TaskbarPlugin * tb)
6cc5e1a6 1675{
2ba86315
DB
1676 int num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "num"));
1677 Xclimsg(tb->menutask->win, a_NET_WM_DESKTOP, num, 0, 0, 0, 0);
1678 task_group_menu_destroy(tb);
6cc5e1a6
DB
1679}
1680
2ba86315
DB
1681/* Handler for "activate" event on Close item of right-click menu for task buttons. */
1682static void menu_close_window(GtkWidget * widget, TaskbarPlugin * tb)
6cc5e1a6 1683{
2ba86315
DB
1684 Xclimsgwm(tb->menutask->win, a_WM_PROTOCOLS, a_WM_DELETE_WINDOW);
1685 task_group_menu_destroy(tb);
6cc5e1a6
DB
1686}
1687
2ba86315
DB
1688/* Make right-click menu for task buttons.
1689 * This depends on number of desktops and edge. */
1690static void taskbar_make_menu(TaskbarPlugin * tb)
6cc5e1a6 1691{
2ba86315
DB
1692 /* Deallocate old menu if present. */
1693 if (tb->menu != NULL)
1694 gtk_widget_destroy(tb->menu);
1695
1696 /* Allocate menu. */
1697 GtkWidget * menu = gtk_menu_new();
1698
1699 /* Add Raise menu item. */
1700 GtkWidget *mi = gtk_menu_item_new_with_mnemonic(_("_Raise"));
1701 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
1702 g_signal_connect(G_OBJECT(mi), "activate", (GCallback) menu_raise_window, tb);
1703
1704 /* Add Restore menu item. */
1705 mi = gtk_menu_item_new_with_mnemonic(_("R_estore"));
1706 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
1707 g_signal_connect(G_OBJECT(mi), "activate", (GCallback) menu_restore_window, tb);
1708
1709 /* Add Maximize menu item. */
1710 mi = gtk_menu_item_new_with_mnemonic(_("Ma_ximize"));
1711 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
1712 g_signal_connect(G_OBJECT(mi), "activate", (GCallback) menu_maximize_window, tb);
1713
1714 /* Add Iconify menu item. */
1715 mi = gtk_menu_item_new_with_mnemonic(_("Ico_nify"));
1716 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
1717 g_signal_connect(G_OBJECT(mi), "activate", (GCallback) menu_iconify_window, tb);
1718
1719 /* If multiple desktops are supported, add menu items to select them. */
1720 if (tb->number_of_desktops > 1)
1721 {
1722 char label[128];
6cc5e1a6 1723
2ba86315
DB
1724 /* Allocate submenu. */
1725 GtkWidget * workspace_menu = gtk_menu_new();
6cc5e1a6 1726
2ba86315
DB
1727 /* Loop over all desktops. */
1728 int i;
1729 for (i = 1; i <= tb->number_of_desktops; i++)
6cc5e1a6 1730 {
2ba86315
DB
1731 /* For the first 9 desktops, allow the desktop number as a keyboard shortcut. */
1732 if (i <= 9)
1733 {
1734 g_snprintf(label, sizeof(label), _("Workspace _%d"), i);
1735 mi = gtk_menu_item_new_with_mnemonic(label);
1736 }
1737 else
1738 {
1739 g_snprintf(label, sizeof(label), _("Workspace %d"), i);
1740 mi = gtk_menu_item_new_with_label(label);
1741 }
1742
1743 /* Set the desktop number as a property on the menu item. */
1744 g_object_set_data(G_OBJECT(mi), "num", GINT_TO_POINTER(i - 1));
1745 g_signal_connect(mi, "activate", G_CALLBACK(menu_move_to_workspace), tb);
1746 gtk_menu_shell_append(GTK_MENU_SHELL(workspace_menu), mi);
6cc5e1a6 1747 }
6cc5e1a6 1748
2ba86315
DB
1749 /* Add a separator. */
1750 gtk_menu_shell_append(GTK_MENU_SHELL(workspace_menu), gtk_separator_menu_item_new());
6cc5e1a6 1751
2ba86315
DB
1752 /* Add "move to all workspaces" item. This causes the window to be visible no matter what desktop is active. */
1753 mi = gtk_menu_item_new_with_mnemonic(_("_All workspaces"));
1754 g_object_set_data(G_OBJECT(mi), "num", GINT_TO_POINTER(ALL_WORKSPACES));
1755 g_signal_connect(mi, "activate", G_CALLBACK(menu_move_to_workspace), tb);
1756 gtk_menu_shell_append(GTK_MENU_SHELL(workspace_menu), mi);
6cc5e1a6 1757
2ba86315
DB
1758 /* Add Move to Workspace menu item as a submenu. */
1759 mi = gtk_menu_item_new_with_mnemonic(_("_Move to Workspace"));
1760 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
1761 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), workspace_menu);
6cc5e1a6
DB
1762 }
1763
2ba86315
DB
1764 /* Add Close menu item. By popular demand, we place this menu item closest to the cursor. */
1765 mi = gtk_menu_item_new_with_mnemonic (_("_Close Window"));
1766 if (tb->plug->panel->edge != EDGE_BOTTOM)
6cc5e1a6 1767 {
2ba86315
DB
1768 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
1769 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
6cc5e1a6
DB
1770 }
1771 else
1772 {
2ba86315
DB
1773 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
1774 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
6cc5e1a6
DB
1775 }
1776 g_signal_connect(G_OBJECT(mi), "activate", (GCallback)menu_close_window, tb);
6cc5e1a6 1777
2ba86315
DB
1778 gtk_widget_show_all(menu);
1779 tb->menu = menu;
6cc5e1a6
DB
1780}
1781
32a67dc7
DB
1782/* Handler for "window-manager-changed" event. */
1783static void taskbar_window_manager_changed(GdkScreen * screen, TaskbarPlugin * tb)
aa0e9095 1784{
32a67dc7
DB
1785 /* Force re-evaluation of use_net_active. */
1786 tb->net_active_checked = FALSE;
aa0e9095
DB
1787}
1788
2ba86315
DB
1789/* Build graphic elements needed for the taskbar. */
1790static void taskbar_build_gui(Plugin * p)
6cc5e1a6 1791{
2ba86315
DB
1792 TaskbarPlugin * tb = (TaskbarPlugin *) p->priv;
1793
1794 /* Set up style for taskbar. */
1795 gtk_rc_parse_string(taskbar_rc);
1796
1797 /* Allocate top level widget and set into Plugin widget pointer. */
1798 p->pwid = gtk_event_box_new();
6cc5e1a6 1799 gtk_container_set_border_width(GTK_CONTAINER(p->pwid), 0);
2ba86315
DB
1800 GTK_WIDGET_SET_FLAGS(p->pwid, GTK_NO_WINDOW);
1801 gtk_widget_set_name(p->pwid, "taskbar");
1802
1803 /* Make container for task buttons as a child of top level widget. */
1804 GtkOrientation bo = (tb->plug->panel->orientation == ORIENT_HORIZ) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
1805 tb->icon_grid = icon_grid_new(p->panel, p->pwid, bo, tb->task_width_max, tb->icon_size, tb->spacing, 0, p->panel->height);
1806 icon_grid_set_constrain_width(tb->icon_grid, TRUE);
1807 taskbar_update_style(tb);
1808
1809 /* Add GDK event filter. */
1810 gdk_window_add_filter(NULL, (GdkFilterFunc) taskbar_event_filter, tb);
1811
1812 /* Connect signal to receive mouse events on the unused portion of the taskbar. */
1813 g_signal_connect(p->pwid, "button-press-event", G_CALLBACK(plugin_button_press_event), p);
1814
1815 /* Connect signals to receive root window events and initialize root window properties. */
1816 tb->number_of_desktops = get_net_number_of_desktops();
1817 tb->current_desktop = get_net_current_desktop();
1818 g_signal_connect(G_OBJECT(fbev), "current_desktop", G_CALLBACK(taskbar_net_current_desktop), (gpointer) tb);
1819 g_signal_connect(G_OBJECT(fbev), "active_window", G_CALLBACK(taskbar_net_active_window), (gpointer) tb);
1820 g_signal_connect(G_OBJECT(fbev), "number_of_desktops", G_CALLBACK(taskbar_net_number_of_desktops), (gpointer) tb);
1821 g_signal_connect(G_OBJECT(fbev), "client_list", G_CALLBACK(taskbar_net_client_list), (gpointer) tb);
1822
1823 /* Make right-click menu for task buttons.
1824 * It is retained for the life of the taskbar and will be shown as needed.
1825 * Number of desktops and edge is needed for this operation. */
1826 taskbar_make_menu(tb);
6cc5e1a6 1827
32a67dc7
DB
1828 /* Connect a signal to be notified when the window manager changes. This causes re-evaluation of the "use_net_active" status. */
1829 g_signal_connect(gtk_widget_get_screen(p->pwid), "window-manager-changed", G_CALLBACK(taskbar_window_manager_changed), tb);
6cc5e1a6
DB
1830}
1831
2ba86315
DB
1832/* Plugin constructor. */
1833static int taskbar_constructor(Plugin * p, char ** fp)
6cc5e1a6 1834{
2ba86315
DB
1835 /* Allocate plugin context and set into Plugin private data pointer. */
1836 TaskbarPlugin * tb = g_new0(TaskbarPlugin, 1);
6cc5e1a6
DB
1837 tb->plug = p;
1838 p->priv = tb;
1839
2ba86315
DB
1840 /* Initialize to defaults. */
1841 tb->icon_size = p->panel->icon_size;
1842 tb->tooltips = TRUE;
1843 tb->icons_only = FALSE;
1844 tb->show_all_desks = FALSE;
6cc5e1a6 1845 tb->task_width_max = TASK_WIDTH_MAX;
6cc5e1a6 1846 tb->spacing = 1;
2ba86315
DB
1847 tb->use_mouse_wheel = TRUE;
1848 tb->use_urgency_hint = TRUE;
1849 tb->grouped_tasks = FALSE;
2ba86315
DB
1850
1851 /* Process configuration file. */
1852 line s;
6cc5e1a6
DB
1853 s.len = 256;
1854 if( fp )
1855 {
1856 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
1857 if (s.type == LINE_NONE) {
1858 ERR( "taskbar: illegal token %s\n", s.str);
2ba86315 1859 return 0;
6cc5e1a6 1860 }
2ba86315
DB
1861 if (s.type == LINE_VAR)
1862 {
1863 if (g_ascii_strcasecmp(s.t[0], "tooltips") == 0)
6cc5e1a6 1864 tb->tooltips = str2num(bool_pair, s.t[1], 1);
2ba86315 1865 else if (g_ascii_strcasecmp(s.t[0], "IconsOnly") == 0)
6cc5e1a6 1866 tb->icons_only = str2num(bool_pair, s.t[1], 0);
2ba86315
DB
1867 else if (g_ascii_strcasecmp(s.t[0], "AcceptSkipPager") == 0) /* For backward compatibility */
1868 ;
1869 else if (g_ascii_strcasecmp(s.t[0], "ShowIconified") == 0) /* For backward compatibility */
1870 ;
1871 else if (g_ascii_strcasecmp(s.t[0], "ShowMapped") == 0) /* For backward compatibility */
1872 ;
1873 else if (g_ascii_strcasecmp(s.t[0], "ShowAllDesks") == 0)
6cc5e1a6 1874 tb->show_all_desks = str2num(bool_pair, s.t[1], 0);
2ba86315 1875 else if (g_ascii_strcasecmp(s.t[0], "MaxTaskWidth") == 0)
6cc5e1a6 1876 tb->task_width_max = atoi(s.t[1]);
2ba86315 1877 else if (g_ascii_strcasecmp(s.t[0], "spacing") == 0)
6cc5e1a6 1878 tb->spacing = atoi(s.t[1]);
2ba86315 1879 else if (g_ascii_strcasecmp(s.t[0], "UseMouseWheel") == 0)
6cc5e1a6 1880 tb->use_mouse_wheel = str2num(bool_pair, s.t[1], 1);
2ba86315 1881 else if (g_ascii_strcasecmp(s.t[0], "UseUrgencyHint") == 0)
6cc5e1a6 1882 tb->use_urgency_hint = str2num(bool_pair, s.t[1], 1);
2ba86315 1883 else if (g_ascii_strcasecmp(s.t[0], "FlatButton") == 0)
6cc5e1a6 1884 tb->flat_button = str2num(bool_pair, s.t[1], 1);
2ba86315
DB
1885 else if (g_ascii_strcasecmp(s.t[0], "GroupedTasks") == 0)
1886 tb->grouped_tasks = str2num(bool_pair, s.t[1], 1);
1887 else
6cc5e1a6 1888 ERR( "taskbar: unknown var %s\n", s.t[0]);
2ba86315
DB
1889 }
1890 else
1891 {
6cc5e1a6 1892 ERR( "taskbar: illegal in this context %s\n", s.str);
2ba86315 1893 return 0;
6cc5e1a6
DB
1894 }
1895 }
1896 }
6cc5e1a6 1897
2ba86315
DB
1898 /* Build the graphic elements. */
1899 taskbar_build_gui(p);
6cc5e1a6 1900
2ba86315
DB
1901 /* Fetch the client list and redraw the taskbar. Then determine what window has focus. */
1902 taskbar_net_client_list(NULL, tb);
1903 taskbar_net_active_window(NULL, tb);
1904 return 1;
6cc5e1a6
DB
1905}
1906
2ba86315
DB
1907/* Plugin destructor. */
1908static void taskbar_destructor(Plugin * p)
6cc5e1a6 1909{
2ba86315 1910 TaskbarPlugin * tb = (TaskbarPlugin *) p->priv;
6cc5e1a6 1911
2ba86315
DB
1912 /* Remove GDK event filter. */
1913 gdk_window_remove_filter(NULL, (GdkFilterFunc) taskbar_event_filter, tb);
6cc5e1a6 1914
2ba86315
DB
1915 /* Remove root window signal handlers. */
1916 g_signal_handlers_disconnect_by_func(fbev, taskbar_net_current_desktop, tb);
1917 g_signal_handlers_disconnect_by_func(fbev, taskbar_net_active_window, tb);
1918 g_signal_handlers_disconnect_by_func(fbev, taskbar_net_number_of_desktops, tb);
1919 g_signal_handlers_disconnect_by_func(fbev, taskbar_net_client_list, tb);
7486d297 1920
32a67dc7
DB
1921 /* Remove "window-manager-changed" handler. */
1922 g_signal_handlers_disconnect_by_func(gtk_widget_get_screen(p->pwid), taskbar_window_manager_changed, tb);
aa0e9095 1923
2ba86315
DB
1924 /* Deallocate task list. */
1925 while (tb->task_list != NULL)
1926 task_delete(tb, tb->task_list, TRUE);
1927
1928 /* Deallocate class list. */
1929 while (tb->res_class_list != NULL)
7486d297 1930 {
2ba86315
DB
1931 TaskClass * tc = tb->res_class_list;
1932 tb->res_class_list = tc->res_class_flink;
1933 g_free(tc->res_class);
1934 g_free(tc);
7486d297 1935 }
6cc5e1a6 1936
2ba86315
DB
1937 /* Deallocate other memory. */
1938 gtk_widget_destroy(tb->menu);
1939 g_free(tb);
6cc5e1a6
DB
1940}
1941
2ba86315
DB
1942/* Callback from configuration dialog mechanism to apply the configuration. */
1943static void taskbar_apply_configuration(Plugin * p)
6cc5e1a6 1944{
2ba86315
DB
1945 TaskbarPlugin * tb = (TaskbarPlugin *) p->priv;
1946
1947 /* Update style on taskbar. */
1948 taskbar_update_style(tb);
1949
1950 /* Update styles on each button. */
1951 Task * tk;
1952 for (tk = tb->task_list; tk != NULL; tk = tk->task_flink)
1953 task_update_style(tk, tb);
1954
1955 /* Refetch the client list and redraw. */
1956 recompute_group_visibility_on_current_desktop(tb);
1957 taskbar_net_client_list(NULL, tb);
6cc5e1a6
DB
1958}
1959
2ba86315
DB
1960/* Display the configuration dialog. */
1961static void taskbar_configure(Plugin * p, GtkWindow * parent)
6cc5e1a6 1962{
2ba86315
DB
1963 TaskbarPlugin * tb = (TaskbarPlugin *) p->priv;
1964 GtkWidget* dlg = create_generic_config_dlg(
1965 _(p->class->name),
1966 GTK_WIDGET(parent),
1967 (GSourceFunc) taskbar_apply_configuration, (gpointer) p,
1968 _("Show tooltips"), &tb->tooltips, CONF_TYPE_BOOL,
1969 _("Icons only"), &tb->icons_only, CONF_TYPE_BOOL,
1970 _("Flat buttons"), &tb->flat_button, CONF_TYPE_BOOL,
1971 _("Show windows from all desktops"), &tb->show_all_desks, CONF_TYPE_BOOL,
1972 _("Use mouse wheel"), &tb->use_mouse_wheel, CONF_TYPE_BOOL,
1973 _("Flash when there is any window requiring attention"), &tb->use_urgency_hint, CONF_TYPE_BOOL,
1974 _("Combine multiple application windows into a single button"), &tb->grouped_tasks, CONF_TYPE_BOOL,
1975 _("Maximum width of task button"), &tb->task_width_max, CONF_TYPE_INT,
1976 _("Spacing"), &tb->spacing, CONF_TYPE_INT,
1977 NULL);
1978 gtk_window_present(GTK_WINDOW(dlg));
6cc5e1a6
DB
1979}
1980
2ba86315
DB
1981/* Save the configuration to the configuration file. */
1982static void taskbar_save_configuration(Plugin * p, FILE * fp)
6cc5e1a6 1983{
2ba86315
DB
1984 TaskbarPlugin * tb = (TaskbarPlugin *) p->priv;
1985 lxpanel_put_bool(fp, "tooltips", tb->tooltips);
1986 lxpanel_put_bool(fp, "IconsOnly", tb->icons_only);
1987 lxpanel_put_bool(fp, "ShowAllDesks", tb->show_all_desks);
1988 lxpanel_put_bool(fp, "UseMouseWheel", tb->use_mouse_wheel);
1989 lxpanel_put_bool(fp, "UseUrgencyHint", tb->use_urgency_hint);
1990 lxpanel_put_bool(fp, "FlatButton", tb->flat_button);
1991 lxpanel_put_int(fp, "MaxTaskWidth", tb->task_width_max);
1992 lxpanel_put_int(fp, "spacing", tb->spacing);
1993 lxpanel_put_bool(fp, "GroupedTasks", tb->grouped_tasks);
6cc5e1a6
DB
1994}
1995
2ba86315
DB
1996/* Callback when panel configuration changes. */
1997static void taskbar_panel_configuration_changed(Plugin * p)
6cc5e1a6 1998{
2ba86315
DB
1999 TaskbarPlugin * tb = (TaskbarPlugin *) p->priv;
2000 taskbar_update_style(tb);
2001 taskbar_make_menu(tb);
2002 GtkOrientation bo = (tb->plug->panel->orientation == ORIENT_HORIZ) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
2003 icon_grid_set_geometry(tb->icon_grid, bo,
2004 ((tb->icons_only) ? tb->plug->panel->icon_size + ICON_ONLY_EXTRA : tb->task_width_max), tb->plug->panel->icon_size + BUTTON_HEIGHT_EXTRA,
2005 tb->spacing, 0, tb->plug->panel->height);
2006
2007 /* If the icon size changed, refetch all the icons. */
2008 if (tb->plug->panel->icon_size != tb->icon_size)
2009 {
2010 tb->icon_size = tb->plug->panel->icon_size;
2011 Task * tk;
2012 for (tk = tb->task_list; tk != NULL; tk = tk->task_flink)
2013 {
2014 GdkPixbuf * pixbuf = task_update_icon(tb, tk, None);
2015 if (pixbuf != NULL)
2016 {
2017 gtk_image_set_from_pixbuf(GTK_IMAGE(tk->image), pixbuf);
2018 g_object_unref(pixbuf);
2019 }
6cc5e1a6 2020 }
6cc5e1a6 2021 }
6cc5e1a6 2022
2ba86315
DB
2023 /* Redraw all the labels. Icon size or font color may have changed. */
2024 taskbar_redraw(tb);
6cc5e1a6
DB
2025}
2026
2ba86315 2027/* Plugin descriptor. */
6cc5e1a6 2028PluginClass taskbar_plugin_class = {
2ba86315
DB
2029
2030 PLUGINCLASS_VERSIONING,
6cc5e1a6
DB
2031
2032 type : "taskbar",
2033 name : N_("Task Bar (Window List)"),
2034 version: "1.0",
2035 description : N_("Taskbar shows all opened windows and allow to iconify them, shade or get focus"),
2036
2ba86315
DB
2037 /* Stretch is available and default for this plugin. */
2038 expand_available : TRUE,
2039 expand_default : TRUE,
2040
6cc5e1a6
DB
2041 constructor : taskbar_constructor,
2042 destructor : taskbar_destructor,
2ba86315
DB
2043 config : taskbar_configure,
2044 save : taskbar_save_configuration,
2045 panel_configuration_changed : taskbar_panel_configuration_changed
6cc5e1a6 2046
2ba86315 2047};