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