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