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