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