Adding upstream version 0.9.0.
[debian/lxpanel.git] / plugins / task-button.c
1 /*
2 * Copyright (C) 2006-2009 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
3 * 2006-2008 Jim Huang <jserv.tw@gmail.com>
4 * 2008 Fred Chien <fred@lxde.org>
5 * 2009 Jürgen Hötzel <juergen@archlinux.org>
6 * 2009 Ying-Chun Liu (PaulLiu) <grandpaul@gmail.com>
7 * 2009-2010 Marty Jack <martyj19@comcast.net>
8 * 2010 Julien Lavergne <julien.lavergne@gmail.com>
9 * 2011-2014 Henry Gebhardt <hsggebhardt@gmail.com>
10 * 2012 Piotr Sipika <Piotr.Sipika@gmail.com>
11 * 2013 Vincenzo di Cicco <enzodicicco@gmail.com>
12 * 2013 Rouslan <rouslan-k@users.sourceforge.net>
13 * 2014-2016 Andriy Grytsenko <andrej@rep.kiev.ua>
14 * 2014 Andy Balaam <axis3x3@users.sf.net>
15 * 2015 Balló György <ballogyor@gmail.com>
16 * 2015 Rafał Mużyło <galtgendo@gmail.com>
17 *
18 * This file is a part of LXPanel project.
19 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software Foundation,
32 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
33 */
34
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
38
39 #include "task-button.h"
40
41 #include <X11/Xlib.h>
42 #include <X11/Xutil.h>
43
44 #include <gdk-pixbuf/gdk-pixbuf.h>
45 #include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
46 #include <gdk/gdk.h>
47 #include <glib/gi18n.h>
48 #include <cairo-xlib.h>
49
50 #include "plugin.h"
51 #include "misc.h"
52 #include "icon.xpm"
53 #include "gtk-compat.h"
54
55 #define ALL_WORKSPACES -1
56
57 static Atom a_NET_WM_STATE_MAXIMIZED_VERT;
58 static Atom a_NET_WM_STATE_MAXIMIZED_HORZ;
59
60 /* -----------------------------------------------------------------------------
61 * Class data
62 */
63
64 /* individual task data */
65 typedef struct
66 {
67 Window win; /* X window ID */
68 gint desktop; /* Desktop that contains task, needed to switch to it on Raise */
69 gint monitor; /* Monitor that the window is on or closest to */
70 char * name; /* Taskbar label when normal, from WM_NAME or NET_WM_NAME */
71 GdkPixbuf * icon; /* the taskbar icon */
72 GtkWidget * menu_item; /* if menu_list exists then it's an item in it */
73 Atom name_source; /* Atom that is the source of taskbar label */
74 Atom image_source; /* Atom that is the source of taskbar icon */
75 unsigned int visible :1; /* TRUE if window is shown in taskbar */
76 unsigned int focused :1; /* True if window has focus */
77 unsigned int iconified :1; /* True if window is iconified, from WM_STATE */
78 unsigned int urgency :1; /* True if window has an urgency hint, from WM_HINTS */
79 } TaskDetails;
80
81 /* widget data */
82 struct _TaskButton
83 {
84 GtkToggleButton parent;
85 char * res_class; /* Class name */
86 GtkWidget * image; /* Icon for task, child of button */
87 GtkWidget * label; /* Label for task, child of button */
88 LXPanel * panel; /* points to panel (grandparent widget) */
89 TaskDetails * last_focused; /* points to details of last focused task */
90 GtkMenu * menu_list; /* list of tasks on menu activation */
91 Window menu_target; /* window which activated menu */
92 guint n_visible; /* number of windows that are shown */
93 guint idle_loader; /* id of icons loader */
94 GList * details; /* details for each window, TaskDetails */
95 gint desktop; /* Current desktop of the button */
96 gint n_desktops; /* total number of desktops */
97 gint monitor; /* current monitor for the panel */
98 guint icon_size; /* Current value from last update */
99 TaskShowFlags flags; /* flags to show */
100 unsigned int set_bold :1; /* flat buttons only: TRUE if set bold */
101 unsigned int visible :1; /* TRUE if any window shown on current desktop */
102 unsigned int same_name :1; /* TRUE if all visible windows have the same name */
103 unsigned int entered_state :1; /* True if cursor is inside taskbar button */
104 };
105
106 enum {
107 MENU_BUILT,
108 MENU_TARGET_SET,
109 N_SIGNALS
110 };
111
112 static guint signals[N_SIGNALS];
113
114
115 static void task_update_icon(TaskButton *task, TaskDetails *details, Atom source);
116
117 /* -----------------------------------------------------------------------------
118 * Internal functions
119 */
120
121 /* Determine which monitor a given window is associated with */
122 static gint get_window_monitor(Window win)
123 {
124 GdkDisplay *display;
125 GdkWindow *gwin;
126 gint m;
127
128 display = gdk_display_get_default();
129 g_assert(display);
130 gwin = gdk_x11_window_foreign_new_for_display(display,win);
131 g_assert(gwin);
132 m = gdk_screen_get_monitor_at_window(gdk_window_get_screen(gwin),gwin);
133 g_object_unref(gwin);
134 return m;
135 }
136
137 /* Determine if the "urgency" hint is set on a window. */
138 static gboolean task_has_urgency(Window win)
139 {
140 gboolean result = FALSE;
141 XWMHints * hints = (XWMHints *) get_xaproperty(win, XA_WM_HINTS, XA_WM_HINTS, 0);
142 if (hints != NULL)
143 {
144 if (hints->flags & XUrgencyHint)
145 result = TRUE;
146 XFree(hints);
147 }
148 //FIXME: also test _NET_WM_STATE_DEMANDS_ATTENTION flag in _NET_WM_STATE
149 return result;
150 }
151
152 /* Returns TRUE if change name affects button name */
153 static gboolean task_set_names(TaskDetails *tk, Atom source)
154 {
155 char * name = NULL;
156
157 /* Try _NET_WM_VISIBLE_NAME, which supports UTF-8.
158 * If it is set, the window manager is displaying it as the window title. */
159 if ((source == None) || (source == a_NET_WM_VISIBLE_NAME))
160 {
161 name = get_utf8_property(tk->win, a_NET_WM_VISIBLE_NAME);
162 if (name != NULL)
163 tk->name_source = a_NET_WM_VISIBLE_NAME;
164 }
165
166 /* Try _NET_WM_NAME, which supports UTF-8, but do not overwrite _NET_WM_VISIBLE_NAME. */
167 if ((name == NULL)
168 && ((source == None) || (source == a_NET_WM_NAME))
169 && ((tk->name_source == None) || (tk->name_source == a_NET_WM_NAME) || (tk->name_source == XA_WM_NAME)))
170 {
171 name = get_utf8_property(tk->win, a_NET_WM_NAME);
172 if (name != NULL)
173 tk->name_source = a_NET_WM_NAME;
174 }
175
176 /* Try WM_NAME, which supports only ISO-8859-1, but do not overwrite _NET_WM_VISIBLE_NAME or _NET_WM_NAME. */
177 if ((name == NULL)
178 && ((source == None) || (source == XA_WM_NAME))
179 && ((tk->name_source == None) || (tk->name_source == XA_WM_NAME)))
180 {
181 name = get_textproperty(tk->win, XA_WM_NAME);
182 if (name != NULL)
183 tk->name_source = XA_WM_NAME;
184 }
185
186 /* Set the name into the task context, and also on the tooltip. */
187 if (name != NULL)
188 {
189 if (g_strcmp0(name, tk->name) != 0)
190 {
191 g_free(tk->name);
192 tk->name = name;
193 return TRUE;
194 }
195 g_free(name);
196 }
197 return FALSE;
198 }
199
200 static gboolean task_is_visible(TaskButton *b, TaskDetails *task)
201 {
202 /* Not on same monitor */
203 if (b->flags.same_monitor_only && b->monitor != task->monitor && b->monitor >= 0)
204 return FALSE;
205
206 /* Desktop placement. */
207 return ((task->desktop == ALL_WORKSPACES) ||
208 (task->desktop == b->desktop) ||
209 (b->flags.show_all_desks) ||
210 (b->flags.use_urgency_hint && task->urgency));
211 }
212
213 static TaskDetails *task_details_for_window(TaskButton *button, Window win)
214 {
215 TaskDetails *details = g_slice_new0(TaskDetails);
216 GdkDisplay *display = gdk_display_get_default();
217 /* NOTE
218 * 1. the extended mask is sum of taskbar and pager needs
219 * see bug [ 940441 ] pager loose track of windows
220 *
221 * Do not change event mask to gtk windows spawned by this gtk client
222 * this breaks gtk internals */
223 #if GTK_CHECK_VERSION(2, 24, 0)
224 if (!gdk_x11_window_lookup_for_display(display, win))
225 #else
226 if (!gdk_window_lookup(win))
227 #endif
228 XSelectInput(GDK_DISPLAY_XDISPLAY(display), win,
229 PropertyChangeMask | StructureNotifyMask);
230
231 /* fetch task details */
232 details->win = win;
233 details->desktop = get_net_wm_desktop(win);
234 details->monitor = get_window_monitor(win);
235 task_set_names(details, None);
236 task_update_icon(button, details, None);
237 details->urgency = task_has_urgency(win);
238 details->iconified = (get_wm_state(win) == IconicState);
239 // FIXME: may want _NET_WM_STATE check
240 // FIXME: check if task is focused
241 /* check task visibility by flags */
242 details->visible = task_is_visible(button, details);
243 return details;
244 }
245
246 static void free_task_details(TaskDetails *details)
247 {
248 g_free(details->name);
249 if (details->icon)
250 g_object_unref(details->icon);
251 g_slice_free(TaskDetails, details);
252 }
253
254 static TaskDetails *task_details_lookup(TaskButton *task, Window win)
255 {
256 GList *l;
257
258 for (l = task->details; l; l = l->next)
259 if (((TaskDetails *)l->data)->win == win)
260 return l->data;
261 return NULL;
262 }
263
264 /* Position-calculation callback for grouped-task and window-management popup menu. */
265 static void taskbar_popup_set_position(GtkMenu * menu, gint * px, gint * py, gboolean * push_in, gpointer data)
266 {
267 TaskButton * tb = (TaskButton *) data;
268
269 /* Determine the coordinates. */
270 lxpanel_plugin_popup_set_position_helper(tb->panel, data, GTK_WIDGET(menu), px, py);
271 *push_in = TRUE;
272 }
273
274 static inline TaskButton *get_menu_task_button(GtkWidget *taskbar)
275 {
276 return g_object_get_data(G_OBJECT(taskbar), "task-button-current");
277 }
278
279 /* Handler for "activate" event on Raise item of right-click menu for task buttons. */
280 static void menu_raise_window(GtkWidget * widget, GtkWidget * taskbar)
281 {
282 TaskButton *tb = get_menu_task_button(taskbar);
283 TaskDetails *tk = task_details_lookup(tb, tb->menu_target);
284 Screen *screen = GDK_SCREEN_XSCREEN(gtk_widget_get_screen(widget));
285
286 if ((tk->desktop != ALL_WORKSPACES) && (tk->desktop != tb->desktop))
287 Xclimsgx(screen, RootWindowOfScreen(screen), a_NET_CURRENT_DESKTOP,
288 tk->desktop, 0, 0, 0, 0);
289 XMapRaised(DisplayOfScreen(screen), tk->win);
290 }
291
292 /* Handler for maximize/unmaximize. Taken from WNCK */
293 static void do_maximize(GtkWidget *widget, Window xwindow, gboolean set)
294 {
295 Xclimsgx(GDK_SCREEN_XSCREEN(gtk_widget_get_screen(widget)), xwindow,
296 a_NET_WM_STATE, set ? a_NET_WM_STATE_ADD : a_NET_WM_STATE_REMOVE,
297 a_NET_WM_STATE_MAXIMIZED_VERT, a_NET_WM_STATE_MAXIMIZED_HORZ,
298 1 /* application */, 0);
299 }
300
301 /* Handler for "activate" event on Restore item of right-click menu for task buttons. */
302 static void menu_restore_window(GtkWidget * widget, GtkWidget * taskbar)
303 {
304 TaskButton *tb = get_menu_task_button(taskbar);
305 do_maximize(GTK_WIDGET(tb), tb->menu_target, FALSE);
306 }
307
308 /* Handler for "activate" event on Maximize item of right-click menu for task buttons. */
309 static void menu_maximize_window(GtkWidget * widget, GtkWidget * taskbar)
310 {
311 TaskButton *tb = get_menu_task_button(taskbar);
312 do_maximize(GTK_WIDGET(tb), tb->menu_target, TRUE);
313 }
314
315 /* Handler for "activate" event on Iconify item of right-click menu for task buttons. */
316 static void menu_iconify_window(GtkWidget * widget, GtkWidget * taskbar)
317 {
318 TaskButton *tb = get_menu_task_button(taskbar);
319 Display *xdisplay = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(widget));
320 XIconifyWindow(xdisplay, tb->menu_target, DefaultScreen(xdisplay));
321 }
322
323 /* Handler for "activate" event on Move to Workspace item of right-click menu for task buttons. */
324 static void menu_move_to_workspace(GtkWidget * widget, GtkWidget * taskbar)
325 {
326 TaskButton *tb = get_menu_task_button(taskbar);
327 int num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "num"));
328 Xclimsgx(GDK_SCREEN_XSCREEN(gtk_widget_get_screen(widget)), tb->menu_target,
329 a_NET_WM_DESKTOP, num, 0, 0, 0, 0);
330 }
331
332 /* Handler for "activate" event on Close item of right-click menu for task buttons. */
333 static void menu_close_window(GtkWidget * widget, GtkWidget * taskbar)
334 {
335 TaskButton *tb = get_menu_task_button(taskbar);
336 Xclimsgwm(tb->menu_target, a_WM_PROTOCOLS, a_WM_DELETE_WINDOW);
337 }
338
339 /* Make right-click menu for task buttons.
340 * This depends on number of desktops and edge. */
341 static GtkWidget *taskbar_make_menu(TaskButton *tb, GtkWidget *parent)
342 {
343 /* Function to iterate in direction */
344 void (*_m_add)(GtkMenuShell *self, GtkWidget* child);
345 /* Allocate menu. */
346 GtkWidget *menu = gtk_menu_new();
347 GtkWidget *mi;
348
349 /* Add Raise menu item. */
350 mi = gtk_menu_item_new_with_mnemonic(_("_Raise"));
351 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
352 g_signal_connect(G_OBJECT(mi), "activate", (GCallback) menu_raise_window, parent);
353
354 /* Add Restore menu item. */
355 mi = gtk_menu_item_new_with_mnemonic(_("R_estore"));
356 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
357 g_signal_connect(G_OBJECT(mi), "activate", (GCallback) menu_restore_window, parent);
358
359 /* Add Maximize menu item. */
360 mi = gtk_menu_item_new_with_mnemonic(_("Ma_ximize"));
361 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
362 g_signal_connect(G_OBJECT(mi), "activate", (GCallback) menu_maximize_window, parent);
363
364 /* Add Iconify menu item. */
365 mi = gtk_menu_item_new_with_mnemonic(_("Ico_nify"));
366 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
367 g_signal_connect(G_OBJECT(mi), "activate", (GCallback) menu_iconify_window, parent);
368
369 /* FIXME: if WM is Openbox then add "Window special parameters" submenu */
370
371 /* If multiple desktops are supported, add menu items to select them. */
372 if (tb->n_desktops > 1)
373 {
374 char label[128];
375 /* Allocate submenu. */
376 GtkWidget * workspace_menu = gtk_menu_new();
377 GtkWidget * workspace_menu0 = NULL;
378
379 /* Loop over all desktops. */
380 int i;
381 for (i = 1; i <= tb->n_desktops; i++)
382 {
383 /* For the first 9 desktops, allow the desktop number as a keyboard shortcut. */
384 if (i <= 9)
385 {
386 g_snprintf(label, sizeof(label), _("Workspace _%d"), i);
387 mi = gtk_menu_item_new_with_mnemonic(label);
388 }
389 else
390 {
391 g_snprintf(label, sizeof(label), _("Workspace %d"), i);
392 mi = gtk_menu_item_new_with_label(label);
393 }
394
395 /* Set the desktop number as a property on the menu item. */
396 g_object_set_data(G_OBJECT(mi), "num", GINT_TO_POINTER(i - 1));
397 g_signal_connect(mi, "activate", G_CALLBACK(menu_move_to_workspace), parent);
398 gtk_menu_shell_append(GTK_MENU_SHELL(workspace_menu), mi);
399 if (G_UNLIKELY(workspace_menu0 == NULL))
400 workspace_menu0 = mi;
401 }
402 g_object_set_data(G_OBJECT(menu), "task-menu-workspace0", workspace_menu0);
403
404 /* Add a separator. */
405 gtk_menu_shell_append(GTK_MENU_SHELL(workspace_menu), gtk_separator_menu_item_new());
406
407 /* Add "move to all workspaces" item. This causes the window to be visible no matter what desktop is active. */
408 mi = gtk_menu_item_new_with_mnemonic(_("_All workspaces"));
409 g_object_set_data(G_OBJECT(mi), "num", GINT_TO_POINTER(ALL_WORKSPACES));
410 g_signal_connect(mi, "activate", G_CALLBACK(menu_move_to_workspace), parent);
411 gtk_menu_shell_append(GTK_MENU_SHELL(workspace_menu), mi);
412
413 /* FIXME: add "Current workspace" item, active if not on a current */
414
415 /* Add Move to Workspace menu item as a submenu. */
416 mi = gtk_menu_item_new_with_mnemonic(_("_Move to Workspace"));
417 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
418 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), workspace_menu);
419 }
420
421 /* Extend the menu by callbacks */
422 g_signal_emit(tb, signals[MENU_BUILT], 0, menu);
423
424 /* Add Close menu item. By popular demand, we place this menu item closest to the cursor. */
425 if (panel_is_at_bottom(tb->panel))
426 _m_add = gtk_menu_shell_append;
427 else
428 _m_add = gtk_menu_shell_prepend;
429
430 _m_add(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
431 mi = gtk_menu_item_new_with_mnemonic (_("_Close Window"));
432 _m_add(GTK_MENU_SHELL(menu), mi);
433 g_signal_connect(G_OBJECT(mi), "activate", (GCallback)menu_close_window, parent);
434
435 return menu;
436 }
437
438 static GtkWidget *get_task_button_menu(TaskButton *tb, TaskDetails *task)
439 {
440 GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(tb));
441 GtkWidget *menu = g_object_get_data(G_OBJECT(parent), "task-button-menu");
442 GtkWidget *workspace_menu0; /* item in task menu for workspace 0 */
443
444 if (menu == NULL)
445 {
446 /* this GtkMenu is built on demand on the parent widget */
447 menu = taskbar_make_menu(tb, parent);
448 gtk_widget_show_all(menu);
449 g_object_set_data_full(G_OBJECT(parent), "task-button-menu",
450 g_object_ref_sink(menu), g_object_unref);
451 }
452 g_object_set_data(G_OBJECT(parent), "task-button-current", tb);
453 /* save current choice for our callbacks */
454 tb->menu_target = task->win;
455 /* notify menu makers about current choise */
456 g_signal_emit(tb, signals[MENU_TARGET_SET], 0, (gulong)task->win);
457 /* gray out workspace where window is on */
458 workspace_menu0 = g_object_get_data(G_OBJECT(menu), "task-menu-workspace0");
459 if (workspace_menu0)
460 {
461 GList *items = gtk_container_get_children(GTK_CONTAINER(gtk_widget_get_parent(workspace_menu0)));
462 GList *item = g_list_find(items, workspace_menu0);
463 int i;
464 if (item != NULL) /* else error */
465 for (i = 0; i < tb->n_desktops; i++, item = item->next)
466 gtk_widget_set_sensitive(item->data, i != task->desktop);
467 g_list_free(items);
468 }
469 //FIXME: do the same for 'All workspaces' item
470
471 return menu;
472 }
473
474 /* Do the proper steps to raise a window.
475 * This means removing it from iconified state and bringing it to the front.
476 * We also switch the active desktop and viewport if needed. */
477 static void task_raise_window(TaskButton *tb, TaskDetails *tk, guint32 time)
478 {
479 #if GTK_CHECK_VERSION(2, 24, 0)
480 GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(tb));
481 #endif
482 Screen *xscreen = GDK_SCREEN_XSCREEN(gtk_widget_get_screen(GTK_WIDGET(tb)));
483 Display *xdisplay = DisplayOfScreen(xscreen);
484
485 /* Change desktop if needed. */
486 if ((tk->desktop != ALL_WORKSPACES) && (tk->desktop != tb->desktop))
487 Xclimsgx(xscreen, RootWindowOfScreen(xscreen), a_NET_CURRENT_DESKTOP, tk->desktop, 0, 0, 0, 0);
488
489 /* Raise the window. We can use NET_ACTIVE_WINDOW if the window manager supports it.
490 * Otherwise, do it the old way with XMapRaised and XSetInputFocus. */
491 if (tb->flags.use_net_active)
492 Xclimsgx(xscreen, tk->win, a_NET_ACTIVE_WINDOW, 2, time, 0, 0, 0);
493 else
494 {
495 #if GTK_CHECK_VERSION(2, 24, 0)
496 GdkWindow * gdkwindow = gdk_x11_window_lookup_for_display(display, tk->win);
497 #else
498 GdkWindow * gdkwindow = gdk_xid_table_lookup(tk->win);
499 #endif
500 if (gdkwindow != NULL)
501 gdk_window_show(gdkwindow);
502 else
503 XMapRaised(xdisplay, tk->win);
504
505 /* There is a race condition between the X server actually executing the XMapRaised and this code executing XSetInputFocus.
506 * If the window is not viewable, the XSetInputFocus will fail with BadMatch. */
507 XWindowAttributes attr;
508 XGetWindowAttributes(xdisplay, tk->win, &attr);
509 if (attr.map_state == IsViewable)
510 XSetInputFocus(xdisplay, tk->win, RevertToNone, time);
511 }
512
513 /* Change viewport if needed. */
514 XWindowAttributes xwa;
515 XGetWindowAttributes(xdisplay, tk->win, &xwa);
516 Xclimsgx(xscreen, tk->win, a_NET_DESKTOP_VIEWPORT, xwa.x, xwa.y, 0, 0, 0);
517 }
518
519 /* called when list of windows menu emits signal "selection-done" */
520 static void on_menu_list_selection_done(GtkMenuShell *menushell, TaskButton *tb)
521 {
522 g_object_remove_weak_pointer(G_OBJECT(menushell), (void **)&tb->menu_list);
523 tb->menu_list = NULL;
524 }
525
526 static gboolean task_button_window_do_release_event(GtkWidget *tb, TaskDetails *task, GdkEventButton *event)
527 {
528 if (event->button == 1)
529 {
530 Display *xdisplay = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(tb));
531 /* Left button.
532 * If the task is iconified, raise it.
533 * If the task is not iconified and has focus, iconify it.
534 * If the task is not iconified and does not have focus, raise it. */
535 if (task->iconified)
536 task_raise_window(PANEL_TASK_BUTTON(tb), task, event->time);
537 else if (task->focused)
538 XIconifyWindow(xdisplay, task->win, DefaultScreen(xdisplay));
539 else
540 task_raise_window(PANEL_TASK_BUTTON(tb), task, event->time);
541 }
542 else if (event->button == 2)
543 {
544 /* Middle button. Toggle the shaded state of the window. */
545 Xclimsgx(GDK_SCREEN_XSCREEN(gtk_widget_get_screen(tb)),
546 task->win, a_NET_WM_STATE,
547 a_NET_WM_STATE_TOGGLE,
548 a_NET_WM_STATE_SHADED,
549 0, 0, 0);
550 }
551 return TRUE;
552 }
553
554 /* Handler for "button-press-event" event from grouped-task popup menu item. */
555 static gboolean taskbar_popup_activate_event(GtkWidget *widget, GdkEventButton *event,
556 TaskButton *tk)
557 {
558 GtkWidget *menu;
559 GList *l;
560
561 /* find details of this menu item and set tk->menu_target */
562 for (l = tk->details; l; l = l->next)
563 if (((TaskDetails *)l->data)->menu_item == widget)
564 break;
565 if (l == NULL) /* it's impossible really */
566 return FALSE;
567 /* if button 1 or 2 pressed then handle it the same as button-release
568 event on a single task button */
569 if (event->button == 1 || event->button == 2)
570 return task_button_window_do_release_event(GTK_WIDGET(tk), l->data, event);
571 else if (event->button != 3) /* don't process other buttons */
572 return FALSE;
573 /* process event the same way as for single task button */
574 menu = get_task_button_menu(tk, l->data);
575 /* attach and show menu */
576 gtk_menu_item_set_submenu(GTK_MENU_ITEM(widget), menu);
577 /* let menu continue with submenu */
578 return FALSE;
579 }
580
581 static void menu_task_selected(GtkMenuItem *item, TaskButton *tb)
582 {
583 GList *l;
584 TaskDetails *task;
585
586 for (l = tb->details; l; l = l->next)
587 if ((task = l->data)->menu_item == (GtkWidget *)item)
588 break;
589 if (l == NULL) /* it's impossible really */
590 return;
591 tb->menu_target = task->win;
592 // FIXME: auto attach menu?
593 }
594
595 static void menu_task_deselected(GtkMenuItem *item, TaskButton *tb)
596 {
597 GList *l;
598 TaskDetails *task;
599
600 for (l = tb->details; l; l = l->next)
601 if ((task = l->data)->menu_item == (GtkWidget *)item)
602 break;
603 if (l == NULL) /* it's impossible really */
604 return;
605 /* remove submenu from item */
606 gtk_menu_item_set_submenu(item, NULL);
607 }
608
609 /* Handler for "activate" event from "close all windows" menu item */
610 static void taskbar_close_all_windows(GtkWidget * widget, TaskButton *tb)
611 {
612 GList *l;
613
614 for (l = tb->details; l; l = l->next)
615 {
616 TaskDetails *tk = l->data;
617
618 if (tk->visible)
619 {
620 Xclimsgwm(tk->win, a_WM_PROTOCOLS, a_WM_DELETE_WINDOW);
621 }
622 }
623 }
624
625 static void assemble_gui(TaskButton *self)
626 {
627 /* Create a box to contain the application icon and window title. */
628 GtkWidget * container = gtk_hbox_new(FALSE, 1);
629 gtk_container_set_border_width(GTK_CONTAINER(container), 0);
630
631 /* Add the image to contain the application icon to the box. */
632 gtk_misc_set_padding(GTK_MISC(self->image), 0, 0);
633 gtk_box_pack_start(GTK_BOX(container), self->image, FALSE, FALSE, 0);
634
635 /* Add the label to contain the window title to the box. */
636 gtk_misc_set_alignment(GTK_MISC(self->label), 0.0, 0.5);
637 gtk_label_set_ellipsize(GTK_LABEL(self->label), PANGO_ELLIPSIZE_END);
638 gtk_box_pack_start(GTK_BOX(container), self->label, TRUE, TRUE, 0);
639
640 /* Add the box to the button. */
641 gtk_container_add(GTK_CONTAINER(self), container);
642 gtk_widget_show(container);
643 gtk_widget_show(self->image);
644 gtk_widget_set_visible(self->label, !self->flags.icons_only);
645 }
646
647 static void map_xwindow_animation(GtkWidget *widget, Window win, GtkAllocation *alloc)
648 {
649 /* Tell WM to set iconifying animation the window into the task button */
650 if (gtk_widget_get_realized(widget))
651 {
652 int x, y;
653 gulong data[4];
654
655 /* Get the coordinates of the button. */
656 gdk_window_get_origin(gtk_button_get_event_window(GTK_BUTTON(widget)), &x, &y);
657
658 /* Send a NET_WM_ICON_GEOMETRY property change on the window. */
659 data[0] = x;
660 data[1] = y;
661 data[2] = alloc->width;
662 data[3] = alloc->height;
663 XChangeProperty(GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(widget)), win,
664 gdk_x11_get_xatom_by_name("_NET_WM_ICON_GEOMETRY"),
665 XA_CARDINAL, 32, PropModeReplace, (guchar *) &data, 4);
666 }
667 }
668
669 /* Get a pixbuf from a pixmap.
670 * Originally from libwnck, Copyright (C) 2001 Havoc Pennington. */
671 #if !GTK_CHECK_VERSION(3, 0, 0)
672 static GdkPixbuf * _wnck_gdk_pixbuf_get_from_pixmap(GdkScreen *screen, Pixmap xpixmap, Window win, int width, int height)
673 {
674 /* Get the drawable. */
675 #if GTK_CHECK_VERSION(2, 24, 0)
676 GdkDrawable * drawable = gdk_x11_window_lookup_for_display(gdk_display_get_default(), xpixmap);
677 #else
678 GdkDrawable * drawable = gdk_xid_table_lookup(xpixmap);
679 #endif
680 if (drawable != NULL)
681 g_object_ref(G_OBJECT(drawable));
682 else
683 drawable = gdk_pixmap_foreign_new(xpixmap);
684
685 GdkColormap * colormap = NULL;
686 GdkPixbuf * retval = NULL;
687 if (drawable != NULL)
688 {
689 /* Get the colormap.
690 * If the drawable has no colormap, use no colormap or the system colormap as recommended in the documentation of gdk_drawable_get_colormap. */
691 colormap = gdk_drawable_get_colormap(drawable);
692 gint depth = gdk_drawable_get_depth(drawable);
693 if (colormap != NULL)
694 g_object_ref(G_OBJECT(colormap));
695 else if (depth == 1)
696 colormap = NULL;
697 else
698 {
699 colormap = gdk_screen_get_system_colormap(screen);
700 g_object_ref(G_OBJECT(colormap));
701 }
702
703 /* Be sure we aren't going to fail due to visual mismatch. */
704 if ((colormap != NULL) && (gdk_visual_get_depth(gdk_colormap_get_visual(colormap)) != depth))
705 {
706 g_object_unref(G_OBJECT(colormap));
707 colormap = NULL;
708 }
709
710 /* Do the major work. */
711 retval = gdk_pixbuf_get_from_drawable(NULL, drawable, colormap, 0, 0, 0, 0, width, height);
712 }
713
714 /* Clean up and return. */
715 if (colormap != NULL)
716 g_object_unref(G_OBJECT(colormap));
717 if (drawable != NULL)
718 g_object_unref(G_OBJECT(drawable));
719 return retval;
720 }
721 #else
722 static GdkPixbuf * _wnck_gdk_pixbuf_get_from_pixmap(GdkScreen *screen, Pixmap xpixmap, Window win, int width, int height)
723 {
724 cairo_surface_t *surface;
725 GdkPixbuf *pixbuf = NULL;
726 Display *xdisplay;
727 XWindowAttributes attrs;
728
729 surface = NULL;
730 xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
731
732 gdk_error_trap_push();
733
734 if (!XGetWindowAttributes (xdisplay, win, &attrs))
735 goto TRAP_POP;
736
737 if (attrs.depth == 1)
738 {
739 surface = cairo_xlib_surface_create_for_bitmap (xdisplay,
740 xpixmap,
741 attrs.screen,
742 width,
743 height);
744 }
745 else
746 {
747 surface = cairo_xlib_surface_create (xdisplay,
748 xpixmap,
749 attrs.visual,
750 width, height);
751 }
752
753 pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, width, height);
754 cairo_surface_destroy (surface);
755
756 TRAP_POP:
757 gdk_flush();
758 if (gdk_error_trap_pop())
759 g_warning("task button : X error");
760
761 return pixbuf;
762 }
763 #endif
764
765 /* Apply a mask to a pixbuf.
766 * Originally from libwnck, Copyright (C) 2001 Havoc Pennington. */
767 static GdkPixbuf * apply_mask(GdkPixbuf * pixbuf, GdkPixbuf * mask)
768 {
769 /* Initialize. */
770 int w = MIN(gdk_pixbuf_get_width(mask), gdk_pixbuf_get_width(pixbuf));
771 int h = MIN(gdk_pixbuf_get_height(mask), gdk_pixbuf_get_height(pixbuf));
772 GdkPixbuf * with_alpha = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
773 guchar * dst = gdk_pixbuf_get_pixels(with_alpha);
774 guchar * src = gdk_pixbuf_get_pixels(mask);
775 int dst_stride = gdk_pixbuf_get_rowstride(with_alpha);
776 int src_stride = gdk_pixbuf_get_rowstride(mask);
777
778 /* Loop to do the work. */
779 int i;
780 for (i = 0; i < h; i += 1)
781 {
782 int j;
783 for (j = 0; j < w; j += 1)
784 {
785 guchar * s = src + i * src_stride + j * 3;
786 guchar * d = dst + i * dst_stride + j * 4;
787
788 /* s[0] == s[1] == s[2], they are 255 if the bit was set, 0 otherwise. */
789 d[3] = ((s[0] == 0) ? 0 : 255); /* 0 = transparent, 255 = opaque */
790 }
791 }
792
793 return with_alpha;
794 }
795
796 /* Get an icon from the window manager for a task, and scale it to a specified size. */
797 static GdkPixbuf * get_wm_icon(Window task_win, guint required_width,
798 guint required_height, Atom source,
799 Atom * current_source, TaskButton * tb)
800 {
801 /* The result. */
802 GdkPixbuf * pixmap = NULL;
803 Atom possible_source = None;
804 int result = -1;
805 Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
806 GdkScreen *screen = gtk_widget_get_screen(GTK_WIDGET(tb));
807
808 if ((source == None) || (source == a_NET_WM_ICON))
809 {
810 /* Important Notes:
811 * According to freedesktop.org document:
812 * http://standards.freedesktop.org/wm-spec/wm-spec-1.4.html#id2552223
813 * _NET_WM_ICON contains an array of 32-bit packed CARDINAL ARGB.
814 * However, this is incorrect. Actually it's an array of long integers.
815 * Toolkits like gtk+ use unsigned long here to store icons.
816 * Besides, according to manpage of XGetWindowProperty, when returned format,
817 * is 32, the property data will be stored as an array of longs
818 * (which in a 64-bit application will be 64-bit values that are
819 * padded in the upper 4 bytes).
820 */
821
822 /* Get the window property _NET_WM_ICON, if possible. */
823 Atom type = None;
824 int format;
825 gulong nitems;
826 gulong bytes_after;
827 gulong * data = NULL;
828 result = XGetWindowProperty(
829 xdisplay,
830 task_win,
831 a_NET_WM_ICON,
832 0, G_MAXLONG,
833 False, XA_CARDINAL,
834 &type, &format, &nitems, &bytes_after, (void *) &data);
835
836 /* Inspect the result to see if it is usable. If not, and we got data, free it. */
837 if ((type != XA_CARDINAL) || (nitems <= 0))
838 {
839 if (data != NULL)
840 XFree(data);
841 result = -1;
842 }
843
844 /* If the result is usable, extract the icon from it. */
845 if (result == Success)
846 {
847 /* Get the largest icon available, unless there is one that is the desired size. */
848 /* FIXME: should we try to find an icon whose size is closest to
849 * required_width and required_height to reduce unnecessary resizing? */
850 gulong * pdata = data;
851 gulong * pdata_end = data + nitems;
852 gulong * max_icon = NULL;
853 gulong max_w = 0;
854 gulong max_h = 0;
855 while ((pdata + 2) < pdata_end)
856 {
857 /* Extract the width and height. */
858 guint w = pdata[0];
859 guint h = pdata[1];
860 gulong size = w * h;
861 pdata += 2;
862
863 /* Bounds check the icon. Also check for invalid width and height,
864 see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=801319 */
865 if (size == 0 || w > 1024 || h > 1024 || pdata + size > pdata_end)
866 break;
867
868 /* Rare special case: the desired size is the same as icon size. */
869 if ((required_width == w) && (required_height == h))
870 {
871 max_icon = pdata;
872 max_w = w;
873 max_h = h;
874 break;
875 }
876
877 /* If the icon is the largest so far, capture it. */
878 if ((w > max_w) && (h > max_h))
879 {
880 max_icon = pdata;
881 max_w = w;
882 max_h = h;
883 }
884 pdata += size;
885 }
886
887 /* If an icon was extracted, convert it to a pixbuf.
888 * Its size is max_w and max_h. */
889 if (max_icon != NULL)
890 {
891 /* Allocate enough space for the pixel data. */
892 gulong len = max_w * max_h;
893 guchar * pixdata = g_new(guchar, len * 4);
894
895 /* Loop to convert the pixel data. */
896 guchar * p = pixdata;
897 gulong i;
898 for (i = 0; i < len; p += 4, i += 1)
899 {
900 guint argb = max_icon[i];
901 guint rgba = (argb << 8) | (argb >> 24);
902 p[0] = rgba >> 24;
903 p[1] = (rgba >> 16) & 0xff;
904 p[2] = (rgba >> 8) & 0xff;
905 p[3] = rgba & 0xff;
906 }
907
908 /* Initialize a pixmap with the pixel data. */
909 pixmap = gdk_pixbuf_new_from_data(
910 pixdata,
911 GDK_COLORSPACE_RGB,
912 TRUE, 8, /* has_alpha, bits_per_sample */
913 max_w, max_h, max_w * 4,
914 (GdkPixbufDestroyNotify) g_free,
915 NULL);
916 possible_source = a_NET_WM_ICON;
917 }
918 else
919 result = -1;
920
921 /* Free the X property data. */
922 XFree(data);
923 }
924 }
925
926 /* No icon available from _NET_WM_ICON. Next try WM_HINTS, but do not overwrite _NET_WM_ICON. */
927 if ((result != Success) && (*current_source != a_NET_WM_ICON)
928 && ((source == None) || (source != a_NET_WM_ICON)))
929 {
930 XWMHints * hints = XGetWMHints(xdisplay, task_win);
931 result = (hints != NULL) ? Success : -1;
932 Pixmap xpixmap = None;
933 Pixmap xmask = None;
934 Window win = None;
935
936 if (result == Success)
937 {
938 /* WM_HINTS is available. Extract the X pixmap and mask. */
939 if ((hints->flags & IconPixmapHint))
940 xpixmap = hints->icon_pixmap;
941 if ((hints->flags & IconMaskHint))
942 xmask = hints->icon_mask;
943 XFree(hints);
944 if (xpixmap != None)
945 {
946 result = Success;
947 possible_source = XA_WM_HINTS;
948 }
949 else
950 result = -1;
951 }
952
953 if (result != Success)
954 {
955 /* No icon available from _NET_WM_ICON or WM_HINTS. Next try KWM_WIN_ICON. */
956 Atom type = None;
957 int format;
958 gulong nitems;
959 gulong bytes_after;
960 Pixmap *icons = NULL;
961 Atom kwin_win_icon_atom = gdk_x11_get_xatom_by_name("KWM_WIN_ICON");
962 result = XGetWindowProperty(
963 xdisplay,
964 task_win,
965 kwin_win_icon_atom,
966 0, G_MAXLONG,
967 False, kwin_win_icon_atom,
968 &type, &format, &nitems, &bytes_after, (void *) &icons);
969
970 /* Inspect the result to see if it is usable. If not, and we got data, free it. */
971 if (type != kwin_win_icon_atom)
972 {
973 if (icons != NULL)
974 XFree(icons);
975 result = -1;
976 }
977
978 /* If the result is usable, extract the X pixmap and mask from it. */
979 if (result == Success)
980 {
981 xpixmap = icons[0];
982 xmask = icons[1];
983 if (xpixmap != None)
984 {
985 result = Success;
986 possible_source = kwin_win_icon_atom;
987 }
988 else
989 result = -1;
990 }
991 }
992
993 /* If we have an X pixmap, get its geometry.*/
994 unsigned int w, h;
995 if (result == Success)
996 {
997 int unused;
998 unsigned int unused_2;
999 result = XGetGeometry(
1000 xdisplay, xpixmap,
1001 &win, &unused, &unused, &w, &h, &unused_2, &unused_2) ? Success : -1;
1002 }
1003
1004 /* If we have an X pixmap and its geometry, convert it to a GDK pixmap. */
1005 if (result == Success)
1006 {
1007 pixmap = _wnck_gdk_pixbuf_get_from_pixmap(screen, xpixmap, win, w, h);
1008 result = ((pixmap != NULL) ? Success : -1);
1009 }
1010
1011 /* If we have success, see if the result needs to be masked.
1012 * Failures here are implemented as nonfatal. */
1013 if ((result == Success) && (xmask != None))
1014 {
1015 Window win;
1016 int unused;
1017 unsigned int unused_2;
1018 if (XGetGeometry(
1019 xdisplay, xmask,
1020 &win, &unused, &unused, &w, &h, &unused_2, &unused_2))
1021 {
1022 /* Convert the X mask to a GDK pixmap. */
1023 GdkPixbuf * mask = _wnck_gdk_pixbuf_get_from_pixmap(screen, xmask, win, w, h);
1024 if (mask != NULL)
1025 {
1026 /* Apply the mask. */
1027 GdkPixbuf * masked_pixmap = apply_mask(pixmap, mask);
1028 g_object_unref(G_OBJECT(pixmap));
1029 g_object_unref(G_OBJECT(mask));
1030 pixmap = masked_pixmap;
1031 }
1032 }
1033 }
1034 }
1035
1036 /* If we got a pixmap, scale it and return it. */
1037 if (pixmap == NULL)
1038 return NULL;
1039 else
1040 {
1041 GdkPixbuf * ret;
1042
1043 *current_source = possible_source;
1044 if (tb->flags.disable_taskbar_upscale)
1045 {
1046 guint w = gdk_pixbuf_get_width (pixmap);
1047 guint h = gdk_pixbuf_get_height (pixmap);
1048 if (w <= required_width || h <= required_height)
1049 return pixmap;
1050 }
1051 ret = gdk_pixbuf_scale_simple(pixmap, required_width, required_height,
1052 GDK_INTERP_BILINEAR);
1053 g_object_unref(pixmap);
1054 return ret;
1055 }
1056 }
1057
1058 /* Update the icon of a task. */
1059 static void _task_update_icon(TaskButton *task, TaskDetails *details, Atom source)
1060 {
1061 GdkPixbuf *pixbuf = NULL;
1062
1063 if (source == a_NET_ACTIVE_WINDOW && details != NULL)
1064 pixbuf = details->icon; /* use cached icon */
1065
1066 /* Get the icon from the window's hints. */
1067 if (details != NULL && pixbuf == NULL)
1068 {
1069 pixbuf = get_wm_icon(details->win, task->icon_size, task->icon_size,
1070 source, &details->image_source, task);
1071 if (pixbuf)
1072 {
1073 /* replace old cached image */
1074 if (details->icon)
1075 g_object_unref(details->icon);
1076 details->icon = g_object_ref_sink(pixbuf);
1077 }
1078 else
1079 /* use cached icon if available */
1080 pixbuf = details->icon;
1081 }
1082
1083 /* If that fails, and we have no other icon yet, return the fallback icon. */
1084 if ((pixbuf == NULL)
1085 && ((source == None) || (details->image_source == None)))
1086 {
1087 GObject *parent = G_OBJECT(gtk_widget_get_parent(GTK_WIDGET(task)));
1088
1089 /* Establish the fallback task icon. This is used when no other icon is available. */
1090 pixbuf = g_object_get_data(parent, "task-fallback-pixbuf");
1091 if (pixbuf == NULL)
1092 {
1093 pixbuf = gdk_pixbuf_new_from_xpm_data((const char **) icon_xpm);
1094 if (pixbuf != NULL)
1095 g_object_set_data_full(parent, "task-fallback-pixbuf",
1096 g_object_ref_sink(pixbuf), g_object_unref);
1097 }
1098 }
1099
1100 if (pixbuf != NULL)
1101 gtk_image_set_from_pixbuf(GTK_IMAGE(task->image), pixbuf);
1102 }
1103
1104 static gboolean task_update_icon_idle(gpointer user_data)
1105 {
1106 TaskButton *task;
1107 GList *l;
1108 TaskDetails *details;
1109
1110 if (g_source_is_destroyed(g_main_current_source()))
1111 return FALSE;
1112 task = user_data;
1113 task->idle_loader = 0;
1114 for (l = task->details; l; l = l->next)
1115 {
1116 details = l->data;
1117 if (details->icon == NULL)
1118 _task_update_icon(task, details, None);
1119 }
1120 return FALSE;
1121 }
1122
1123 static void task_update_icon(TaskButton *task, TaskDetails *details, Atom source)
1124 {
1125 if (source != None || (details && details->icon))
1126 _task_update_icon(task, details, source);
1127 else if (task->idle_loader == 0)
1128 task->idle_loader = gdk_threads_add_timeout_full(G_PRIORITY_LOW, 20,
1129 task_update_icon_idle,
1130 task, NULL);
1131 }
1132
1133 /* Draw the label and tooltip on a taskbar button. */
1134 static void task_draw_label(TaskButton *tb, gboolean bold_style, gboolean force)
1135 {
1136 GString *str;
1137 gboolean old_bold = !!tb->set_bold;
1138
1139 if (!force && old_bold == bold_style) /* nothing to do */
1140 return;
1141 if (tb->flags.icons_only) /* no label to show */
1142 return;
1143
1144 tb->set_bold = bold_style;
1145 str = g_string_sized_new(32);
1146 if (!tb->visible)
1147 g_string_append_c(str, '[');
1148 if (tb->n_visible > 1)
1149 g_string_append_printf(str, "(%d) ", tb->n_visible);
1150 if (!tb->same_name || !tb->last_focused || !tb->last_focused->name)
1151 g_string_append(str, tb->res_class);
1152 else
1153 g_string_append(str, tb->last_focused->name);
1154 if (!tb->visible)
1155 g_string_append_c(str, ']');
1156
1157 if (force && tb->flags.tooltips)
1158 gtk_widget_set_tooltip_text(GTK_WIDGET(tb), str->str);
1159
1160 lxpanel_draw_label_text(tb->panel, tb->label, str->str, bold_style, 1,
1161 tb->flags.flat_button);
1162
1163 g_string_free(str, TRUE);
1164 }
1165
1166 /* conventional macro */
1167 #define task_redraw_label(b) task_draw_label(b, (b->flags.flat_button && b->entered_state), TRUE)
1168
1169
1170 /* update task->visible, task->n_visible, task->same_name
1171 also update task->last_focused if it was NULL
1172 returns TRUE if button's label would need update */
1173 static gboolean task_update_visibility(TaskButton *task)
1174 {
1175 guint old_n_visible = task->n_visible;
1176 gboolean old_visible = !!task->visible;
1177 gboolean old_same_name = !!task->same_name;
1178 gboolean old_last_focused = (task->last_focused != NULL && task->last_focused->visible);
1179 GList *l;
1180 TaskDetails *details, *first_visible = NULL;
1181
1182 task->same_name = TRUE;
1183 task->visible = FALSE;
1184 task->n_visible = 0;
1185 for (l = task->details; l; l = l->next)
1186 {
1187 details = l->data;
1188 details->visible = task_is_visible(task, details);
1189 if (!details->visible)
1190 continue;
1191 if (details->monitor == task->monitor && !details->iconified)
1192 /* window is visible on the current desktop */
1193 task->visible = TRUE;
1194 /* Compute the visible name. If all visible windows have the same title, use that.
1195 * Otherwise, use the class name. This follows WNCK. */
1196 if (first_visible == NULL)
1197 first_visible = details;
1198 else if (task->same_name
1199 && g_strcmp0(first_visible->name, details->name) != 0)
1200 task->same_name = FALSE;
1201 task->n_visible++;
1202 if (task->last_focused == NULL || !task->last_focused->visible)
1203 task->last_focused = details;
1204 }
1205 if (!task->n_visible && old_n_visible)
1206 {
1207 /* task button became invisible */
1208 gtk_widget_hide(GTK_WIDGET(task));
1209 return FALSE;
1210 }
1211 else if (task->n_visible && !old_n_visible)
1212 /* task button became visible */
1213 gtk_widget_show(GTK_WIDGET(task));
1214 return (task->n_visible != old_n_visible || /* n_visible changed */
1215 !task->visible == old_visible || /* visible changed */
1216 !task->same_name == old_same_name || /* visible name changed */
1217 (task->same_name && !old_last_focused)); /* visible name unavailable */
1218 }
1219
1220
1221 /* -----------------------------------------------------------------------------
1222 * Class implementation
1223 */
1224 G_DEFINE_TYPE(TaskButton, task_button, GTK_TYPE_TOGGLE_BUTTON)
1225
1226 static void task_button_finalize(GObject *object)
1227 {
1228 TaskButton *self = (TaskButton *)object;
1229
1230 /* free all data */
1231 g_free(self->res_class);
1232 if (self->menu_list)
1233 g_object_remove_weak_pointer(G_OBJECT(self->menu_list),
1234 (void **)&self->menu_list);
1235 if (self->idle_loader)
1236 g_source_remove(self->idle_loader);
1237 g_list_free_full(self->details, (GDestroyNotify)free_task_details);
1238
1239 G_OBJECT_CLASS(task_button_parent_class)->finalize(object);
1240 }
1241
1242 static gboolean task_button_button_press_event(GtkWidget *widget, GdkEventButton *event)
1243 {
1244 GtkWidget *menu, *mi;
1245 TaskButton *tb = PANEL_TASK_BUTTON(widget);
1246
1247 if (event->button == 3) /* Right click */
1248 {
1249 if (tb->n_visible > 1)
1250 {
1251 /* This is grouped-task representative, meaning that there is a class
1252 * with at least two windows. */
1253 menu = gtk_menu_new();
1254 mi = gtk_menu_item_new_with_mnemonic (_("_Close all windows"));
1255 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
1256 g_signal_connect(mi, "activate", G_CALLBACK(taskbar_close_all_windows), tb);
1257 gtk_widget_show_all(menu);
1258 }
1259 else
1260 {
1261 /* Not a grouped-task representative, or entered from the grouped-task popup menu. */
1262 menu = get_task_button_menu(tb, tb->last_focused);
1263 }
1264 /* detach menu from other button it it's already attached */
1265 if ((mi = gtk_menu_get_attach_widget(GTK_MENU(menu))) != NULL)
1266 gtk_menu_detach(GTK_MENU(menu));
1267 /* attach menu to the widget and show it */
1268 gtk_menu_attach_to_widget(GTK_MENU(menu), widget, NULL);
1269 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, taskbar_popup_set_position,
1270 tb, event->button, event->time);
1271 }
1272 return TRUE;
1273 }
1274
1275 static gboolean task_button_button_release_event(GtkWidget *widget, GdkEventButton *event)
1276 {
1277 TaskButton *tb = PANEL_TASK_BUTTON(widget);
1278 TaskDetails *task;
1279 GList *l;
1280 char *name;
1281
1282 if (!tb->entered_state)
1283 /* SF bug#731: don't process button release with DND. Also if button was
1284 released outside of widget but DND wasn't activated: this might happen
1285 if drag started at edge of button so drag treshold wasn't reached. */
1286 ;
1287
1288 else if (tb->n_visible > 1)
1289 {
1290 /* This is grouped-task representative, meaning that there is a class
1291 * with at least two windows. */
1292 if (event->button == 1) /* Left click */
1293 {
1294 if (tb->menu_list) // FIXME: is that possible?
1295 {
1296 g_object_remove_weak_pointer(G_OBJECT(tb->menu_list),
1297 (void **)&tb->menu_list);
1298 g_signal_handlers_disconnect_by_func(G_OBJECT(tb->menu_list),
1299 on_menu_list_selection_done, tb);
1300 gtk_menu_detach(tb->menu_list);
1301 }
1302 tb->menu_list = GTK_MENU(gtk_menu_new());
1303 g_object_add_weak_pointer(G_OBJECT(tb->menu_list), (void **)&tb->menu_list);
1304 g_signal_connect(G_OBJECT(tb->menu_list), "selection-done",
1305 G_CALLBACK(on_menu_list_selection_done), tb);
1306 /* Bring up a popup menu listing all the class members. */
1307 for (l = tb->details; l; l = l->next)
1308 {
1309 task = l->data;
1310 if (task->visible)
1311 {
1312 /* The menu item has the name, or the iconified name, and
1313 * the icon of the application window. */
1314 name = task->iconified ? g_strdup_printf("[%s]", task->name) : NULL;
1315 task->menu_item = gtk_image_menu_item_new_with_label(name ? name : task->name);
1316 g_free(name);
1317 if (task->icon)
1318 {
1319 GtkWidget *im = gtk_image_new_from_pixbuf(task->icon);
1320 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(task->menu_item), im);
1321 }
1322 g_signal_connect(task->menu_item, "button-press-event",
1323 G_CALLBACK(taskbar_popup_activate_event), tb);
1324 g_signal_connect(task->menu_item, "select",
1325 G_CALLBACK(menu_task_selected), tb);
1326 g_signal_connect(task->menu_item, "deselect",
1327 G_CALLBACK(menu_task_deselected), tb);
1328 gtk_menu_shell_append(GTK_MENU_SHELL(tb->menu_list), task->menu_item);
1329 }
1330 else
1331 task->menu_item = NULL;
1332 }
1333 /* Show the menu. Set context so we can find the menu later to dismiss it.
1334 * Use a position-calculation callback to get the menu nicely
1335 * positioned with respect to the button. */
1336 gtk_widget_show_all(GTK_WIDGET(tb->menu_list));
1337 gtk_menu_attach_to_widget(tb->menu_list, widget, NULL);
1338 gtk_menu_popup(tb->menu_list, NULL, NULL, taskbar_popup_set_position,
1339 tb, event->button, event->time);
1340 }
1341 }
1342 else
1343 {
1344 /* Not a grouped-task representative, or entered from the grouped-task popup menu. */
1345 task_button_window_do_release_event(widget, tb->last_focused, event);
1346 }
1347
1348 /* As a matter of policy, avoid showing selected or prelight states on flat buttons. */
1349 if (tb->flags.flat_button)
1350 gtk_widget_set_state(widget, GTK_STATE_NORMAL);
1351 return TRUE;
1352 }
1353
1354 static gboolean task_button_enter_notify_event(GtkWidget *widget, GdkEventCrossing *event)
1355 {
1356 TaskButton *tb = PANEL_TASK_BUTTON(widget);
1357
1358 tb->entered_state = TRUE;
1359 /* As a matter of policy, avoid showing selected or prelight states on flat buttons. */
1360 if (tb->flags.flat_button)
1361 gtk_widget_set_state(widget, GTK_STATE_NORMAL);
1362 task_draw_label(tb, tb->flags.flat_button, FALSE);
1363 return GTK_WIDGET_CLASS(task_button_parent_class)->enter_notify_event(widget, event);
1364 }
1365
1366 static gboolean task_button_leave_notify_event(GtkWidget *widget, GdkEventCrossing *event)
1367 {
1368 TaskButton *tb = PANEL_TASK_BUTTON(widget);
1369
1370 tb->entered_state = FALSE;
1371 task_draw_label(tb, FALSE, FALSE);
1372 return GTK_WIDGET_CLASS(task_button_parent_class)->leave_notify_event(widget, event);
1373 }
1374
1375 static gboolean task_button_scroll_event(GtkWidget *widget, GdkEventScroll *event)
1376 {
1377 TaskButton *tb = PANEL_TASK_BUTTON(widget);
1378
1379 if (tb->flags.use_mouse_wheel && tb->n_visible == 1)
1380 {
1381 if ((event->direction == GDK_SCROLL_UP) || (event->direction == GDK_SCROLL_LEFT))
1382 task_raise_window(tb, tb->last_focused, event->time);
1383 else
1384 {
1385 Display *xdisplay = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(widget));
1386 XIconifyWindow(xdisplay, tb->last_focused->win, DefaultScreen(xdisplay));
1387 }
1388 }
1389 return TRUE;
1390 }
1391
1392 static void task_button_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
1393 {
1394 TaskButton *tb = PANEL_TASK_BUTTON(widget);
1395 GList *l;
1396
1397 /* Pass it to the GtkToggleButton handler first */
1398 GTK_WIDGET_CLASS(task_button_parent_class)->size_allocate(widget, alloc);
1399
1400 /* Set iconifying animation for all related windows into this button */
1401 if (gtk_widget_get_realized(widget))
1402 for (l = tb->details; l; l = l->next)
1403 map_xwindow_animation(widget, ((TaskDetails *)l->data)->win, alloc);
1404 }
1405
1406 static void task_button_class_init(TaskButtonClass *klass)
1407 {
1408 GObjectClass *object_class = G_OBJECT_CLASS(klass);
1409 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1410
1411 object_class->finalize = task_button_finalize;
1412 widget_class->button_press_event = task_button_button_press_event;
1413 widget_class->button_release_event = task_button_button_release_event;
1414 widget_class->enter_notify_event = task_button_enter_notify_event;
1415 widget_class->leave_notify_event = task_button_leave_notify_event;
1416 widget_class->scroll_event = task_button_scroll_event;
1417 widget_class->size_allocate = task_button_size_allocate;
1418
1419 /**
1420 * Signal TaskButton::menu-built is emitted when GtkMenu is built
1421 * by TaskButton on its parent widget. Connected callback therefore
1422 * can add own menu items with handlers.
1423 */
1424 signals[MENU_BUILT] = g_signal_new ("menu-built",
1425 G_TYPE_FROM_CLASS(klass),
1426 G_SIGNAL_RUN_FIRST,
1427 G_STRUCT_OFFSET(TaskButtonClass, menu_built),
1428 NULL, NULL,
1429 g_cclosure_marshal_VOID__OBJECT,
1430 G_TYPE_NONE, 1, GTK_TYPE_MENU);
1431
1432 /**
1433 * Signal TaskButton::menu-target-set is emitted when TaskButton
1434 * activated menu popup against some task. If any items were added
1435 * in TaskButton::menu-built callback, their visibility should be
1436 * managed in this callback, or all them will be visible by default.
1437 */
1438 signals[MENU_TARGET_SET] = g_signal_new ("menu-target-set",
1439 G_TYPE_FROM_CLASS(klass),
1440 G_SIGNAL_RUN_FIRST,
1441 G_STRUCT_OFFSET(TaskButtonClass, menu_target_set),
1442 NULL, NULL,
1443 g_cclosure_marshal_VOID__ULONG,
1444 G_TYPE_NONE, 1, G_TYPE_ULONG);
1445
1446 a_NET_WM_STATE_MAXIMIZED_VERT = XInternAtom(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
1447 "_NET_WM_STATE_MAXIMIZED_VERT", False);
1448 a_NET_WM_STATE_MAXIMIZED_HORZ = XInternAtom(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
1449 "_NET_WM_STATE_MAXIMIZED_HORZ", False);
1450 }
1451
1452 static void task_button_init(TaskButton *self)
1453 {
1454 gtk_container_set_border_width(GTK_CONTAINER(self), 0);
1455 gtk_widget_set_can_focus(GTK_WIDGET(self), FALSE);
1456 gtk_widget_set_can_default(GTK_WIDGET(self), FALSE);
1457 gtk_widget_set_state(GTK_WIDGET(self), GTK_STATE_NORMAL);
1458 #if GTK_CHECK_VERSION(3, 0, 0)
1459 gtk_widget_add_events(GTK_WIDGET(self), GDK_SCROLL_MASK);
1460 #endif
1461 }
1462
1463
1464 /* -----------------------------------------------------------------------------
1465 * Interface functions
1466 */
1467
1468 /* creates new button and sets rendering options */
1469 TaskButton *task_button_new(Window win, gint desk, gint desks, LXPanel *panel,
1470 const char *res_class, TaskShowFlags flags)
1471 {
1472 TaskButton *self = g_object_new(PANEL_TYPE_TASK_BUTTON,
1473 "relief", flags.flat_button ? GTK_RELIEF_NONE : GTK_RELIEF_NORMAL,
1474 NULL);
1475
1476 /* remember data */
1477 self->desktop = desk;
1478 self->n_desktops = desks;
1479 self->panel = panel;
1480 self->monitor = panel_get_monitor(panel);
1481 self->icon_size = panel_get_icon_size(panel);
1482 if (flags.use_smaller_icons)
1483 self->icon_size -= 4;
1484 self->res_class = g_strdup(res_class);
1485 self->flags = flags;
1486 /* create empty image and label */
1487 self->image = gtk_image_new();
1488 self->label = gtk_label_new(NULL);
1489 /* append the window and set icon/label by that */
1490 task_button_add_window(self, win, self->res_class);
1491 /* and now let assemble all widgets we got */
1492 assemble_gui(self);
1493 /* and finally set visibility on it */
1494 gtk_widget_set_visible(GTK_WIDGET(self), self->n_visible > 0);
1495 return self;
1496 }
1497
1498 gboolean task_button_has_window(TaskButton *button, Window win)
1499 {
1500 GList *l;
1501
1502 g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button), FALSE);
1503
1504 for (l = button->details; l; l = l->next)
1505 if (((TaskDetails *)l->data)->win == win)
1506 return TRUE;
1507 return FALSE;
1508 }
1509
1510 /* removes windows from button, that are missing in list */
1511 void task_button_update_windows_list(TaskButton *button, Window *list, gint n)
1512 {
1513 GList *l, *next;
1514 TaskDetails *details;
1515 gint i;
1516 gboolean has_removed = FALSE;
1517
1518 g_return_if_fail(PANEL_IS_TASK_BUTTON(button));
1519
1520 for (l = button->details; l; )
1521 {
1522 next = l->next;
1523 details = l->data;
1524 for (i = 0; i < n; i++)
1525 if (list[i] == details->win)
1526 break;
1527 if (i >= n) /* not found, remove details now */
1528 {
1529 button->details = g_list_delete_link(button->details, l);
1530 free_task_details(details);
1531 if (button->last_focused == details)
1532 button->last_focused = NULL;
1533 has_removed = TRUE;
1534 }
1535 l = next; /* go next details */
1536 }
1537 if (button->details == NULL) /* all windows were deleted */
1538 gtk_widget_destroy(GTK_WIDGET(button));
1539 else if (has_removed && task_update_visibility(button))
1540 task_redraw_label(button);
1541 // FIXME: test if need to update menu
1542 }
1543
1544 /* returns TRUE if found and updated */
1545 gboolean task_button_window_xprop_changed(TaskButton *button, Window win, Atom atom)
1546 {
1547 TaskDetails *details;
1548
1549 g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button), FALSE);
1550
1551 details = task_details_lookup(button, win);
1552 if (details == NULL)
1553 return FALSE;
1554
1555 /* Dispatch on atom. */
1556 if (atom == a_NET_WM_DESKTOP)
1557 {
1558 /* Window changed desktop. */
1559 details->desktop = get_net_wm_desktop(win);
1560 details->visible = task_is_visible(button, details);
1561 if (task_update_visibility(button))
1562 task_redraw_label(button);
1563 }
1564 else if ((atom == XA_WM_NAME) || (atom == a_NET_WM_NAME) || (atom == a_NET_WM_VISIBLE_NAME))
1565 {
1566 /* Window changed name. */
1567 if (task_set_names(details, atom))
1568 task_redraw_label(button);
1569 }
1570 else if (atom == XA_WM_CLASS)
1571 {
1572 /* Read the WM_CLASS property. */
1573 XClassHint ch;
1574 gchar *res_class;
1575
1576 ch.res_name = NULL;
1577 ch.res_class = NULL;
1578 XGetClassHint(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), win, &ch);
1579 if (ch.res_name != NULL)
1580 XFree(ch.res_name);
1581 if (ch.res_class != NULL)
1582 {
1583 res_class = g_locale_to_utf8(ch.res_class, -1, NULL, NULL, NULL);
1584 XFree(ch.res_class);
1585 if (res_class != NULL)
1586 {
1587 g_free(button->res_class);
1588 button->res_class = res_class;
1589 if (!button->same_name)
1590 task_redraw_label(button);
1591 }
1592 }
1593 }
1594 else if (atom == a_WM_STATE)
1595 {
1596 /* Window changed state. */
1597 details->iconified = (get_wm_state(win) == IconicState);
1598 details->visible = task_is_visible(button, details);
1599 if (task_update_visibility(button))
1600 task_redraw_label(button);
1601 }
1602 else if (atom == XA_WM_HINTS)
1603 {
1604 gboolean has_urgency = details->urgency;
1605
1606 details->urgency = task_has_urgency(win);
1607 if (!has_urgency && details->urgency && button->flags.use_urgency_hint)
1608 {
1609 /* gained urgency, update the button */
1610 details->visible = task_is_visible(button, details);
1611 task_update_visibility(button);
1612 if (details->visible)
1613 button->last_focused = details;
1614 task_redraw_label(button);
1615 }
1616 /* Window changed "window manager hints".
1617 * Some windows set their WM_HINTS icon after mapping. */
1618 task_update_icon(button, details, atom);
1619 }
1620 else if (atom == a_NET_WM_ICON)
1621 {
1622 /* Window changed EWMH icon. */
1623 task_update_icon(button, details, atom);
1624 }
1625 /* else
1626 {
1627 char *ev_name = XGetAtomName(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), atom);
1628 g_debug("got event for me: %s", ev_name);
1629 XFree(ev_name);
1630 } */
1631
1632 return TRUE;
1633 }
1634
1635 /* gboolean task_button_window_state_changed(TaskButton *button, Window win, NetWMState nws)
1636 {
1637 TaskDetails *details;
1638
1639 g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button), FALSE);
1640
1641 details = task_details_lookup(button, win);
1642 if (details == NULL)
1643 return FALSE;
1644
1645 details->iconified = nws.hidden;
1646 g_debug("is hidden: %d",nws.hidden);
1647 details->visible = task_is_visible(button, details);
1648 if (task_update_visibility(button))
1649 task_redraw_label(button);
1650
1651 return TRUE;
1652 } */
1653
1654 gboolean task_button_window_focus_changed(TaskButton *button, Window *win)
1655 {
1656 GList *l;
1657 TaskDetails *details;
1658 gboolean res = FALSE;
1659
1660 g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button), FALSE);
1661
1662 for (l = button->details; l; l = l->next)
1663 {
1664 details = l->data;
1665 if (details->win == *win)
1666 {
1667 res = TRUE;
1668 details->focused = TRUE;
1669 button->last_focused = details;
1670 }
1671 else
1672 details->focused = FALSE;
1673 }
1674 if (res)
1675 {
1676 /* for no flat buttons we have to reflect focus by button state */
1677 if (!button->flags.flat_button)
1678 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
1679 /* if focus changed that means button widgets may need update */
1680 task_update_icon(button, button->last_focused, a_NET_ACTIVE_WINDOW);
1681 task_redraw_label(button);
1682 // FIXME: test if need to update menu
1683 }
1684 else
1685 {
1686 /* if no focus on any button window then button may need style update */
1687 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE);
1688 // FIXME: test if need to update menu
1689 }
1690 return res;
1691 }
1692
1693 /* update internal data */
1694 gboolean task_button_window_reconfigured(TaskButton *button, Window win)
1695 {
1696 gint old_mon, new_mon;
1697 TaskDetails *details;
1698
1699 g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button), FALSE);
1700
1701 details = task_details_lookup(button, win);
1702 if (details == NULL)
1703 return FALSE;
1704
1705 /* If the same_monitor_only option is set and the window is on a different
1706 monitor than before, redraw the task button */
1707 old_mon = details->monitor;
1708 new_mon = get_window_monitor(details->win);
1709
1710 if (button->flags.same_monitor_only
1711 && (old_mon == button->monitor || new_mon == button->monitor))
1712 {
1713 details->visible = task_is_visible(button, details);
1714 task_update_visibility(button);
1715 task_redraw_label(button);
1716 // FIXME: test if need to update menu
1717 }
1718 details->monitor = new_mon;
1719 return TRUE;
1720 }
1721
1722 /* updates rendering options */
1723 void task_button_update(TaskButton *button, gint desk, gint desks,
1724 gint mon, guint icon_size, TaskShowFlags flags)
1725 {
1726 gboolean changed = FALSE, changed_icon = FALSE, changed_label = FALSE;
1727
1728 g_return_if_fail(PANEL_IS_TASK_BUTTON(button));
1729
1730 if (button->desktop != desk
1731 || button->monitor != mon
1732 || button->flags.show_all_desks != flags.show_all_desks
1733 || button->flags.same_monitor_only != flags.same_monitor_only)
1734 changed = TRUE;
1735 if (button->n_desktops != desks)
1736 task_button_reset_menu(gtk_widget_get_parent(GTK_WIDGET(button)));
1737 if (button->icon_size != icon_size
1738 || button->flags.disable_taskbar_upscale != flags.disable_taskbar_upscale)
1739 changed_icon = TRUE;
1740 if (button->flags.flat_button != flags.flat_button)
1741 changed_label = TRUE;
1742 if (button->flags.icons_only != flags.icons_only)
1743 {
1744 changed_label = !flags.icons_only;
1745 gtk_widget_set_visible(button->label, changed_label);
1746 }
1747 if (button->flags.flat_button != flags.flat_button)
1748 {
1749 if(flags.flat_button)
1750 {
1751 gtk_toggle_button_set_active((GtkToggleButton*)button, FALSE);
1752 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
1753 }
1754 else
1755 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NORMAL);
1756 }
1757 button->desktop = desk;
1758 button->n_desktops = desks;
1759 button->monitor = mon;
1760 button->icon_size = icon_size;
1761 button->flags = flags;
1762
1763 if (changed)
1764 {
1765 if (task_update_visibility(button))
1766 changed_label = TRUE;
1767 // FIXME: test if need to update menu
1768 }
1769 if (changed_label)
1770 task_redraw_label(button);
1771 if (changed_icon)
1772 task_update_icon(button, button->last_focused, None);
1773 }
1774
1775 /* updates state for flashing buttons, including menu list */
1776 void task_button_set_flash_state(TaskButton *button, gboolean state)
1777 {
1778 gboolean has_flash = FALSE, m_state;
1779 GList *l;
1780 TaskDetails *details;
1781
1782 g_return_if_fail(PANEL_IS_TASK_BUTTON(button));
1783
1784 for (l = button->details; l; l = l->next)
1785 {
1786 details = l->data;
1787 if (button->flags.use_urgency_hint && details->urgency)
1788 {
1789 has_flash = TRUE;
1790 m_state = state;
1791 }
1792 else
1793 m_state = FALSE;
1794 if (button->menu_list && details->menu_item
1795 /* don't ever touch selected menu item, it makes odd effects */
1796 && button->menu_target != details->win)
1797 /* if submenu exists and mapped then set state too */
1798 gtk_widget_set_state(details->menu_item,
1799 m_state ? GTK_STATE_SELECTED : GTK_STATE_NORMAL);
1800 }
1801 /* Set state on the button and redraw. */
1802 if (!has_flash)
1803 state = button->entered_state;
1804 if (button->flags.flat_button)
1805 task_draw_label(button, state, FALSE); /* we have to redraw bold text state */
1806 else
1807 gtk_widget_set_state(GTK_WIDGET(button),
1808 state ? GTK_STATE_SELECTED : GTK_STATE_NORMAL);
1809 }
1810
1811 /* adds task only if it's the same class */
1812 gboolean task_button_add_window(TaskButton *button, Window win, const char *cl)
1813 {
1814 TaskDetails *details;
1815 GtkAllocation alloc;
1816
1817 g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button), FALSE);
1818
1819 if (g_strcmp0(button->res_class, cl) != 0)
1820 return FALSE;
1821 /* fetch task details */
1822 details = task_details_for_window(button, win);
1823 button->details = g_list_append(button->details, details);
1824 /* redraw label on the button if need */
1825 if (details->visible)
1826 {
1827 if (task_update_visibility(button))
1828 task_redraw_label(button);
1829 // FIXME: test if need to update menu
1830 }
1831 gtk_widget_get_allocation(GTK_WIDGET(button), &alloc);
1832 map_xwindow_animation(GTK_WIDGET(button), win, &alloc);
1833 return TRUE;
1834 }
1835
1836 gboolean task_button_drop_window(TaskButton *button, Window win, gboolean leave_last)
1837 {
1838 GList *l;
1839 TaskDetails *details;
1840 gboolean was_last_focused;
1841
1842 g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button), FALSE);
1843
1844 if (leave_last && g_list_length(button->details) <= 1)
1845 return FALSE;
1846 for (l = button->details; l; l = l->next)
1847 if (((TaskDetails *)l->data)->win == win)
1848 break;
1849 if (l == NULL) /* not our window */
1850 return FALSE;
1851 if (g_list_length(button->details) == 1)
1852 {
1853 /* this was last window, destroy the button */
1854 gtk_widget_destroy(GTK_WIDGET(button));
1855 return TRUE;
1856 }
1857 details = l->data;
1858 button->details = g_list_delete_link(button->details, l);
1859 was_last_focused = (button->last_focused == details);
1860 if (was_last_focused)
1861 button->last_focused = NULL;
1862 if (details->visible)
1863 {
1864 task_update_visibility(button);
1865 if (was_last_focused)
1866 task_update_icon(button, button->last_focused, None);
1867 task_redraw_label(button);
1868 // FIXME: test if need to update menu
1869 }
1870 /* bug SF#823: menu may be still opened for this window */
1871 if (button->menu_list && details->menu_item)
1872 gtk_widget_destroy(details->menu_item);
1873 free_task_details(details);
1874 return TRUE;
1875 }
1876
1877 /* leaves only last task in button and returns a copy containing rest */
1878 TaskButton *task_button_split(TaskButton *button)
1879 {
1880 TaskButton *sibling;
1881 GList *llast;
1882
1883 g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button), NULL);
1884
1885 if (g_list_length(button->details) < 2)
1886 return NULL;
1887 sibling = g_object_new(PANEL_TYPE_TASK_BUTTON,
1888 "relief", button->flags.flat_button ? GTK_RELIEF_NONE : GTK_RELIEF_NORMAL,
1889 NULL);
1890 sibling->res_class = g_strdup(button->res_class);
1891 sibling->panel = button->panel;
1892 sibling->image = gtk_image_new();
1893 sibling->label = gtk_label_new(NULL);
1894 llast = g_list_last(button->details);
1895 sibling->details = g_list_remove_link(button->details, llast);
1896 button->details = llast;
1897 if (button->last_focused != llast->data)
1898 {
1899 /* focused item migrated to sibling */
1900 sibling->last_focused = button->last_focused;
1901 button->last_focused = NULL;
1902 }
1903 sibling->desktop = button->desktop;
1904 sibling->n_desktops = button->n_desktops;
1905 sibling->monitor = button->monitor;
1906 sibling->icon_size = button->icon_size;
1907 sibling->flags = button->flags;
1908 task_update_visibility(button);
1909 task_update_visibility(sibling);
1910 /* force redraw icons and labels on buttons */
1911 if (button->n_visible > 0)
1912 {
1913 task_update_icon(button, button->last_focused, None);
1914 task_draw_label(button, FALSE, TRUE);
1915 }
1916 if (sibling->n_visible > 0)
1917 {
1918 task_update_icon(sibling, button->last_focused, None);
1919 task_draw_label(sibling, FALSE, TRUE);
1920 }
1921 assemble_gui(sibling);
1922 // FIXME: test if need to update menu
1923 return sibling;
1924 }
1925
1926 /* merges buttons if they are the same class */
1927 gboolean task_button_merge(TaskButton *button, TaskButton *sibling)
1928 {
1929 g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button) && PANEL_IS_TASK_BUTTON(sibling), FALSE);
1930
1931 if (g_strcmp0(button->res_class, sibling->res_class) != 0)
1932 return FALSE;
1933 /* move data lists from sibling appending to button */
1934 button->details = g_list_concat(button->details, sibling->details);
1935 sibling->details = NULL;
1936 /* update visibility */
1937 button->n_visible += sibling->n_visible;
1938 button->visible = (button->visible | sibling->visible);
1939 /* eliminate sibling widget now */
1940 gtk_widget_destroy(GTK_WIDGET(sibling));
1941 /* redraw label on the button */
1942 task_redraw_label(button);
1943 // FIXME: test if need to update menu
1944 return TRUE;
1945 }
1946
1947 /* single-instance-menu management, should be called on button parent widget */
1948 void task_button_reset_menu(GtkWidget *parent)
1949 {
1950 GtkWidget *menu = g_object_get_data(G_OBJECT(parent), "task-button-menu");
1951
1952 if (menu)
1953 {
1954 gtk_menu_detach(GTK_MENU(menu));
1955 g_object_set_data(G_OBJECT(parent), "task-button-menu", NULL);
1956 }
1957 g_object_set_data(G_OBJECT(parent), "task-button-current", NULL);
1958 }