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