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