launchtaskbar plugin: correctly update data for non-launchtaskbar modes.
[lxde/lxpanel.git] / src / plugins / launchtaskbar.c
CommitLineData
56f19a82
GP
1/**
2 * Copyright (c) 2006 LxDE Developers, see the file AUTHORS for details.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19/*
6d740d5c
GP
20 * Started by Giuseppe Penone <giuspen@gmail.com> merging launchbar and taskbar
21 * and adding interoperability between them.
56f19a82 22*/
b5d1a361 23//#define DEBUG_WITH_GPRINTS // killall lxpanel && lxpanel --profile Lubuntu &
56f19a82
GP
24
25#ifdef HAVE_CONFIG_H
26#include <config.h>
27#endif
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <unistd.h>
33#include <sys/types.h>
34#include <sys/wait.h>
35#include <signal.h>
36#include <errno.h>
37#include <X11/Xlib.h>
38#include <X11/Xutil.h>
39
40#include <gdk-pixbuf/gdk-pixbuf.h>
41#include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
42#include <gdk/gdk.h>
43#include <glib/gi18n.h>
44
6951c204 45#include <libfm/fm-gtk.h>
56f19a82 46
56f19a82 47#include "misc.h"
6951c204
AG
48#include "ev.h"
49#include "plugin.h"
56f19a82 50#include "icon.xpm"
56f19a82 51#include "icon-grid.h"
59475d30
AG
52#ifndef DISABLE_MENU
53# include "menu-policy.h"
54#endif
56f19a82 55
6951c204 56#include "dbg.h" /* for ERR macro. FIXME: is it required? */
56f19a82
GP
57
58
6951c204 59#define PANEL_ICON_SIZE 24 /* see the private.h */
56f19a82 60
6951c204 61extern FbEv *fbev;
56f19a82
GP
62
63/* Column definitions for configuration dialogs. */
64enum {
65 COL_ICON,
66 COL_TITLE,
67 COL_ICON_NAME,
68 COL_BTN,
69 N_COLS
70};
71
6951c204 72typedef struct LaunchTaskBarPlugin LaunchTaskBarPlugin;
56f19a82
GP
73
74/* Structure representing a class. This comes from WM_CLASS, and should identify windows that come from an application. */
75typedef struct _task_class {
76 struct _task_class *p_taskclass_flink; /* Forward link */
77 char * res_class; /* Class name */
78 struct _task * p_task_head; /* Head of list of tasks with this class */
79 struct _task * p_task_visible; /* Task that is visible in current desktop, if any */
80 char * visible_name; /* Name that will be visible for grouped tasks */
81 int visible_count; /* Count of tasks that are visible in current desktop */
82} TaskClass;
83
84/* Structure representing a "task", an open window. */
85typedef struct _task {
86 struct _task * p_task_flink_xwid; /* Forward link to next task in X window ID order */
6951c204 87 LaunchTaskBarPlugin * tb; /* Back pointer to plugin */
56f19a82
GP
88 Window win; /* X window ID */
89 char * name; /* Taskbar label when normal, from WM_NAME or NET_WM_NAME */
90 char * name_iconified; /* Taskbar label when iconified */
6951c204 91 char * exec_bin; /* Exec bin associated to Window */
56f19a82
GP
92 Atom name_source; /* Atom that is the source of taskbar label */
93 TaskClass * p_taskclass; /* Class, from WM_CLASS */
94 struct _task * p_task_flink_same_class; /* Forward link to task in same class */
95 GtkWidget * button; /* Button representing task in taskbar */
96 GtkWidget * image; /* Icon for task, child of button */
97 Atom image_source; /* Atom that is the source of taskbar icon */
98 GtkWidget * label; /* Label for task, child of button */
99 int desktop; /* Desktop that contains task, needed to switch to it on Raise */
4bb5845f 100 gint monitor; /* Monitor that the window is on or closest to */
56f19a82
GP
101 guint flash_timeout; /* Timer for urgency notification */
102 unsigned int focused :1; /* True if window has focus */
103 unsigned int iconified :1; /* True if window is iconified, from WM_STATE */
104 unsigned int urgency :1; /* True if window has an urgency hint, from WM_HINTS */
105 unsigned int flash_state :1; /* One-bit counter to flash taskbar */
106 unsigned int entered_state :1; /* True if cursor is inside taskbar button */
107 unsigned int present_in_client_list :1; /* State during WM_CLIENT_LIST processing to detect deletions */
108} Task;
109
6951c204
AG
110/* Representative of one launch button.
111 * Note that the launch parameters come from the specified desktop file, or from the configuration file.
112 * This structure is also used during the "add to launchtaskbar" dialog to hold menu items. */
113typedef struct {
114 LaunchTaskBarPlugin * p; /* Back pointer to plugin */
115 GtkWidget * widget; /* Pointer to button */
116 GtkWidget * image_widget; /* Pointer to image */
117 FmFileInfo * fi; /* Launcher application descriptor */
118 config_setting_t * settings; /* Pointer to settings */
119 FmDndDest * dd; /* Drag and drop support */
120} LaunchButton;
121
56f19a82 122/* Private context for taskbar plugin. */
6951c204
AG
123struct LaunchTaskBarPlugin {
124 /* LAUNCHBAR */
125 IconGrid *lb_icon_grid; /* Icon grid managing the container */
126 GSList *buttons; /* Launchbar buttons */
127 LaunchButton *bootstrap_button; /* Bootstrapping button for empty launchtaskbar */
128 FmIcon * add_icon; /* Icon for bootstrap_button */
129 GtkWidget *p_button_add, *p_button_remove, *p_label_menu_app_exec, *p_label_def_app_exec;
130 /* TASKBAR */
56f19a82
GP
131 Task * p_task_list; /* List of tasks to be displayed in taskbar */
132 TaskClass * p_taskclass_list; /* Window class list */
6951c204 133 IconGrid * tb_icon_grid; /* Manager for taskbar buttons */
56f19a82
GP
134 GtkWidget * menu; /* Popup menu for task control (Close, Raise, etc.) */
135 GtkWidget * group_menu; /* Popup menu for grouping selection */
136 GdkPixbuf * fallback_pixbuf; /* Fallback task icon when none is available */
137 int number_of_desktops; /* Number of desktops, from NET_WM_NUMBER_OF_DESKTOPS */
138 int current_desktop; /* Current desktop, from NET_WM_CURRENT_DESKTOP */
139 Task * focused; /* Task that has focus */
140 Task * focused_previous; /* Task that had focus just before panel got it */
141 Task * menutask; /* Task for which popup menu is open */
142 guint dnd_delay_timer; /* Timer for drag and drop delay */
143 int icon_size; /* Size of task icons */
144 gboolean show_all_desks; /* User preference: show windows from all desktops */
145 gboolean tooltips; /* User preference: show tooltips */
146 gboolean icons_only; /* User preference: show icons only, omit name */
147 gboolean use_mouse_wheel; /* User preference: scroll wheel does iconify and raise */
148 gboolean use_urgency_hint; /* User preference: windows with urgency will flash */
149 gboolean flat_button; /* User preference: taskbar buttons have visible background */
150 gboolean grouped_tasks; /* User preference: windows from same task are grouped onto a single button */
4bb5845f 151 gboolean same_monitor_only; /* User preference: only show windows that are in the same monitor as the taskbar */
56f19a82
GP
152 int task_width_max; /* Maximum width of a taskbar button in horizontal orientation */
153 int spacing; /* Spacing between taskbar buttons */
154 gboolean use_net_active; /* NET_WM_ACTIVE_WINDOW is supported by the window manager */
155 gboolean net_active_checked; /* True if use_net_active is valid */
6951c204 156 /* COMMON */
59475d30 157#ifndef DISABLE_MENU
6d740d5c
GP
158 GtkWidget *p_menuitem_lock_tbp;
159 GtkWidget *p_menuitem_unlock_tbp;
160 GtkWidget *p_menuitem_new_instance;
59475d30 161#endif
6951c204
AG
162 GtkWidget * plugin; /* Back pointer to Plugin */
163 Panel * panel; /* Back pointer to panel */
164 config_setting_t * settings;
165 GtkWidget *p_evbox_launchbar;
166 GtkWidget *p_evbox_taskbar;
167 GtkWidget *config_dlg; /* Configuration dialog */
168 GtkWidget *p_notebook_page_launch;
169 GtkWidget *p_notebook_page_task;
0c66391d 170 GKeyFile *p_key_file_special_cases;
6951c204
AG
171 gboolean lb_on;
172 gboolean lb_built;
173 gboolean tb_on;
174 gboolean tb_built;
175};
56f19a82 176
2255393d 177static gchar *launchtaskbar_rc = "style 'launchtaskbar-style'\n"
56f19a82
GP
178 "{\n"
179 "GtkWidget::focus-line-width=0\n"
2255393d 180 "GtkWidget::focus-padding=0\n"
56f19a82
GP
181 "GtkButton::default-border={0,0,0,0}\n"
182 "GtkWidget::focus-padding=0\n"
183 "GtkButton::default-outside-border={0,0,0,0}\n"
2255393d 184 "GtkButton::inner-border={0,0,0,0}\n"
56f19a82
GP
185 "}\n"
186 "widget '*launchtaskbar*' style 'launchtaskbar-style'";
187
188#define DRAG_ACTIVE_DELAY 1000
189#define TASK_WIDTH_MAX 200
6951c204 190#define ALL_WORKSPACES -1
56f19a82
GP
191#define ICON_ONLY_EXTRA 6 /* Amount needed to have button lay out symmetrically */
192#define ICON_BUTTON_TRIM 4 /* Amount needed to have button remain on panel */
193
6951c204 194static void launchtaskbar_destructor(gpointer user_data);
56f19a82 195
6951c204
AG
196static void taskbar_redraw(LaunchTaskBarPlugin * tb);
197static void task_delete(LaunchTaskBarPlugin * tb, Task * tk, gboolean unlink);
198static GdkPixbuf * task_update_icon(LaunchTaskBarPlugin * tb, Task * tk, Atom source);
3899f188
AG
199static void flash_window_update(Task * tk);
200static gboolean flash_window_timeout(gpointer tk);
6951c204 201static void task_group_menu_destroy(LaunchTaskBarPlugin * tb);
56f19a82 202static gboolean taskbar_popup_activate_event(GtkWidget * widget, GdkEventButton * event, Task * tk);
6951c204
AG
203static void taskbar_update_style(LaunchTaskBarPlugin * tb);
204static void taskbar_net_client_list(GtkWidget * widget, LaunchTaskBarPlugin * tb);
205static void taskbar_net_current_desktop(GtkWidget * widget, LaunchTaskBarPlugin * tb);
206static void taskbar_net_number_of_desktops(GtkWidget * widget, LaunchTaskBarPlugin * tb);
207static void taskbar_net_active_window(GtkWidget * widget, LaunchTaskBarPlugin * tb);
56f19a82 208static gboolean task_has_urgency(Task * tk);
6951c204
AG
209static GdkFilterReturn taskbar_event_filter(XEvent * xev, GdkEvent * event, LaunchTaskBarPlugin * tb);
210static void taskbar_make_menu(LaunchTaskBarPlugin * tb);
211static void taskbar_window_manager_changed(GdkScreen * screen, LaunchTaskBarPlugin * tb);
212static void taskbar_apply_configuration(LaunchTaskBarPlugin * ltbp);
56f19a82 213
f6388511 214static void f_get_exec_cmd_from_pid(GPid pid, gchar *buffer_128, const gchar *proc_file)
7e9a6a33 215{
d0ab2e8e 216 buffer_128[0] = '\0';
7e9a6a33
GP
217 FILE *pipe;
218 gchar command[64];
f6388511 219 snprintf(command, 64, "cat /proc/%u/%s", pid, proc_file);
7e9a6a33
GP
220 pipe = popen(command, "r");
221 if(pipe == NULL)
b1f647cc 222 ERR("ltbp: popen '%s'\n", command);
d0ab2e8e 223 else if(fgets(buffer_128, 128, pipe) == NULL)
b1f647cc 224 ERR("ltbp: fgets '%s'\n", command);
f6388511
GP
225 else
226 {
227 gchar *p_char = strchr(buffer_128, '\n');
228 if(p_char != NULL) *p_char = '\0';
229 }
d0ab2e8e 230 if(pipe != NULL) pclose(pipe);
7e9a6a33
GP
231}
232
59475d30 233#ifndef DISABLE_MENU
6951c204 234static FmFileInfo *f_find_menu_launchbutton_recursive(const char *exec_bin)
007abf16 235{
59475d30
AG
236 MenuCache *mc;
237 guint32 flags;
238 GSList *apps, *l;
b5d1a361 239 size_t len;
dac88e35 240 const char *exec, *short_exec;
6951c204
AG
241 char *str_path;
242 FmPath *path;
59475d30
AG
243 FmFileInfoJob *job;
244 FmFileInfo *fi = NULL;
245
246 /* FIXME: cache it in Task object */
247 mc = panel_menu_cache_new(&flags);
248 apps = menu_cache_list_all_apps(mc);
b5d1a361
GP
249 short_exec = strrchr(exec_bin, '/');
250 if (short_exec != NULL)
251 short_exec++;
252 else
253 short_exec = exec_bin;
254 len = strlen(short_exec);
255 /* the same executable may be used in numerous applications so wild guess
256 estimation check for desktop id equal to short_exec+".desktop" first */
59475d30
AG
257 for (l = apps; l; l = l->next)
258 {
b5d1a361 259 exec = menu_cache_item_get_id(MENU_CACHE_ITEM(l->data));
ecce04a0
AG
260 /* we don't check flags here because user always can manually
261 start any app that isn't visible in the desktop menu */
b5d1a361
GP
262 if (strncmp(exec, short_exec, len) == 0 && exec[len] == '.')
263 break;
264 }
265 /* if not found then check for non-absolute exec name in application
266 since it usually is expanded by application starting functions */
267 if (l == NULL) for (l = apps; l; l = l->next)
268 {
59475d30 269 exec = menu_cache_app_get_exec(MENU_CACHE_APP(l->data));
b5d1a361
GP
270 if (exec[0] != '/' && strncmp(exec, short_exec, len) == 0 &&
271 (exec[len] == ' ' || exec[len] == 0))
59475d30
AG
272 break;
273 }
b5d1a361
GP
274 /* well, not matched, let try full path, we assume here if application
275 starts executable by full path then process cannot have short name */
276 if (l == NULL && exec_bin[0] == '/')
dac88e35 277 {
b5d1a361 278 len = strlen(exec_bin);
dac88e35
GP
279 for (l = apps; l; l = l->next)
280 {
281 exec = menu_cache_app_get_exec(MENU_CACHE_APP(l->data));
b5d1a361
GP
282 if (exec[0] == '/' && strncmp(exec, exec_bin, len) == 0 &&
283 (exec[len] == ' ' || exec[len] == 0))
dac88e35
GP
284 break;
285 }
286 }
59475d30 287 if (l)
007abf16 288 {
59475d30
AG
289 str_path = menu_cache_dir_make_path(MENU_CACHE_DIR(l->data));
290 path = fm_path_new_relative(fm_path_get_apps_menu(), str_path+13); /* skip /Applications */
291 g_free(str_path);
292 job = fm_file_info_job_new(NULL, FM_FILE_INFO_JOB_NONE);
293 fm_file_info_job_add(job, path);
294 fm_path_unref(path);
295 if (!fm_job_run_sync(FM_JOB(job)))
296 g_warning("launchtaskbar: problem running file info job");
297 else
298 fi = fm_file_info_list_pop_head(job->file_infos);
299 g_object_unref(job);
007abf16 300 }
59475d30
AG
301 g_slist_foreach(apps, (GFunc)menu_cache_item_unref, NULL);
302 g_slist_free(apps);
ecce04a0 303 menu_cache_unref(mc);
6951c204 304 g_debug("f_find_menu_launchbutton_recursive: search '%s' found=%d", exec_bin, (fi != NULL));
6951c204 305 return fi;
007abf16 306}
59475d30 307#endif
007abf16 308
56f19a82
GP
309/* Deallocate a LaunchButton. */
310static void launchbutton_free(LaunchButton * btn)
311{
6951c204
AG
312 if (btn->fi)
313 fm_file_info_unref(btn->fi);
314 if (btn->dd)
315 g_object_unref(btn->dd);
56f19a82
GP
316 g_free(btn);
317}
318
319/* Handler for "button-press-event" event from launchtaskbar button. */
320static gboolean launchbutton_press_event(GtkWidget * widget, GdkEventButton * event, LaunchButton * b)
321{
322 /* Standard right-click handling. */
6951c204 323 if (lxpanel_plugin_button_press_event(b->p->plugin, event, b->p->panel))
56f19a82
GP
324 return TRUE;
325
326 if (event->button == 1) /* left button */
327 {
6951c204
AG
328 if (b->fi == NULL) /* The bootstrap button */
329 lxpanel_plugin_show_config_dialog(b->p->panel, b->p->plugin);
330 else
331 lxpanel_launch_path(b->p->panel, fm_file_info_get_path(b->fi));
56f19a82
GP
332 }
333 return TRUE;
334}
335
6951c204
AG
336/* Handler for "drag-motion" event from launchtaskbar button. */
337static gboolean launchbutton_drag_motion_event(
56f19a82
GP
338 GtkWidget * widget,
339 GdkDragContext * context,
340 gint x,
341 gint y,
56f19a82
GP
342 guint time,
343 LaunchButton * b)
344{
6951c204
AG
345 GdkAtom target;
346 GdkDragAction action = 0;
56f19a82 347
6951c204
AG
348 fm_dnd_dest_set_dest_file(b->dd, b->fi);
349 target = fm_dnd_dest_find_target(b->dd, context);
350 if (target != GDK_NONE && fm_dnd_dest_is_target_supported(b->dd, target))
351 action = fm_dnd_dest_get_default_action(b->dd, context, target);
352 gdk_drag_status(context, action, time);
353 /* g_debug("launchbutton_drag_motion_event: act=%u",action); */
354 return (action != 0);
56f19a82
GP
355}
356
357/* Build the graphic elements for the bootstrap launchtaskbar button. */
6951c204 358static void launchbutton_build_bootstrap(LaunchTaskBarPlugin *lb)
56f19a82 359{
6951c204 360 if(lb->bootstrap_button == NULL)
56f19a82 361 {
6951c204 362 GdkPixbuf * icon;
56f19a82
GP
363 /* Build a button that has the stock "Add" icon.
364 * The "desktop-id" being NULL is the marker that this is the bootstrap button. */
6951c204
AG
365 lb->bootstrap_button = g_new0(LaunchButton, 1);
366 lb->bootstrap_button->p = lb;
56f19a82
GP
367
368 /* Create an event box. */
369 GtkWidget * event_box = gtk_event_box_new();
370 gtk_container_set_border_width(GTK_CONTAINER(event_box), 0);
6951c204 371#if GTK_CHECK_VERSION(2,18,0)
56f19a82
GP
372 gtk_widget_set_can_focus (event_box, FALSE);
373#else
374 GTK_WIDGET_UNSET_FLAGS(event_box, GTK_CAN_FOCUS);
375#endif
6951c204
AG
376 lb->bootstrap_button->widget = event_box;
377 g_signal_connect(event_box, "button-press-event", G_CALLBACK(launchbutton_press_event), lb->bootstrap_button);
56f19a82
GP
378
379 /* Create an image containing the stock "Add" icon as a child of the event box. */
6951c204
AG
380 lb->add_icon = fm_icon_from_name(GTK_STOCK_ADD);
381 icon = fm_pixbuf_from_icon(lb->add_icon, lb->icon_size);
382 lb->bootstrap_button->image_widget = gtk_image_new_from_pixbuf(icon);
383 g_object_unref(icon);
384 gtk_misc_set_padding(GTK_MISC(lb->bootstrap_button->image_widget), 0, 0);
385 gtk_misc_set_alignment(GTK_MISC(lb->bootstrap_button->image_widget), 0, 0);
386 gtk_container_add(GTK_CONTAINER(event_box), lb->bootstrap_button->image_widget);
56f19a82
GP
387
388 /* Add the bootstrap button to the icon grid. By policy it is empty at this point. */
6951c204 389 icon_grid_add(lb->lb_icon_grid, event_box, TRUE);
56f19a82
GP
390 }
391 else
6951c204 392 icon_grid_set_visible(lb->lb_icon_grid, lb->bootstrap_button->widget, TRUE);
56f19a82
GP
393}
394
59475d30 395#ifndef DISABLE_MENU
6951c204 396static LaunchButton *launchbar_exec_bin_exists(LaunchTaskBarPlugin *lb, FmFileInfo *fi)
d0ab2e8e 397{
6d740d5c 398 LaunchButton *ret_val = NULL;
6951c204 399 FmPath *path;
d0ab2e8e 400 GSList* l;
6951c204
AG
401
402 if (!fi)
403 return NULL;
404 path = fm_file_info_get_path(fi);
d0ab2e8e
GP
405 for(l = lb->buttons; l != NULL; l = l->next)
406 {
407 LaunchButton *btn = (LaunchButton *)l->data;
6951c204 408 if (btn->fi && fm_path_equal(path, fm_file_info_get_path(btn->fi)))
d0ab2e8e 409 {
6d740d5c 410 ret_val = btn;
d0ab2e8e
GP
411 break;
412 }
413 }
414 return ret_val;
415}
59475d30 416#endif
d0ab2e8e 417
7f782142 418static void launchbar_update_after_taskbar_class_added(LaunchTaskBarPlugin *ltbp, Task *tk)
56f19a82 419{
39c2d47e
GP
420 GPid pid = get_net_wm_pid(tk->win);
421 gchar exec_bin_full[128];
f6388511 422 f_get_exec_cmd_from_pid(pid, exec_bin_full, "cmdline");
39c2d47e
GP
423 gchar *p_char = strrchr(exec_bin_full, '/');
424 if(p_char == NULL) p_char = exec_bin_full;
425 else p_char++;
0c66391d 426 g_free(tk->exec_bin);
f6388511
GP
427 if(strcmp(p_char, "python") == 0)
428 {
429 f_get_exec_cmd_from_pid(pid, exec_bin_full, "comm");
f6388511 430 }
0c66391d 431 else
c68828af 432 {
0c66391d
AG
433 tk->exec_bin = g_key_file_get_string(ltbp->p_key_file_special_cases,
434 "special_cases", p_char, NULL);
435 if (tk->exec_bin != NULL) /* found this key */
436 return;
437 }
438 tk->exec_bin = g_strdup(exec_bin_full);
c68828af 439
8aff4845
GP
440#ifdef DEBUG_WITH_GPRINTS
441 if(ltbp->lb_on)
fe60665f 442 {
8aff4845
GP
443 FmFileInfo *fi = f_find_menu_launchbutton_recursive(tk->exec_bin);
444 LaunchButton *btn = launchbar_exec_bin_exists(ltbp, fi);
fe60665f
GP
445 g_print("\nTB '%s' OPEN (pid=%u), in LB: %c\n",
446 tk->exec_bin, pid, btn != NULL ? 'Y':'N');
ecce04a0
AG
447 if (fi)
448 fm_file_info_unref(fi);
8aff4845
GP
449 }
450#endif
56f19a82
GP
451}
452
9b3b96db 453static void launchbar_update_after_taskbar_class_removed(LaunchTaskBarPlugin *ltbp, Task *tk)
56f19a82 454{
8aff4845
GP
455#ifdef DEBUG_WITH_GPRINTS
456 if(ltbp->lb_on)
fe60665f 457 {
8aff4845
GP
458 FmFileInfo *fi = f_find_menu_launchbutton_recursive(tk->exec_bin);
459 LaunchButton *btn = launchbar_exec_bin_exists(ltbp, fi);
fe60665f 460 g_print("\nTB '%s' CLOSE, in LB: %c\n", tk->exec_bin, btn != NULL ? 'Y':'N');
ecce04a0
AG
461 if (fi)
462 fm_file_info_unref(fi);
8aff4845
GP
463 }
464#endif
56f19a82
GP
465}
466
467/* Build the graphic elements for a launchtaskbar button. The desktop_id field is already established. */
59475d30 468/* NOTE: this func consumes reference on fi */
6951c204 469static LaunchButton *launchbutton_for_file_info(LaunchTaskBarPlugin * lb, FmFileInfo * fi)
56f19a82 470{
6951c204
AG
471 LaunchButton *btn;
472 GtkWidget *button;
56f19a82 473
6951c204 474 if (fi == NULL)
56f19a82 475 {
6951c204
AG
476 g_warning("launchbar: desktop entry does not exist\n");
477 return NULL;
56f19a82
GP
478 }
479
6951c204
AG
480 /* Allocate the LaunchButton structure. */
481 btn = g_new0(LaunchButton, 1);
482 btn->p = lb;
483 btn->fi = fi;
484
56f19a82 485 /* Create a button with the specified icon. */
6951c204
AG
486 button = lxpanel_button_new_for_fm_icon(lb->panel, fm_file_info_get_icon(fi),
487 NULL, NULL);
56f19a82 488 btn->widget = button;
6951c204 489#if GTK_CHECK_VERSION(2,18,0)
56f19a82
GP
490 gtk_widget_set_can_focus(button, FALSE);
491#else
492 GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS);
493#endif
6951c204
AG
494
495 gtk_widget_set_tooltip_text(button, fm_file_info_get_disp_name(fi));
56f19a82
GP
496
497 /* Add the button to the icon grid. */
6951c204 498 icon_grid_add(lb->lb_icon_grid, button, TRUE);
56f19a82
GP
499
500 /* Drag and drop support. */
6951c204 501 btn->dd = fm_dnd_dest_new_with_handlers(button);
56f19a82
GP
502
503 /* Connect signals. */
504 g_signal_connect(button, "button-press-event", G_CALLBACK(launchbutton_press_event), (gpointer) btn);
6951c204 505 g_signal_connect(button, "drag-motion", G_CALLBACK(launchbutton_drag_motion_event), btn);
56f19a82
GP
506
507 /* If the list goes from null to non-null, remove the bootstrap button. */
6951c204
AG
508 if ((lb->buttons == NULL) && (lb->bootstrap_button != NULL))
509 icon_grid_set_visible(lb->lb_icon_grid, lb->bootstrap_button->widget, FALSE);
56f19a82
GP
510
511 /* Append at end of list to preserve configured order. */
6951c204 512 lb->buttons = g_slist_append(lb->buttons, btn);
56f19a82
GP
513
514 /* Show the widget and return. */
515 gtk_widget_show(button);
6951c204
AG
516 plugin_widget_set_background(button, lb->panel);
517 return btn;
56f19a82
GP
518}
519
6951c204 520static LaunchButton *launchbutton_build_gui(LaunchTaskBarPlugin * lb, FmPath * id)
56f19a82 521{
6951c204
AG
522 /* Try to get the file data */
523 FmFileInfoJob *job = fm_file_info_job_new(NULL, FM_FILE_INFO_JOB_NONE);
524 FmFileInfo *fi;
56f19a82 525
6951c204
AG
526 fm_file_info_job_add(job, id);
527 if (!fm_job_run_sync(FM_JOB(job)))
56f19a82 528 {
6951c204
AG
529 g_warning("launchbar: problem running file info job\n");
530 g_object_unref(job);
531 return NULL;
56f19a82 532 }
6951c204
AG
533 fi = fm_file_info_list_pop_head(job->file_infos);
534 g_object_unref(job);
535 return launchbutton_for_file_info(lb, fi);
536}
537
538static LaunchButton *launchbutton_search_and_build_gui(LaunchTaskBarPlugin * lb, FmPath * id)
539{
540 FmDirListJob *job = fm_dir_list_job_new2(id, FM_DIR_LIST_JOB_FAST);
541 FmFileInfo *fi;
542
543 if (!fm_job_run_sync(FM_JOB(job)))
544 {
545 g_warning("launchbar: problem running file search job\n");
546 g_object_unref(job);
547 return NULL;
548 }
549 fi = fm_file_info_list_pop_head(job->files);
550 g_object_unref(job);
551 return launchbutton_for_file_info(lb, fi);
552}
553
554/* Read the configuration file entry for a launchtaskbar button and create it. */
555static gboolean launchbutton_constructor(LaunchTaskBarPlugin * lb, config_setting_t * s)
556{
aeba40be 557 LaunchButton *btn = NULL;
6951c204
AG
558 const char *str;
559 char *str_path = NULL;
560 FmPath *path;
561
562 /* Read parameters from the configuration file and validate. */
563 if (!config_setting_lookup_string(s, "id", &str) || str[0] == '\0')
564 return FALSE;
56f19a82
GP
565
566 /* Build the structures and return. */
aeba40be 567 if (str[0] == '~')
f6378007 568 {
aeba40be
AG
569 str_path = expand_tilda(str);
570 path = fm_path_new_for_path(str_path);
6951c204 571 btn = launchbutton_build_gui(lb, path);
f6378007 572 }
aeba40be 573 else if (strchr(str, '/') != NULL)
6951c204 574 {
aeba40be
AG
575 path = fm_path_new_for_str(str);
576 /* FIXME: check if str contains invalid path */
6951c204
AG
577 btn = launchbutton_build_gui(lb, path);
578 }
579 else
580 {
581 str_path = g_strdup_printf("search://menu://applications/?recursive=1&show_hidden=1&name=%s", str);
aeba40be 582 path = fm_path_new_for_uri(str_path);
6951c204
AG
583 btn = launchbutton_search_and_build_gui(lb, path);
584 }
585 g_free(str_path);
586 fm_path_unref(path);
587 if (btn)
588 btn->settings = s;
589 return (btn != NULL);
56f19a82
GP
590}
591
6951c204
AG
592/* prototype of this is app_info_create_from_commandline() in libfm */
593static gboolean _launchbutton_create_id(LaunchTaskBarPlugin * lb, config_setting_t * s)
aedcb93d 594{
6951c204
AG
595 const char *icon = NULL, *name, *exec, *path = NULL;
596 char *dirname, *filename;
597 int fd, terminal = 0;
598 gboolean ret = FALSE;
599
600 if (!config_setting_lookup_string(s, "action", &exec) || exec[0] == '\0')
601 return FALSE;
602 if (!config_setting_lookup_string(s, "tooltip", &name) || name[0] == '\0')
603 name = "Launcher"; /* placeholder, XDG requires a non-empty name */
604 config_setting_lookup_string(s, "image", &icon);
605 config_setting_lookup_string(s, "path", &path);
606 config_setting_lookup_int(s, "terminal", &terminal);
607
608 dirname = g_build_filename(g_get_user_data_dir(), "applications", NULL);
609 if (g_mkdir_with_parents(dirname, 0700) == 0)
610 {
611 filename = g_strdup_printf("%s/lxpanel-launcher-XXXXXX.desktop", dirname);
612 fd = g_mkstemp (filename);
613 if (fd != -1)
614 {
615 GString* content = g_string_sized_new(256);
616
617 g_string_printf(content,
618 "[" G_KEY_FILE_DESKTOP_GROUP "]\n"
619 G_KEY_FILE_DESKTOP_KEY_TYPE "=" G_KEY_FILE_DESKTOP_TYPE_APPLICATION "\n"
620 G_KEY_FILE_DESKTOP_KEY_NAME "=%s\n"
621 G_KEY_FILE_DESKTOP_KEY_EXEC "=%s\n"
622 G_KEY_FILE_DESKTOP_KEY_CATEGORIES "=X-LXPanel;\n",
623 name, exec);
624 if (icon)
625 g_string_append_printf(content, "Icon=%s\n", icon);
626 if (terminal)
627 g_string_append(content, G_KEY_FILE_DESKTOP_KEY_TERMINAL "=true\n");
628 if (path && path[0] == '/')
629 g_string_append_printf(content, "Path=%s\n", path);
630 close(fd);
631 ret = g_file_set_contents(filename, content->str, content->len, NULL);
632 if (ret) {
633 config_group_set_string(s, "id", filename);
634 /* FIXME: is it reasonable to remove obsolete keys too? */
635 panel_config_save(lb->panel);
636 } else
637 g_unlink(filename);
638 g_string_free(content, TRUE);
639 }
640 g_free(filename);
641 }
642 g_free(dirname);
643 if (ret) /* we created it, let use it */
644 return launchbutton_constructor(lb, s);
645 return FALSE;
aedcb93d
GP
646}
647
0c66391d 648static void launchtaskbar_constructor_add_default_special_case(LaunchTaskBarPlugin *ltbp, const gchar *tk_exec, const gchar *mb_exec)
6951c204
AG
649{
650 g_key_file_set_value(ltbp->p_key_file_special_cases, "special_cases", tk_exec, mb_exec);
0c66391d 651}
6951c204 652
fe60665f 653static void launchtaskbar_constructor_launch(LaunchTaskBarPlugin *ltbp, gboolean build_bootstrap)
56f19a82 654{
6951c204
AG
655 config_setting_t *settings;
656
0c5430a3 657 if(!ltbp->lb_built)
56f19a82 658 {
0c5430a3 659 ltbp->lb_built = TRUE;
6951c204 660 if(ltbp->lb_icon_grid == NULL)
0c5430a3
GP
661 {
662 /* Allocate an icon grid manager to manage the container. */
6951c204
AG
663 ltbp->lb_icon_grid = icon_grid_new(ltbp->panel, ltbp->p_evbox_launchbar,
664 panel_get_orientation(ltbp->panel),
665 ltbp->icon_size, ltbp->icon_size,
666 3, 0,
667 panel_get_height(ltbp->panel));
668 }
669 /* Read parameters from the configuration file. */
670 settings = config_setting_get_member(ltbp->settings, "");
671 if (settings && config_setting_is_list(settings))
672 {
673 config_setting_t *s;
674 guint i;
675
676 for (i = 0; (s = config_setting_get_elem(settings, i)) != NULL; )
677 {
678 if (strcmp(config_setting_get_name(s), "Button") != 0)
679 {
680 g_warning("launchtaskbar: illegal token %s\n", config_setting_get_name(s));
681 config_setting_destroy(s);
682 }
683 else if (!launchbutton_constructor(ltbp, s) &&
684 /* try to create desktop id from old-style manual setup */
685 !_launchbutton_create_id(ltbp, s))
686 {
687 g_warning( "launchtaskbar: can't init button\n");
688 /* FIXME: show failed id to the user instead */
689 config_setting_destroy(s);
690 }
691 else /* success, accept the setting */
692 i++;
693 }
0c5430a3 694 }
0c5430a3
GP
695 if(build_bootstrap)
696 {
6951c204
AG
697 if(ltbp->buttons == NULL)
698 launchbutton_build_bootstrap(ltbp);
0c5430a3 699 }
3297780c 700 }
0c5430a3 701 else
fe60665f 702 {
0c5430a3 703 gtk_widget_set_visible(ltbp->p_evbox_launchbar, TRUE);
fe60665f 704 }
3297780c
GP
705}
706
fe60665f 707static void launchtaskbar_constructor_task(LaunchTaskBarPlugin *ltbp)
3297780c 708{
0c5430a3
GP
709 if(!ltbp->tb_built)
710 {
6951c204
AG
711 config_setting_t *s = ltbp->settings;
712 gint tmp_int;
713
0c5430a3 714 ltbp->tb_built = TRUE;
6951c204
AG
715
716 /* Parse configuration now */
717 if (config_setting_lookup_int(s, "tooltips", &tmp_int))
718 ltbp->tooltips = (tmp_int != 0);
719 if (config_setting_lookup_int(s, "IconsOnly", &tmp_int))
720 ltbp->icons_only = (tmp_int != 0);
721 if (config_setting_lookup_int(s, "ShowAllDesks", &tmp_int))
722 ltbp->show_all_desks = (tmp_int != 0);
723 if (config_setting_lookup_int(s, "SameMonitorOnly", &tmp_int))
724 ltbp->same_monitor_only = (tmp_int != 0);
725 config_setting_lookup_int(s, "MaxTaskWidth", &ltbp->task_width_max);
726 config_setting_lookup_int(s, "spacing", &ltbp->spacing);
727 if (config_setting_lookup_int(s, "UseMouseWheel", &tmp_int))
728 ltbp->use_mouse_wheel = (tmp_int != 0);
729 if (config_setting_lookup_int(s, "UseUrgencyHint", &tmp_int))
730 ltbp->use_urgency_hint = (tmp_int != 0);
731 if (config_setting_lookup_int(s, "FlatButton", &tmp_int))
732 ltbp->flat_button = (tmp_int != 0);
733 if (config_setting_lookup_int(s, "GroupedTasks", &tmp_int))
734 ltbp->grouped_tasks = (tmp_int != 0);
735
0c5430a3 736 /* Make container for task buttons as a child of top level widget. */
6951c204
AG
737 ltbp->tb_icon_grid = icon_grid_new(ltbp->panel, ltbp->p_evbox_taskbar,
738 panel_get_orientation(ltbp->panel),
739 ltbp->task_width_max, ltbp->icon_size,
740 ltbp->spacing, 0,
741 panel_get_height(ltbp->panel));
742 icon_grid_set_constrain_width(ltbp->tb_icon_grid, TRUE);
743 taskbar_update_style(ltbp);
0c5430a3
GP
744
745 /* Add GDK event filter. */
6951c204 746 gdk_window_add_filter(NULL, (GdkFilterFunc) taskbar_event_filter, ltbp);
0c5430a3
GP
747
748 /* Connect signals to receive root window events and initialize root window properties. */
6951c204
AG
749 ltbp->number_of_desktops = get_net_number_of_desktops();
750 ltbp->current_desktop = get_net_current_desktop();
751 g_signal_connect(G_OBJECT(fbev), "current-desktop", G_CALLBACK(taskbar_net_current_desktop), (gpointer) ltbp);
752 g_signal_connect(G_OBJECT(fbev), "active-window", G_CALLBACK(taskbar_net_active_window), (gpointer) ltbp);
753 g_signal_connect(G_OBJECT(fbev), "number-of-desktops", G_CALLBACK(taskbar_net_number_of_desktops), (gpointer) ltbp);
754 g_signal_connect(G_OBJECT(fbev), "client-list", G_CALLBACK(taskbar_net_client_list), (gpointer) ltbp);
0c5430a3
GP
755
756 /* Make right-click menu for task buttons.
757 * It is retained for the life of the taskbar and will be shown as needed.
758 * Number of desktops and edge is needed for this operation. */
6951c204 759 taskbar_make_menu(ltbp);
0c5430a3
GP
760
761 /* Connect a signal to be notified when the window manager changes. This causes re-evaluation of the "use_net_active" status. */
6951c204 762 g_signal_connect(gtk_widget_get_screen(ltbp->p_evbox_taskbar), "window-manager-changed", G_CALLBACK(taskbar_window_manager_changed), ltbp);
0c5430a3
GP
763
764 /* Fetch the client list and redraw the taskbar. Then determine what window has focus. */
6951c204
AG
765 taskbar_net_client_list(NULL, ltbp);
766 taskbar_net_active_window(NULL, ltbp);
0c5430a3
GP
767 }
768 else
769 {
770 gtk_widget_set_visible(ltbp->p_evbox_taskbar, TRUE);
771 }
3297780c
GP
772}
773
774/* Plugin constructor. */
6951c204 775static GtkWidget *launchtaskbar_constructor(Panel *panel, config_setting_t *settings)
3297780c 776{
6951c204
AG
777 GtkWidget *p;
778 LaunchTaskBarPlugin *ltbp;
779
3297780c
GP
780 gtk_rc_parse_string(launchtaskbar_rc);
781
782 /* Allocate plugin context and set into Plugin private data pointer. */
6951c204
AG
783 ltbp = g_new0(LaunchTaskBarPlugin, 1);
784 ltbp->panel = panel;
785 ltbp->settings = settings;
3297780c
GP
786
787 /* Initialize to defaults. */
6951c204
AG
788 ltbp->icon_size = panel_get_icon_size(panel);
789 ltbp->tooltips = TRUE;
790 ltbp->icons_only = FALSE;
791 ltbp->show_all_desks = TRUE;
792 ltbp->task_width_max = TASK_WIDTH_MAX;
793 ltbp->spacing = 1;
794 ltbp->use_mouse_wheel = TRUE;
795 ltbp->use_urgency_hint = TRUE;
796 ltbp->grouped_tasks = FALSE;
3297780c
GP
797
798 /* Special cases key file */
0c66391d 799 ltbp->p_key_file_special_cases = g_key_file_new();
3297780c
GP
800 gchar *special_cases_filepath = g_build_filename(g_get_user_config_dir(),
801 "lxpanel", "launchtaskbar.cfg", NULL);
0c66391d
AG
802 if (!g_key_file_load_from_file(ltbp->p_key_file_special_cases,
803 special_cases_filepath,
804 G_KEY_FILE_KEEP_COMMENTS, NULL))
3297780c
GP
805 {
806 launchtaskbar_constructor_add_default_special_case(ltbp, "synaptic", "synaptic-pkexec");
807 launchtaskbar_constructor_add_default_special_case(ltbp, "soffice.bin", "libreoffice");
808 launchtaskbar_constructor_add_default_special_case(ltbp, "x-terminal-emulator", "lxterminal");
809 gchar *key_file_data = g_key_file_to_data(ltbp->p_key_file_special_cases, NULL, NULL);
810 g_file_set_contents(special_cases_filepath, key_file_data, -1, NULL);
811 g_free(key_file_data);
812 }
0c66391d 813 g_free(special_cases_filepath);
3297780c
GP
814
815 /* Allocate top level widget and set into Plugin widget pointer. */
6951c204
AG
816 ltbp->plugin = p = panel_box_new(panel, FALSE, 5);
817 lxpanel_plugin_set_data(p, ltbp, launchtaskbar_destructor);
3297780c
GP
818 ltbp->p_evbox_launchbar = gtk_event_box_new();
819 ltbp->p_evbox_taskbar = gtk_event_box_new();
6951c204
AG
820 gtk_box_pack_start(GTK_BOX(p), ltbp->p_evbox_launchbar, FALSE, TRUE, 0);
821 gtk_box_pack_start(GTK_BOX(p), ltbp->p_evbox_taskbar, TRUE, TRUE, 0);
822
823 gtk_container_set_border_width(GTK_CONTAINER(p), 0);
3297780c
GP
824 gtk_container_set_border_width(GTK_CONTAINER(ltbp->p_evbox_launchbar), 0);
825 gtk_container_set_border_width(GTK_CONTAINER(ltbp->p_evbox_taskbar), 0);
6951c204
AG
826#if GTK_CHECK_VERSION(2,18,0)
827 gtk_widget_set_has_window(p, FALSE);
3297780c 828#else
6951c204 829 GTK_WIDGET_SET_FLAGS(p, GTK_NO_WINDOW);
3297780c
GP
830#endif
831
6951c204
AG
832 /* Read parameters from the configuration file. */
833 config_setting_lookup_int(settings, "LaunchBarON", &ltbp->lb_on);
0850d470
AG
834 config_setting_lookup_int(settings, "TaskBarON", &ltbp->tb_on);
835 if (!ltbp->lb_on && !ltbp->tb_on) /* both off is illegal, assume both on */
836 ltbp->lb_on = ltbp->tb_on = TRUE;
b96b69ca 837
fe60665f
GP
838 if(ltbp->lb_on) launchtaskbar_constructor_launch(ltbp, TRUE/*build_bootstrap*/);
839 if(ltbp->tb_on) launchtaskbar_constructor_task(ltbp);
3297780c 840
443a4c75
AG
841 if (!ltbp->lb_on)
842 gtk_widget_set_name(p, "taskbar");
843 else if (!ltbp->tb_on)
844 gtk_widget_set_name(p, "launchbar");
845 else
846 gtk_widget_set_name(p, "launchtaskbar");
847
6951c204 848 return p;
56f19a82
GP
849}
850
e9d877a9 851static void launchtaskbar_destructor_launch(LaunchTaskBarPlugin *ltbp)
56f19a82 852{
e9d877a9 853 /* Free the launchbar. */
6951c204
AG
854 g_slist_foreach(ltbp->buttons, (GFunc) launchbutton_free, NULL);
855 icon_grid_free(ltbp->lb_icon_grid);
856 ltbp->lb_icon_grid = NULL;
56f19a82 857
e9d877a9 858 /* Free the bootstrap button if it exists. */
6951c204 859 if(ltbp->bootstrap_button != NULL)
fe60665f 860 {
6951c204
AG
861 launchbutton_free(ltbp->bootstrap_button);
862 ltbp->bootstrap_button = NULL;
fe60665f 863 }
6951c204
AG
864
865 if (ltbp->add_icon != NULL)
866 g_object_unref(ltbp->add_icon);
e9d877a9
GP
867}
868
869static void launchtaskbar_destructor_task(LaunchTaskBarPlugin *ltbp)
870{
56f19a82 871 /* Remove GDK event filter. */
6951c204 872 gdk_window_remove_filter(NULL, (GdkFilterFunc) taskbar_event_filter, ltbp);
56f19a82
GP
873
874 /* Remove root window signal handlers. */
6951c204
AG
875 g_signal_handlers_disconnect_by_func(fbev, taskbar_net_current_desktop, ltbp);
876 g_signal_handlers_disconnect_by_func(fbev, taskbar_net_active_window, ltbp);
877 g_signal_handlers_disconnect_by_func(fbev, taskbar_net_number_of_desktops, ltbp);
878 g_signal_handlers_disconnect_by_func(fbev, taskbar_net_client_list, ltbp);
56f19a82
GP
879
880 /* Remove "window-manager-changed" handler. */
6951c204 881 g_signal_handlers_disconnect_by_func(gtk_widget_get_screen(ltbp->p_evbox_taskbar), taskbar_window_manager_changed, ltbp);
56f19a82
GP
882
883 /* Deallocate task list. */
6951c204
AG
884 while(ltbp->p_task_list != NULL)
885 task_delete(ltbp, ltbp->p_task_list, TRUE);
56f19a82
GP
886
887 /* Deallocate class list. */
6951c204 888 while(ltbp->p_taskclass_list != NULL)
56f19a82 889 {
6951c204
AG
890 TaskClass * tc = ltbp->p_taskclass_list;
891 ltbp->p_taskclass_list = tc->p_taskclass_flink;
56f19a82
GP
892 g_free(tc->res_class);
893 g_free(tc);
894 }
895
896 /* Deallocate other memory. */
6951c204
AG
897 gtk_widget_destroy(ltbp->menu);
898 icon_grid_free(ltbp->tb_icon_grid);
e9d877a9 899}
56f19a82 900
e9d877a9 901/* Plugin destructor. */
6951c204 902static void launchtaskbar_destructor(gpointer user_data)
e9d877a9 903{
6951c204 904 LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)user_data;
56f19a82 905
e9d877a9 906 // TASKBAR
0c5430a3 907 if(ltbp->tb_built) launchtaskbar_destructor_task(ltbp);
56f19a82 908
e9d877a9 909 // LAUNCHBAR
0c5430a3 910 if(ltbp->lb_built) launchtaskbar_destructor_launch(ltbp);
56f19a82 911
e9d877a9 912 // LAUNCHTASKBAR
56f19a82
GP
913
914 /* Deallocate all memory. */
0c66391d
AG
915 if (ltbp->p_key_file_special_cases != NULL)
916 g_key_file_free(ltbp->p_key_file_special_cases);
7f782142 917 g_free(ltbp);
56f19a82
GP
918}
919
33cdf34d 920static void _launchbar_configure_add(GtkTreeView *menu_view, LaunchTaskBarPlugin *ltbp)
56f19a82 921{
e9d877a9 922 GtkTreeView * defined_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(ltbp->config_dlg), "defined_view"));
6951c204
AG
923 FmPath * sel_path = fm_app_menu_view_dup_selected_app_desktop_path(menu_view);
924 LaunchButton * btn;
7bc7e932 925
6951c204
AG
926 if (sel_path != NULL && (btn = launchbutton_build_gui(ltbp, sel_path)) != NULL)
927 {
56f19a82
GP
928 GtkListStore * list = GTK_LIST_STORE(gtk_tree_view_get_model(defined_view));
929 GtkTreeIter it;
930 GdkPixbuf* pix;
6951c204 931 char *path;
56f19a82 932 gtk_list_store_append(list, &it);
6951c204 933 pix = fm_pixbuf_from_icon(fm_file_info_get_icon(btn->fi), PANEL_ICON_SIZE);
56f19a82
GP
934 gtk_list_store_set(list, &it,
935 COL_ICON, pix,
6951c204
AG
936 COL_TITLE, fm_file_info_get_disp_name(btn->fi),
937 COL_BTN, btn,
56f19a82
GP
938 -1);
939 g_object_unref(pix);
6951c204
AG
940 path = fm_path_to_str(sel_path);
941 /* g_debug("*** path '%s'",path); */
942 btn->settings = config_group_add_subgroup(ltbp->settings, "Button");
943 config_group_set_string(btn->settings, "id", path);
944 g_free(path);
945 fm_path_unref(sel_path);
56f19a82
GP
946 }
947}
948
33cdf34d
AG
949/* Handler for "clicked" action on launchtaskbar configuration dialog "Add" button. */
950static void launchbar_configure_add_button(GtkButton * widget, LaunchTaskBarPlugin *ltbp)
951{
952 GtkTreeView * menu_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(ltbp->config_dlg), "menu_view"));
953
954 _launchbar_configure_add(menu_view, ltbp);
955}
956
6d740d5c
GP
957static void launchbar_remove_button(LaunchTaskBarPlugin *ltbp, LaunchButton *btn)
958{
6951c204
AG
959 icon_grid_remove(ltbp->lb_icon_grid, btn->widget);
960 ltbp->buttons = g_slist_remove(ltbp->buttons, btn);
961 config_setting_destroy(btn->settings);
6d740d5c
GP
962 launchbutton_free(btn);
963 /* Put the bootstrap button back if the list becomes empty. */
6951c204
AG
964 if(ltbp->buttons == NULL)
965 launchbutton_build_bootstrap(ltbp);
6d740d5c
GP
966}
967
56f19a82 968/* Handler for "clicked" action on launchtaskbar configuration dialog "Remove" button. */
6951c204 969static void launchbar_configure_remove_button(GtkButton * widget, LaunchTaskBarPlugin *ltbp)
56f19a82 970{
e9d877a9 971 GtkTreeView * defined_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(ltbp->config_dlg), "defined_view"));
56f19a82
GP
972 GtkTreeModel * list;
973 GtkTreeIter it;
974 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(defined_view), &list, &it))
975 {
976 LaunchButton * btn;
977 gtk_tree_model_get(list, &it, COL_BTN, &btn, -1);
978
979 /* We have found a selected button.
980 * Remove it from the icon grid, the data structure, and the view. */
981 gtk_list_store_remove(GTK_LIST_STORE(list), &it);
6951c204 982 gtk_widget_set_visible(ltbp->p_label_def_app_exec, FALSE);
56f19a82 983
6d740d5c 984 launchbar_remove_button(ltbp, btn);
56f19a82
GP
985 }
986}
987
988/* Handler for "clicked" action on launchtaskbar configuration dialog "Move Up" button. */
6951c204 989static void launchbar_configure_move_up_button(GtkButton * widget, LaunchTaskBarPlugin *ltbp)
56f19a82 990{
e9d877a9 991 GtkTreeView * defined_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(ltbp->config_dlg), "defined_view"));
56f19a82
GP
992 GtkTreeModel * list;
993 GtkTreeIter it;
994 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(defined_view), &list, &it))
995 {
996 LaunchButton *btn;
997 gtk_tree_model_get(GTK_TREE_MODEL(list), &it, COL_BTN, &btn, -1);
998 GtkTreePath * path = gtk_tree_model_get_path(GTK_TREE_MODEL(list), &it);
999 if ((gtk_tree_path_get_indices(path)[0] > 0)
1000 && (gtk_tree_path_prev(path)))
1001 {
1002 GtkTreeIter it2;
1003 if (gtk_tree_model_get_iter(list, &it2, path))
1004 {
1005 /* We have found a selected button that can be moved.
1006 * Reorder it in the icon grid, the data structure, and the view. */
1007 int i = gtk_tree_path_get_indices(path)[0];
6951c204
AG
1008 ltbp->buttons = g_slist_remove(ltbp->buttons, btn);
1009 ltbp->buttons = g_slist_insert(ltbp->buttons, btn, i);
56f19a82 1010 gtk_list_store_move_before(GTK_LIST_STORE(list), &it, &it2);
6951c204
AG
1011 icon_grid_reorder_child(ltbp->lb_icon_grid, btn->widget, i);
1012 config_setting_move_elem(btn->settings,
1013 config_setting_get_parent(btn->settings),
1014 i);
56f19a82
GP
1015 }
1016 }
1017 gtk_tree_path_free(path);
1018 }
1019}
1020
1021/* Handler for "clicked" action on launchtaskbar configuration dialog "Move Down" button. */
6951c204 1022static void launchbar_configure_move_down_button(GtkButton * widget, LaunchTaskBarPlugin *ltbp)
56f19a82 1023{
e9d877a9 1024 GtkTreeView * defined_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(ltbp->config_dlg), "defined_view"));
56f19a82
GP
1025 GtkTreeModel * list;
1026 GtkTreeIter it;
1027 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(defined_view), &list, &it))
1028 {
1029 LaunchButton *btn;
1030 gtk_tree_model_get(GTK_TREE_MODEL(list), &it, COL_BTN, &btn, -1);
1031 GtkTreePath * path = gtk_tree_model_get_path(GTK_TREE_MODEL(list), &it);
1032 int n = gtk_tree_model_iter_n_children(list, NULL);
1033 if (gtk_tree_path_get_indices(path)[0] < (n - 1))
1034 {
1035 gtk_tree_path_next(path);
1036 GtkTreeIter it2;
1037 if (gtk_tree_model_get_iter( list, &it2, path))
1038 {
1039 /* We have found a selected button that can be moved.
1040 * Reorder it in the icon grid, the data structure, and the view. */
1041 int i = gtk_tree_path_get_indices(path)[0];
6951c204
AG
1042 ltbp->buttons = g_slist_remove(ltbp->buttons, btn);
1043 ltbp->buttons = g_slist_insert(ltbp->buttons, btn, i + 1);
56f19a82 1044 gtk_list_store_move_after(GTK_LIST_STORE(list), &it, &it2);
6951c204
AG
1045 icon_grid_reorder_child( ltbp->lb_icon_grid, btn->widget, i);
1046 config_setting_move_elem(btn->settings,
1047 config_setting_get_parent(btn->settings),
1048 i);
56f19a82
GP
1049 }
1050 }
1051 gtk_tree_path_free(path);
1052 }
1053}
1054
56f19a82 1055/* Initialize the list of existing launchtaskbar buttons when the configuration dialog is shown. */
6951c204 1056static void launchbar_configure_initialize_list(LaunchTaskBarPlugin *ltbp, GtkWidget * dlg, GtkTreeView * view)
56f19a82 1057{
56f19a82
GP
1058 /* Set the selection mode. */
1059 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(view), GTK_SELECTION_BROWSE);
1060
1061 /* Define a column. */
1062 GtkTreeViewColumn* col = gtk_tree_view_get_column(view, 0);
1063
1064 /* Establish the pixbuf column cell renderer. */
1065 GtkCellRenderer * render = gtk_cell_renderer_pixbuf_new();
1066 gtk_tree_view_column_pack_start(col, render, FALSE);
1067 gtk_tree_view_column_set_attributes(col, render, "pixbuf", COL_ICON, NULL);
1068
1069 /* Establish the text column cell renderer. */
1070 render = gtk_cell_renderer_text_new();
1071 gtk_tree_view_column_pack_start(col, render, TRUE);
1072 gtk_tree_view_column_add_attribute(col, render, "text", COL_TITLE);
1073
6951c204
AG
1074 /* Establish the column data types. */
1075 GtkListStore* list = GTK_LIST_STORE(gtk_tree_view_get_model(view));
56f19a82 1076
6951c204
AG
1077 /* Initialize from defined launchtaskbar buttons. */
1078 GSList* l;
1079 for (l = ltbp->buttons; l != NULL; l = l->next)
56f19a82 1080 {
6951c204
AG
1081 LaunchButton * btn = (LaunchButton *) l->data;
1082 GdkPixbuf * pix;
1083 GtkTreeIter it;
1084 gtk_list_store_append(list, &it);
1085 pix = fm_pixbuf_from_icon(fm_file_info_get_icon(btn->fi), PANEL_ICON_SIZE);
1086 gtk_list_store_set(list, &it,
1087 COL_ICON, pix,
1088 COL_TITLE, fm_file_info_get_disp_name(btn->fi),
1089 COL_BTN, btn,
1090 -1);
1091 g_object_unref(pix);
56f19a82 1092 }
6951c204 1093 g_object_set_data(G_OBJECT(dlg), "defined_view", view);
56f19a82
GP
1094}
1095
f1fd6957
GP
1096static void plugin_set_expand_status(LaunchTaskBarPlugin *ltbp, gboolean expand_new)
1097{
f1fd6957
GP
1098 gboolean old_expand, fill;
1099 guint padding;
1100 GtkPackType pack_type;
6951c204
AG
1101 GtkWidget *box = gtk_widget_get_parent(ltbp->plugin);
1102 g_return_if_fail(box);
1103 gtk_box_query_child_packing(GTK_BOX(box), ltbp->plugin, &old_expand, &fill, &padding, &pack_type);
1104 gtk_box_set_child_packing(GTK_BOX(box), ltbp->plugin, expand_new, fill, padding, pack_type);
f1fd6957
GP
1105}
1106
b96b69ca
GP
1107static void on_radiobutton_launch_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
1108{
1109 if(!gtk_toggle_button_get_active(p_togglebutton)) return;
1110 LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
6951c204 1111
fe60665f
GP
1112 if(!ltbp->lb_on)
1113 {
1114 ltbp->lb_on = TRUE;
1115 launchtaskbar_constructor_launch(ltbp, TRUE/*build_bootstrap*/);
0850d470 1116 config_group_set_int(ltbp->settings, "LaunchBarON", 1);
fe60665f
GP
1117 }
1118 if(ltbp->tb_on)
1119 {
1120 ltbp->tb_on = FALSE;
0c5430a3 1121 gtk_widget_set_visible(ltbp->p_evbox_taskbar, FALSE);
0850d470 1122 config_group_set_int(ltbp->settings, "TaskBarON", 0);
fe60665f 1123 }
b96b69ca
GP
1124 gtk_widget_set_visible(ltbp->p_notebook_page_launch, TRUE);
1125 gtk_widget_set_visible(ltbp->p_notebook_page_task, FALSE);
6951c204 1126
f1fd6957 1127 plugin_set_expand_status(ltbp, FALSE);
443a4c75 1128 gtk_widget_set_name(ltbp->plugin, "launchbar");
b96b69ca
GP
1129}
1130
1131static void on_radiobutton_task_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
1132{
1133 if(!gtk_toggle_button_get_active(p_togglebutton)) return;
1134 LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
6951c204 1135
fe60665f
GP
1136 if(ltbp->lb_on)
1137 {
1138 ltbp->lb_on = FALSE;
0c5430a3 1139 gtk_widget_set_visible(ltbp->p_evbox_launchbar, FALSE);
0850d470 1140 config_group_set_int(ltbp->settings, "LaunchBarON", 0);
fe60665f
GP
1141 }
1142 if(!ltbp->tb_on)
1143 {
1144 ltbp->tb_on = TRUE;
1145 launchtaskbar_constructor_task(ltbp);
0850d470 1146 config_group_set_int(ltbp->settings, "TaskBarON", 1);
fe60665f 1147 }
b96b69ca
GP
1148 gtk_widget_set_visible(ltbp->p_notebook_page_launch, FALSE);
1149 gtk_widget_set_visible(ltbp->p_notebook_page_task, TRUE);
6951c204 1150
f1fd6957 1151 plugin_set_expand_status(ltbp, TRUE);
443a4c75 1152 gtk_widget_set_name(ltbp->plugin, "taskbar");
b96b69ca
GP
1153}
1154
1155static void on_radiobutton_launchtask_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
1156{
1157 if(!gtk_toggle_button_get_active(p_togglebutton)) return;
1158 LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
6951c204 1159
fe60665f
GP
1160 if(!ltbp->lb_on)
1161 {
1162 ltbp->lb_on = TRUE;
1163 launchtaskbar_constructor_launch(ltbp, TRUE/*build_bootstrap*/);
0850d470 1164 config_group_set_int(ltbp->settings, "LaunchBarON", 1);
fe60665f
GP
1165 }
1166 if(!ltbp->tb_on)
1167 {
1168 ltbp->tb_on = TRUE;
1169 launchtaskbar_constructor_task(ltbp);
0850d470 1170 config_group_set_int(ltbp->settings, "TaskBarON", 1);
fe60665f 1171 }
b96b69ca
GP
1172 gtk_widget_set_visible(ltbp->p_notebook_page_launch, TRUE);
1173 gtk_widget_set_visible(ltbp->p_notebook_page_task, TRUE);
6951c204 1174
f1fd6957 1175 plugin_set_expand_status(ltbp, TRUE);
443a4c75 1176 gtk_widget_set_name(ltbp->plugin, "launchtaskbar");
b96b69ca
GP
1177}
1178
56f19a82
GP
1179static void on_checkbutton_show_tooltips_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
1180{
6951c204
AG
1181 LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
1182 ltbp->tooltips = gtk_toggle_button_get_active(p_togglebutton);
1183 //g_print("\nltbp->tooltips upd\n");
1184 config_group_set_int(ltbp->settings, "tooltips", ltbp->tooltips);
1185 taskbar_apply_configuration(ltbp);
56f19a82
GP
1186}
1187
1188static void on_checkbutton_icons_only_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
1189{
6951c204
AG
1190 LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
1191 ltbp->icons_only = gtk_toggle_button_get_active(p_togglebutton);
56f19a82 1192 //g_print("\ntb->icons_only upd\n");
6951c204
AG
1193 config_group_set_int(ltbp->settings, "IconsOnly", ltbp->icons_only);
1194 taskbar_apply_configuration(ltbp);
56f19a82
GP
1195}
1196
1197static void on_checkbutton_flat_buttons_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
1198{
6951c204
AG
1199 LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
1200 ltbp->flat_button = gtk_toggle_button_get_active(p_togglebutton);
56f19a82 1201 //g_print("\ntb->flat_button upd\n");
6951c204
AG
1202 config_group_set_int(ltbp->settings, "FlatButton", ltbp->flat_button);
1203 taskbar_apply_configuration(ltbp);
56f19a82
GP
1204}
1205
1206static void on_checkbutton_show_all_desks_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
1207{
6951c204
AG
1208 LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
1209 ltbp->show_all_desks = gtk_toggle_button_get_active(p_togglebutton);
56f19a82 1210 //g_print("\ntb->show_all_desks upd\n");
6951c204
AG
1211 config_group_set_int(ltbp->settings, "ShowAllDesks", ltbp->show_all_desks);
1212 taskbar_apply_configuration(ltbp);
56f19a82
GP
1213}
1214
4bb5845f
GP
1215static void on_checkbutton_same_monitor_only_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
1216{
6951c204
AG
1217 LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
1218 ltbp->same_monitor_only = gtk_toggle_button_get_active(p_togglebutton);
4bb5845f 1219 //g_print("\ntb->same_monitor_only upd\n");
6951c204
AG
1220 config_group_set_int(ltbp->settings, "SameMonitorOnly", ltbp->same_monitor_only);
1221 taskbar_apply_configuration(ltbp);
4bb5845f
GP
1222}
1223
56f19a82
GP
1224static void on_checkbutton_mouse_wheel_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
1225{
6951c204
AG
1226 LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
1227 ltbp->use_mouse_wheel = gtk_toggle_button_get_active(p_togglebutton);
56f19a82 1228 //g_print("\ntb->use_mouse_wheel upd\n");
6951c204
AG
1229 config_group_set_int(ltbp->settings, "UseMouseWheel", ltbp->use_mouse_wheel);
1230 taskbar_apply_configuration(ltbp);
56f19a82
GP
1231}
1232
1233static void on_checkbutton_urgency_hint_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
1234{
6951c204
AG
1235 LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
1236 ltbp->use_urgency_hint = gtk_toggle_button_get_active(p_togglebutton);
56f19a82 1237 //g_print("\ntb->use_urgency_hint upd\n");
6951c204
AG
1238 config_group_set_int(ltbp->settings, "UseUrgencyHint", ltbp->use_urgency_hint);
1239 taskbar_apply_configuration(ltbp);
56f19a82
GP
1240}
1241
1242static void on_checkbutton_grouped_tasks_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
1243{
6951c204
AG
1244 LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
1245 ltbp->grouped_tasks = gtk_toggle_button_get_active(p_togglebutton);
56f19a82 1246 //g_print("\ntb->grouped_tasks upd\n");
6951c204
AG
1247 config_group_set_int(ltbp->settings, "GroupedTasks", ltbp->grouped_tasks);
1248 taskbar_apply_configuration(ltbp);
56f19a82
GP
1249}
1250
1251static void on_spinbutton_max_width_value_changed(GtkSpinButton *p_spinbutton, gpointer p_data)
1252{
6951c204
AG
1253 LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
1254 ltbp->task_width_max = gtk_spin_button_get_value(p_spinbutton);
56f19a82 1255 //g_print("\ntb->task_width_max upd\n");
6951c204
AG
1256 config_group_set_int(ltbp->settings, "MaxTaskWidth", ltbp->task_width_max);
1257 taskbar_apply_configuration(ltbp);
56f19a82
GP
1258}
1259
1260static void on_spinbutton_spacing_value_changed(GtkSpinButton *p_spinbutton, gpointer p_data)
1261{
6951c204
AG
1262 LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
1263 ltbp->spacing = gtk_spin_button_get_value(p_spinbutton);
56f19a82 1264 //g_print("\ntb->spacing upd\n");
6951c204
AG
1265 config_group_set_int(ltbp->settings, "spacing", ltbp->spacing);
1266 taskbar_apply_configuration(ltbp);
56f19a82
GP
1267}
1268
1269static gboolean on_defined_view_button_press_event(GtkWidget *p_widget, GdkEventButton *p_event, gpointer p_data)
1270{
6951c204 1271 LaunchTaskBarPlugin *lb = (LaunchTaskBarPlugin *)p_data;
56f19a82
GP
1272 if(p_event->button == 1)
1273 {
1274 if(p_event->type == GDK_2BUTTON_PRESS)
1275 {
1276 gtk_button_clicked(GTK_BUTTON(lb->p_button_remove));
1277 }
1278 }
1279 return FALSE;
1280}
1281
1282static void on_defined_view_cursor_changed(GtkTreeView *p_treeview, gpointer p_data)
1283{
1284 gboolean label_set = FALSE;
6951c204 1285 LaunchTaskBarPlugin *lb = (LaunchTaskBarPlugin *)p_data;
56f19a82
GP
1286 GtkTreeIter tree_iter_sel;
1287 GtkTreeModel* p_treemodel = gtk_tree_view_get_model(p_treeview);
1288 GtkTreeSelection *p_treeselection = gtk_tree_view_get_selection(p_treeview);
1289 if(gtk_tree_selection_get_selected(p_treeselection,
1290 (GtkTreeModel **)(&p_treemodel),
1291 &tree_iter_sel))
1292 {
1293 LaunchButton * p_btn;
1294 gtk_tree_model_get(p_treemodel, &tree_iter_sel, COL_BTN, &p_btn, -1);
6951c204 1295 if( (p_btn != NULL) && (p_btn->fi != NULL) )
56f19a82
GP
1296 {
1297 GString *p_gstring = g_string_new("");
6951c204 1298 g_string_printf(p_gstring, "<i>%s</i>", fm_file_info_get_disp_name(p_btn->fi));
56f19a82
GP
1299 gtk_label_set_markup(GTK_LABEL(lb->p_label_def_app_exec), p_gstring->str);
1300 g_string_free(p_gstring, TRUE/*free also gstring->str*/);
56f19a82
GP
1301 label_set = TRUE;
1302 }
1303 }
6951c204
AG
1304 gtk_widget_set_visible(lb->p_label_def_app_exec, label_set);
1305 gtk_widget_set_sensitive(lb->p_button_remove, label_set);
56f19a82
GP
1306}
1307
1308static void on_menu_view_cursor_changed(GtkTreeView *p_treeview, gpointer p_data)
1309{
1310 gboolean label_set = FALSE;
6951c204
AG
1311 LaunchTaskBarPlugin *lb = (LaunchTaskBarPlugin *)p_data;
1312 GAppInfo *app = fm_app_menu_view_dup_selected_app(p_treeview);
56f19a82 1313
6951c204 1314 if (app)
56f19a82 1315 {
6951c204
AG
1316 GString *p_gstring = g_string_new("");
1317 if (g_app_info_get_description(app))
1318 g_string_printf(p_gstring, "<i>%s</i>", g_app_info_get_description(app));
1319 else
1320 g_string_printf(p_gstring, "<i>%s</i>", g_app_info_get_name(app));
1321 gtk_label_set_markup(GTK_LABEL(lb->p_label_menu_app_exec), p_gstring->str);
1322 g_string_free(p_gstring, TRUE/*free also gstring->str*/);
1323 label_set = TRUE;
56f19a82 1324 }
6951c204
AG
1325 gtk_widget_set_visible(lb->p_label_menu_app_exec, label_set);
1326 gtk_widget_set_sensitive(lb->p_button_add, label_set);
56f19a82
GP
1327}
1328
33cdf34d
AG
1329static void on_menu_view_row_activated(GtkTreeView *tree_view, GtkTreePath *path,
1330 GtkTreeViewColumn *column,
1331 LaunchTaskBarPlugin *ltbp)
1332{
1333 _launchbar_configure_add(tree_view, ltbp);
1334}
1335
56f19a82 1336/* Callback when the configuration dialog is to be shown. */
6951c204 1337static GtkWidget *launchtaskbar_configure(Panel *panel, GtkWidget *p, GtkWindow *parent)
56f19a82 1338{
6951c204 1339 LaunchTaskBarPlugin *ltbp = lxpanel_plugin_get_data(p);
56f19a82 1340
56f19a82 1341 {
6951c204 1342 GtkWidget *dlg, *btn, *defined_view, *menu_view, *menu_view_window;
56f19a82 1343 GtkBuilder *builder = gtk_builder_new();
d9742f08 1344 GObject *object;
56f19a82
GP
1345
1346 gtk_builder_add_from_file(builder, PACKAGE_UI_DIR "/launchtaskbar.ui", NULL);
1347 dlg = (GtkWidget *)gtk_builder_get_object(builder, "dlg");
1348 panel_apply_icon(GTK_WINDOW(dlg));
1349
1350 defined_view = (GtkWidget *)gtk_builder_get_object(builder, "defined_view");
6951c204
AG
1351 menu_view_window = (GtkWidget*)gtk_builder_get_object(builder, "menu_view_window");
1352 if (menu_view_window == NULL) /* fallback for old glade file */
1353 {
1354 menu_view_window = (GtkWidget*)gtk_builder_get_object(builder, "scroll2");
1355 gtk_widget_destroy(gtk_bin_get_child(GTK_BIN(menu_view_window)));
1356 }
1357 menu_view = GTK_WIDGET(fm_app_menu_view_new());
1358 gtk_container_add(GTK_CONTAINER(menu_view_window), menu_view);
1359 gtk_widget_show(menu_view);
1360 ltbp->p_label_def_app_exec = (GtkWidget*)gtk_builder_get_object(builder, "label_def_app_exec");
1361 ltbp->p_label_menu_app_exec = (GtkWidget*)gtk_builder_get_object(builder, "label_menu_app_exec");
56f19a82
GP
1362
1363 /* Connect signals. */
6951c204
AG
1364 ltbp->p_button_add = (GtkWidget *)gtk_builder_get_object(builder, "button_add");
1365 g_signal_connect(ltbp->p_button_add, "clicked", G_CALLBACK(launchbar_configure_add_button), ltbp);
56f19a82 1366
6951c204
AG
1367 ltbp->p_button_remove = (GtkWidget *)gtk_builder_get_object(builder, "button_remove");
1368 g_signal_connect(ltbp->p_button_remove, "clicked", G_CALLBACK(launchbar_configure_remove_button), ltbp);
56f19a82
GP
1369
1370 btn = (GtkWidget *)gtk_builder_get_object(builder, "button_up");
6951c204 1371 g_signal_connect(btn, "clicked", G_CALLBACK(launchbar_configure_move_up_button), ltbp);
56f19a82
GP
1372
1373 btn = (GtkWidget *)gtk_builder_get_object(builder, "button_down");
6951c204 1374 g_signal_connect(btn, "clicked", G_CALLBACK(launchbar_configure_move_down_button), ltbp);
56f19a82 1375
6951c204
AG
1376 g_signal_connect(defined_view, "button-press-event", G_CALLBACK(on_defined_view_button_press_event), ltbp);
1377 g_signal_connect(defined_view, "cursor-changed", G_CALLBACK(on_defined_view_cursor_changed), ltbp);
1378 g_signal_connect(menu_view, "cursor-changed", G_CALLBACK(on_menu_view_cursor_changed), ltbp);
33cdf34d 1379 g_signal_connect(menu_view, "row-activated", G_CALLBACK(on_menu_view_row_activated), ltbp);
56f19a82 1380
d9742f08
AG
1381 object = gtk_builder_get_object(builder, "notebook");
1382 ltbp->p_notebook_page_launch = gtk_notebook_get_nth_page(GTK_NOTEBOOK(object), 0);
1383 ltbp->p_notebook_page_task = gtk_notebook_get_nth_page(GTK_NOTEBOOK(object), 1);
b96b69ca
GP
1384 gtk_widget_set_visible(ltbp->p_notebook_page_launch, ltbp->lb_on);
1385 gtk_widget_set_visible(ltbp->p_notebook_page_task, ltbp->tb_on);
d9742f08
AG
1386
1387 object = gtk_builder_get_object(builder, "radiobutton_launch");
1388 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(object), !ltbp->tb_on);
1389 g_signal_connect(object, "toggled",
b96b69ca 1390 G_CALLBACK(on_radiobutton_launch_toggled), ltbp);
d9742f08
AG
1391 object = gtk_builder_get_object(builder, "radiobutton_task");
1392 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(object), !ltbp->lb_on);
1393 g_signal_connect(object, "toggled",
b96b69ca 1394 G_CALLBACK(on_radiobutton_task_toggled), ltbp);
d9742f08
AG
1395 object = gtk_builder_get_object(builder, "radiobutton_launchtask");
1396 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(object), ltbp->lb_on && ltbp->tb_on);
1397 g_signal_connect(object, "toggled",
b96b69ca 1398 G_CALLBACK(on_radiobutton_launchtask_toggled), ltbp);
d9742f08
AG
1399
1400#define SETUP_TOGGLE_BUTTON(button,member) \
1401 object = gtk_builder_get_object(builder, #button); \
1402 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(object), ltbp->member); \
1403 g_signal_connect(object, "toggled", G_CALLBACK(on_##button##_toggled), ltbp)
1404
1405 SETUP_TOGGLE_BUTTON(checkbutton_show_tooltips, tooltips);
1406 SETUP_TOGGLE_BUTTON(checkbutton_icons_only, icons_only);
1407 SETUP_TOGGLE_BUTTON(checkbutton_flat_buttons, flat_button);
1408 SETUP_TOGGLE_BUTTON(checkbutton_show_all_desks, show_all_desks);
1409 SETUP_TOGGLE_BUTTON(checkbutton_same_monitor_only, same_monitor_only);
1410 SETUP_TOGGLE_BUTTON(checkbutton_mouse_wheel, use_mouse_wheel);
1411 SETUP_TOGGLE_BUTTON(checkbutton_urgency_hint, use_urgency_hint);
1412 SETUP_TOGGLE_BUTTON(checkbutton_grouped_tasks, grouped_tasks);
1413#undef SETUP_TOGGLE_BUTTON
1414
1415#define SETUP_SPIN_BUTTON(button,member) \
1416 object = gtk_builder_get_object(builder, #button); \
1417 gtk_spin_button_set_value(GTK_SPIN_BUTTON(object), ltbp->member); \
1418 g_signal_connect(object, "value-changed", \
1419 G_CALLBACK(on_##button##_value_changed), ltbp)
1420
1421 SETUP_SPIN_BUTTON(spinbutton_max_width, task_width_max);
1422 SETUP_SPIN_BUTTON(spinbutton_spacing, spacing);
1423#undef SETUP_SPIN_BUTTON
56f19a82 1424
e9d877a9 1425 ltbp->config_dlg = dlg;
56f19a82 1426
56f19a82 1427 /* Initialize the tree view contents. */
6951c204
AG
1428 launchbar_configure_initialize_list(ltbp, dlg, GTK_TREE_VIEW(defined_view));
1429 g_object_set_data(G_OBJECT(dlg), "menu_view", menu_view);
56f19a82 1430
6951c204
AG
1431 gtk_widget_set_visible(ltbp->p_label_menu_app_exec, FALSE);
1432 gtk_widget_set_visible(ltbp->p_label_def_app_exec, FALSE);
1433 gtk_widget_set_sensitive(ltbp->p_button_add, FALSE);
1434 gtk_widget_set_sensitive(ltbp->p_button_remove, FALSE);
56f19a82
GP
1435
1436 g_object_unref(builder);
56f19a82 1437 }
6951c204 1438 return ltbp->config_dlg;
56f19a82
GP
1439}
1440
1441/* Callback when panel configuration changes. */
6951c204 1442static void launchtaskbar_panel_configuration_changed(Panel *panel, GtkWidget *p)
56f19a82
GP
1443{
1444 /* Set orientation into the icon grid. */
6951c204
AG
1445 LaunchTaskBarPlugin *ltbp = lxpanel_plugin_get_data(p);
1446 int new_icon_size = panel_get_icon_size(panel);
1447
443a4c75
AG
1448 if (ltbp->lb_built)
1449 icon_grid_set_geometry(ltbp->lb_icon_grid, panel_get_orientation(panel),
1450 new_icon_size, new_icon_size, 3, 0,
1451 panel_get_height(panel));
56f19a82 1452
6951c204
AG
1453 /* Reset the bootstrap button. */
1454 if (ltbp->bootstrap_button != NULL)
56f19a82 1455 {
6951c204
AG
1456 GdkPixbuf * icon = fm_pixbuf_from_icon(ltbp->add_icon, new_icon_size);
1457 gtk_image_set_from_pixbuf(GTK_IMAGE(ltbp->bootstrap_button->image_widget), icon);
1458 g_object_unref(icon);
56f19a82
GP
1459 }
1460
443a4c75
AG
1461 if (ltbp->tb_built)
1462 {
1463 taskbar_update_style(ltbp);
1464 taskbar_make_menu(ltbp);
1465 }
56f19a82
GP
1466
1467 /* If the icon size changed, refetch all the icons. */
6951c204 1468 if (new_icon_size != ltbp->icon_size)
56f19a82 1469 {
56f19a82 1470 Task * tk;
6951c204
AG
1471 GSList * l;
1472 ltbp->icon_size = new_icon_size;
1473 for (tk = ltbp->p_task_list; tk != NULL; tk = tk->p_task_flink_xwid)
56f19a82 1474 {
6951c204 1475 GdkPixbuf * pixbuf = task_update_icon(ltbp, tk, None);
56f19a82
GP
1476 if (pixbuf != NULL)
1477 {
1478 gtk_image_set_from_pixbuf(GTK_IMAGE(tk->image), pixbuf);
1479 g_object_unref(pixbuf);
1480 }
1481 }
6951c204
AG
1482 for (l = ltbp->buttons; l != NULL; l = l->next)
1483 {
1484 LaunchButton * btn = (LaunchButton *) l->data;
1485 lxpanel_button_update_icon(btn->widget, fm_file_info_get_icon(btn->fi),
1486 new_icon_size);
1487 }
1488
56f19a82
GP
1489 }
1490
1491 /* Redraw all the labels. Icon size or font color may have changed. */
443a4c75
AG
1492 if (ltbp->tb_built)
1493 taskbar_redraw(ltbp);
56f19a82
GP
1494}
1495
1496/* Set an urgency timer on a task. */
1497static void set_timer_on_task(Task * tk)
1498{
1499 gint interval;
1500 g_return_if_fail(tk->flash_timeout == 0);
1501 g_object_get(gtk_widget_get_settings(tk->button), "gtk-cursor-blink-time", &interval, NULL);
3899f188 1502 tk->flash_timeout = g_timeout_add(interval, flash_window_timeout, tk);
56f19a82
GP
1503}
1504
1505/* Determine if a task is visible considering only its desktop placement. */
6951c204 1506static gboolean task_is_visible_on_current_desktop(LaunchTaskBarPlugin * tb, Task * tk)
56f19a82
GP
1507{
1508 return ((tk->desktop == ALL_WORKSPACES) || (tk->desktop == tb->current_desktop) || (tb->show_all_desks));
1509}
1510
1511/* Recompute the visible task for a class when the class membership changes.
1512 * Also transfer the urgency state to the visible task if necessary. */
6951c204 1513static void recompute_group_visibility_for_class(LaunchTaskBarPlugin * tb, TaskClass * tc)
56f19a82
GP
1514{
1515 tc->visible_count = 0;
1516 tc->p_task_visible = NULL;
1517 tc->visible_name = NULL;
1518 Task * flashing_task = NULL;
1519 gboolean class_has_urgency = FALSE;
1520 Task * tk;
1521 for (tk = tc->p_task_head; tk != NULL; tk = tk->p_task_flink_same_class)
1522 {
1523 if (task_is_visible_on_current_desktop(tb, tk))
1524 {
1525 /* Count visible tasks and make the first visible task the one that is used for display. */
1526 if (tc->visible_count == 0)
1527 tc->p_task_visible = tk;
1528 tc->visible_count += 1;
1529
1530 /* Compute summary bit for urgency anywhere in the class. */
1531 if (tk->urgency)
1532 class_has_urgency = TRUE;
1533
1534 /* If there is urgency, record the currently flashing task. */
1535 if (tk->flash_timeout != 0)
1536 flashing_task = tk;
1537
1538 /* Compute the visible name. If all visible windows have the same title, use that.
1539 * Otherwise, use the class name. This follows WNCK.
1540 * Note that the visible name is not a separate string, but is set to point to one of the others. */
1541 if (tc->visible_name == NULL)
1542 tc->visible_name = tk->name;
1543 else if ((tc->visible_name != tc->res_class)
1544 && (tc->visible_name != NULL) && (tk->name != NULL)
1545 && (strcmp(tc->visible_name, tk->name) != 0))
1546 tc->visible_name = tc->res_class;
1547 }
1548 }
1549
1550 /* Transfer the flash timeout to the visible task. */
1551 if (class_has_urgency)
1552 {
1553 if (flashing_task == NULL)
1554 {
1555 /* Set the flashing context and flash the window immediately. */
1556 tc->p_task_visible->flash_state = TRUE;
3899f188 1557 flash_window_update(tc->p_task_visible);
56f19a82
GP
1558
1559 /* Set the timer, since none is set. */
1560 set_timer_on_task(tc->p_task_visible);
1561 }
1562 else if (flashing_task != tc->p_task_visible)
1563 {
1564 /* Reset the timer on the new representative.
1565 * There will be a slight hiccup on the flash cadence. */
1566 g_source_remove(flashing_task->flash_timeout);
1567 flashing_task->flash_timeout = 0;
1568 tc->p_task_visible->flash_state = flashing_task->flash_state;
1569 flashing_task->flash_state = FALSE;
1570 set_timer_on_task(tc->p_task_visible);
3899f188 1571 }
56f19a82
GP
1572 }
1573 else
1574 {
1575 /* No task has urgency. Cancel the timer if one is set. */
1576 if (flashing_task != NULL)
1577 {
1578 g_source_remove(flashing_task->flash_timeout);
1579 flashing_task->flash_state = FALSE;
1580 flashing_task->flash_timeout = 0;
1581 }
1582 }
1583}
1584
1585/* Recompute the visible task for all classes when the desktop changes. */
6951c204 1586static void recompute_group_visibility_on_current_desktop(LaunchTaskBarPlugin * tb)
56f19a82
GP
1587{
1588 TaskClass * tc;
1589 for (tc = tb->p_taskclass_list; tc != NULL; tc = tc->p_taskclass_flink)
1590 {
1591 recompute_group_visibility_for_class(tb, tc);
1592 }
1593}
1594
1595/* Draw the label and tooltip on a taskbar button. */
1596static void task_draw_label(Task * tk)
1597{
1598 TaskClass * tc = tk->p_taskclass;
1599 gboolean bold_style = (((tk->entered_state) || (tk->flash_state)) && (tk->tb->flat_button));
1600 char *label;
443a4c75 1601
56f19a82
GP
1602 if ((tk->tb->grouped_tasks) && (tc != NULL) && (tc->p_task_visible == tk) && (tc->visible_count > 1))
1603 {
1604 label = g_strdup_printf("(%d) %s", tc->visible_count, tc->visible_name);
1605 }
1606 else
1607 {
1608 label = g_strdup(tk->iconified ? tk->name_iconified : tk->name);
1609 }
443a4c75 1610
56f19a82
GP
1611 if (tk->tb->tooltips)
1612 gtk_widget_set_tooltip_text(tk->button, label);
1613
6951c204 1614 panel_draw_label_text(tk->tb->panel, tk->label, label, bold_style, 1,
56f19a82
GP
1615 tk->tb->flat_button);
1616
1617 g_free(label);
1618}
1619
1620/* Determine if a task is visible. */
6951c204 1621static gboolean task_is_visible(LaunchTaskBarPlugin * tb, Task * tk)
56f19a82
GP
1622{
1623 /* Not visible due to grouping. */
1624 if ((tb->grouped_tasks) && (tk->p_taskclass != NULL) && (tk->p_taskclass->p_task_visible != tk))
1625 return FALSE;
1626
4bb5845f 1627 /* Not on same monitor */
6951c204 1628 if (tb->same_monitor_only && panel_get_monitor(tb->panel) != tk->monitor)
4bb5845f
GP
1629 return FALSE;
1630
56f19a82
GP
1631 /* Desktop placement. */
1632 return task_is_visible_on_current_desktop(tb, tk);
1633}
1634
1635/* Redraw a task button. */
6951c204 1636static void task_button_redraw(Task * tk, LaunchTaskBarPlugin * tb)
56f19a82
GP
1637{
1638 if (task_is_visible(tb, tk))
1639 {
1640 task_draw_label(tk);
6951c204 1641 icon_grid_set_visible(tb->tb_icon_grid, tk->button, TRUE);
56f19a82
GP
1642 }
1643 else
6951c204 1644 icon_grid_set_visible(tb->tb_icon_grid, tk->button, FALSE);
56f19a82
GP
1645}
1646
1647/* Redraw all tasks in the taskbar. */
6951c204 1648static void taskbar_redraw(LaunchTaskBarPlugin * tb)
56f19a82
GP
1649{
1650 Task * tk;
1651 for (tk = tb->p_task_list; tk != NULL; tk = tk->p_task_flink_xwid)
1652 task_button_redraw(tk, tb);
1653}
1654
1655/* Determine if a task should be visible given its NET_WM_STATE. */
1656static gboolean accept_net_wm_state(NetWMState * nws)
1657{
1658 return ( ! (nws->skip_taskbar));
1659}
1660
1661/* Determine if a task should be visible given its NET_WM_WINDOW_TYPE. */
1662static gboolean accept_net_wm_window_type(NetWMWindowType * nwwt)
1663{
1664 return ( ! ((nwwt->desktop) || (nwwt->dock) || (nwwt->splash)));
1665}
1666
1667/* Free the names associated with a task. */
1668static void task_free_names(Task * tk)
1669{
1670 g_free(tk->name);
1671 g_free(tk->name_iconified);
1672 tk->name = tk->name_iconified = NULL;
1673}
1674
1675/* Set the names associated with a task.
1676 * This is expected to be the same as the title the window manager is displaying. */
1677static void task_set_names(Task * tk, Atom source)
1678{
1679 char * name = NULL;
1680
1681 /* Try _NET_WM_VISIBLE_NAME, which supports UTF-8.
1682 * If it is set, the window manager is displaying it as the window title. */
1683 if ((source == None) || (source == a_NET_WM_VISIBLE_NAME))
1684 {
1685 name = get_utf8_property(tk->win, a_NET_WM_VISIBLE_NAME);
1686 if (name != NULL)
1687 tk->name_source = a_NET_WM_VISIBLE_NAME;
1688 }
1689
1690 /* Try _NET_WM_NAME, which supports UTF-8, but do not overwrite _NET_WM_VISIBLE_NAME. */
1691 if ((name == NULL)
1692 && ((source == None) || (source == a_NET_WM_NAME))
1693 && ((tk->name_source == None) || (tk->name_source == a_NET_WM_NAME) || (tk->name_source == XA_WM_NAME)))
1694 {
1695 name = get_utf8_property(tk->win, a_NET_WM_NAME);
1696 if (name != NULL)
1697 tk->name_source = a_NET_WM_NAME;
1698 }
1699
1700 /* Try WM_NAME, which supports only ISO-8859-1, but do not overwrite _NET_WM_VISIBLE_NAME or _NET_WM_NAME. */
1701 if ((name == NULL)
1702 && ((source == None) || (source == XA_WM_NAME))
1703 && ((tk->name_source == None) || (tk->name_source == XA_WM_NAME)))
1704 {
1705 name = get_textproperty(tk->win, XA_WM_NAME);
1706 if (name != NULL)
1707 tk->name_source = XA_WM_NAME;
1708 }
1709
1710 /* Set the name into the task context, and also on the tooltip. */
1711 if (name != NULL)
1712 {
1713 task_free_names(tk);
1714 tk->name = g_strdup(name);
1715 tk->name_iconified = g_strdup_printf("[%s]", name);
1716 g_free(name);
1717
1718 /* Redraw the button. */
1719 task_button_redraw(tk, tk->tb);
1720 }
1721}
1722
1723/* Unlink a task from the class list because its class changed or it was deleted. */
1724static void task_unlink_class(Task * tk)
1725{
1726 TaskClass * tc = tk->p_taskclass;
1727 if (tc != NULL)
1728 {
1729 /* Action in Launchbar after class removed */
6951c204 1730 launchbar_update_after_taskbar_class_removed(tk->tb, tk);
443a4c75 1731
56f19a82
GP
1732 /* Remove from per-class task list. */
1733 if (tc->p_task_head == tk)
1734 {
1735 /* Removing the head of the list. This causes a new task to be the visible task, so we redraw. */
1736 tc->p_task_head = tk->p_task_flink_same_class;
1737 if (tc->p_task_head != NULL)
1738 task_button_redraw(tc->p_task_head, tk->tb);
1739 }
1740 else
1741 {
1742 /* Locate the task and its predecessor in the list and then remove it. For safety, ensure it is found. */
1743 Task * tk_pred = NULL;
1744 Task * tk_cursor;
1745 for (
1746 tk_cursor = tc->p_task_head;
1747 ((tk_cursor != NULL) && (tk_cursor != tk));
1748 tk_pred = tk_cursor, tk_cursor = tk_cursor->p_task_flink_same_class) ;
1749 if (tk_cursor == tk)
1750 tk_pred->p_task_flink_same_class = tk->p_task_flink_same_class;
1751 }
1752 tk->p_task_flink_same_class = NULL;
1753
1754 /* Recompute group visibility. */
1755 recompute_group_visibility_for_class(tk->tb, tc);
1756 }
1757}
1758
1759/* Enter class with specified name. */
6951c204 1760static TaskClass * taskbar_enter_res_class(LaunchTaskBarPlugin * tb, char * res_class, gboolean * p_name_consumed)
56f19a82
GP
1761{
1762 /* Find existing entry or insertion point. */
1763 *p_name_consumed = FALSE;
1764 TaskClass * tc_pred = NULL;
1765 TaskClass * tc;
1766 for (tc = tb->p_taskclass_list; tc != NULL; tc_pred = tc, tc = tc->p_taskclass_flink)
1767 {
1768 int status = strcmp(res_class, tc->res_class);
1769 if (status == 0)
1770 return tc;
1771 if (status < 0)
1772 break;
1773 }
1774
1775 /* Insert new entry. */
1776 tc = g_new0(TaskClass, 1);
1777 tc->res_class = res_class;
1778 *p_name_consumed = TRUE;
1779 if (tc_pred == NULL)
1780 {
1781 tc->p_taskclass_flink = tb->p_taskclass_list;
1782 tb->p_taskclass_list = tc;
1783 }
1784 else
1785 {
1786 tc->p_taskclass_flink = tc_pred->p_taskclass_flink;
1787 tc_pred->p_taskclass_flink = tc;
1788 }
1789 return tc;
1790}
1791
1792/* Set the class associated with a task. */
1793static void task_set_class(Task * tk)
1794{
1795 /* Read the WM_CLASS property. */
1796 XClassHint ch;
1797 ch.res_name = NULL;
1798 ch.res_class = NULL;
1799 XGetClassHint(GDK_DISPLAY(), tk->win, &ch);
1800
1801 /* If the res_name was returned, free it. We make no use of it at this time. */
1802 if (ch.res_name != NULL)
1803 {
1804 XFree(ch.res_name);
1805 }
1806
1807 /* If the res_class was returned, process it.
1808 * This identifies the application that created the window and is the basis for taskbar grouping. */
1809 if (ch.res_class != NULL)
1810 {
1811 /* Convert the class to UTF-8 and enter it in the class table. */
1812 gchar * res_class = g_locale_to_utf8(ch.res_class, -1, NULL, NULL, NULL);
1813 if (res_class != NULL)
1814 {
1815 gboolean name_consumed;
1816 TaskClass * tc = taskbar_enter_res_class(tk->tb, res_class, &name_consumed);
1817 if ( ! name_consumed) g_free(res_class);
1818
1819 /* If the task changed class, update data structures. */
1820 TaskClass * old_tc = tk->p_taskclass;
1821 if (old_tc != tc)
1822 {
1823 /* Unlink from previous class, if any. */
1824 task_unlink_class(tk);
1825
1826 /* Add to end of per-class task list. Do this to keep the popup menu in order of creation. */
1827 if (tc->p_task_head == NULL)
1828 tc->p_task_head = tk;
1829 else
1830 {
1831 Task * tk_pred;
1832 for (tk_pred = tc->p_task_head; tk_pred->p_task_flink_same_class != NULL; tk_pred = tk_pred->p_task_flink_same_class) ;
1833 tk_pred->p_task_flink_same_class = tk;
1834 task_button_redraw(tk, tk->tb);
1835 }
1836 tk->p_taskclass = tc;
1837
1838 /* Recompute group visibility. */
1839 recompute_group_visibility_for_class(tk->tb, tc);
443a4c75 1840
56f19a82 1841 /* Action in Launchbar after class added */
6951c204 1842 launchbar_update_after_taskbar_class_added(tk->tb, tk);
56f19a82
GP
1843 }
1844 }
1845 XFree(ch.res_class);
1846 }
1847}
1848
1849/* Look up a task in the task list. */
6951c204 1850static Task * task_lookup(LaunchTaskBarPlugin * tb, Window win)
56f19a82
GP
1851{
1852 Task * tk;
1853 for (tk = tb->p_task_list; tk != NULL; tk = tk->p_task_flink_xwid)
1854 {
1855 if (tk->win == win)
1856 return tk;
1857 if (tk->win > win)
1858 break;
1859 }
1860 return NULL;
1861}
1862
1863/* Delete a task and optionally unlink it from the task list. */
6951c204 1864static void task_delete(LaunchTaskBarPlugin * tb, Task * tk, gboolean unlink)
56f19a82
GP
1865{
1866 /* If we think this task had focus, remove that. */
1867 if (tb->focused == tk)
1868 tb->focused = NULL;
1869
1870 /* If there is an urgency timeout, remove it. */
1871 if (tk->flash_timeout != 0) {
1872 g_source_remove(tk->flash_timeout);
1873 tk->flash_timeout = 0;
1874 }
1875
1876 /* Deallocate structures. */
6951c204 1877 icon_grid_remove(tb->tb_icon_grid, tk->button);
56f19a82 1878 task_free_names(tk);
6951c204 1879 g_free(tk->exec_bin);
56f19a82
GP
1880 task_unlink_class(tk);
1881
1882 /* If requested, unlink the task from the task list.
1883 * If not requested, the caller will do this. */
1884 if (unlink)
1885 {
1886 if (tb->p_task_list == tk)
1887 tb->p_task_list = tk->p_task_flink_xwid;
1888 else
1889 {
1890 /* Locate the task and its predecessor in the list and then remove it. For safety, ensure it is found. */
1891 Task * tk_pred = NULL;
1892 Task * tk_cursor;
1893 for (
1894 tk_cursor = tb->p_task_list;
1895 ((tk_cursor != NULL) && (tk_cursor != tk));
1896 tk_pred = tk_cursor, tk_cursor = tk_cursor->p_task_flink_xwid) ;
1897 if (tk_cursor == tk)
1898 tk_pred->p_task_flink_xwid = tk->p_task_flink_xwid;
1899 }
1900 }
1901
1902 /* Deallocate the task structure. */
1903 g_free(tk);
1904}
1905
1906/* Get a pixbuf from a pixmap.
1907 * Originally from libwnck, Copyright (C) 2001 Havoc Pennington. */
1908static GdkPixbuf * _wnck_gdk_pixbuf_get_from_pixmap(Pixmap xpixmap, int width, int height)
1909{
1910 /* Get the drawable. */
1911 GdkDrawable * drawable = gdk_xid_table_lookup(xpixmap);
1912 if (drawable != NULL)
1913 g_object_ref(G_OBJECT(drawable));
1914 else
1915 drawable = gdk_pixmap_foreign_new(xpixmap);
1916
1917 GdkColormap * colormap = NULL;
1918 GdkPixbuf * retval = NULL;
1919 if (drawable != NULL)
1920 {
1921 /* Get the colormap.
1922 * If the drawable has no colormap, use no colormap or the system colormap as recommended in the documentation of gdk_drawable_get_colormap. */
1923 colormap = gdk_drawable_get_colormap(drawable);
1924 gint depth = gdk_drawable_get_depth(drawable);
1925 if (colormap != NULL)
1926 g_object_ref(G_OBJECT(colormap));
1927 else if (depth == 1)
1928 colormap = NULL;
1929 else
1930 {
1931 colormap = gdk_screen_get_system_colormap(gdk_drawable_get_screen(drawable));
1932 g_object_ref(G_OBJECT(colormap));
1933 }
1934
1935 /* Be sure we aren't going to fail due to visual mismatch. */
1936#if GTK_CHECK_VERSION(2,22,0)
1937 if ((colormap != NULL) && (gdk_visual_get_depth(gdk_colormap_get_visual(colormap)) != depth))
1938#else
1939 if ((colormap != NULL) && (gdk_colormap_get_visual(colormap)->depth != depth))
1940#endif
1941 {
1942 g_object_unref(G_OBJECT(colormap));
1943 colormap = NULL;
1944 }
1945
1946 /* Do the major work. */
1947 retval = gdk_pixbuf_get_from_drawable(NULL, drawable, colormap, 0, 0, 0, 0, width, height);
1948 }
1949
1950 /* Clean up and return. */
1951 if (colormap != NULL)
1952 g_object_unref(G_OBJECT(colormap));
1953 if (drawable != NULL)
1954 g_object_unref(G_OBJECT(drawable));
1955 return retval;
1956}
1957
1958/* Apply a mask to a pixbuf.
1959 * Originally from libwnck, Copyright (C) 2001 Havoc Pennington. */
1960static GdkPixbuf * apply_mask(GdkPixbuf * pixbuf, GdkPixbuf * mask)
1961{
1962 /* Initialize. */
1963 int w = MIN(gdk_pixbuf_get_width(mask), gdk_pixbuf_get_width(pixbuf));
1964 int h = MIN(gdk_pixbuf_get_height(mask), gdk_pixbuf_get_height(pixbuf));
1965 GdkPixbuf * with_alpha = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
1966 guchar * dst = gdk_pixbuf_get_pixels(with_alpha);
1967 guchar * src = gdk_pixbuf_get_pixels(mask);
1968 int dst_stride = gdk_pixbuf_get_rowstride(with_alpha);
1969 int src_stride = gdk_pixbuf_get_rowstride(mask);
1970
1971 /* Loop to do the work. */
1972 int i;
1973 for (i = 0; i < h; i += 1)
1974 {
1975 int j;
1976 for (j = 0; j < w; j += 1)
1977 {
1978 guchar * s = src + i * src_stride + j * 3;
1979 guchar * d = dst + i * dst_stride + j * 4;
1980
1981 /* s[0] == s[1] == s[2], they are 255 if the bit was set, 0 otherwise. */
1982 d[3] = ((s[0] == 0) ? 0 : 255); /* 0 = transparent, 255 = opaque */
1983 }
1984 }
1985
1986 return with_alpha;
1987}
1988
1989/* Get an icon from the window manager for a task, and scale it to a specified size. */
6951c204 1990static GdkPixbuf * get_wm_icon(Window task_win, guint required_width, guint required_height, Atom source, Atom * current_source)
56f19a82
GP
1991{
1992 /* The result. */
1993 GdkPixbuf * pixmap = NULL;
1994 Atom possible_source = None;
1995 int result = -1;
1996
1997 if ((source == None) || (source == a_NET_WM_ICON))
1998 {
1999 /* Important Notes:
2000 * According to freedesktop.org document:
2001 * http://standards.freedesktop.org/wm-spec/wm-spec-1.4.html#id2552223
2002 * _NET_WM_ICON contains an array of 32-bit packed CARDINAL ARGB.
2003 * However, this is incorrect. Actually it's an array of long integers.
2004 * Toolkits like gtk+ use unsigned long here to store icons.
2005 * Besides, according to manpage of XGetWindowProperty, when returned format,
2006 * is 32, the property data will be stored as an array of longs
2007 * (which in a 64-bit application will be 64-bit values that are
2008 * padded in the upper 4 bytes).
2009 */
2010
2011 /* Get the window property _NET_WM_ICON, if possible. */
2012 Atom type = None;
2013 int format;
2014 gulong nitems;
2015 gulong bytes_after;
2016 gulong * data = NULL;
2017 result = XGetWindowProperty(
2018 GDK_DISPLAY(),
2019 task_win,
2020 a_NET_WM_ICON,
2021 0, G_MAXLONG,
2022 False, XA_CARDINAL,
2023 &type, &format, &nitems, &bytes_after, (void *) &data);
2024
2025 /* Inspect the result to see if it is usable. If not, and we got data, free it. */
2026 if ((type != XA_CARDINAL) || (nitems <= 0))
2027 {
2028 if (data != NULL)
2029 XFree(data);
2030 result = -1;
2031 }
2032
2033 /* If the result is usable, extract the icon from it. */
2034 if (result == Success)
2035 {
2036 /* Get the largest icon available, unless there is one that is the desired size. */
2037 /* FIXME: should we try to find an icon whose size is closest to
2038 * required_width and required_height to reduce unnecessary resizing? */
2039 gulong * pdata = data;
2040 gulong * pdata_end = data + nitems;
2041 gulong * max_icon = NULL;
2042 gulong max_w = 0;
2043 gulong max_h = 0;
2044 while ((pdata + 2) < pdata_end)
2045 {
2046 /* Extract the width and height. */
6951c204
AG
2047 guint w = pdata[0];
2048 guint h = pdata[1];
56f19a82
GP
2049 gulong size = w * h;
2050 pdata += 2;
2051
2052 /* Bounds check the icon. */
2053 if (pdata + size > pdata_end)
2054 break;
2055
2056 /* Rare special case: the desired size is the same as icon size. */
2057 if ((required_width == w) && (required_height == h))
2058 {
2059 max_icon = pdata;
2060 max_w = w;
2061 max_h = h;
2062 break;
2063 }
2064
2065 /* If the icon is the largest so far, capture it. */
2066 if ((w > max_w) && (h > max_h))
2067 {
2068 max_icon = pdata;
2069 max_w = w;
2070 max_h = h;
2071 }
2072 pdata += size;
2073 }
2074
2075 /* If an icon was extracted, convert it to a pixbuf.
2076 * Its size is max_w and max_h. */
2077 if (max_icon != NULL)
2078 {
2079 /* Allocate enough space for the pixel data. */
2080 gulong len = max_w * max_h;
2081 guchar * pixdata = g_new(guchar, len * 4);
2082
2083 /* Loop to convert the pixel data. */
2084 guchar * p = pixdata;
6951c204 2085 gulong i;
56f19a82
GP
2086 for (i = 0; i < len; p += 4, i += 1)
2087 {
2088 guint argb = max_icon[i];
2089 guint rgba = (argb << 8) | (argb >> 24);
2090 p[0] = rgba >> 24;
2091 p[1] = (rgba >> 16) & 0xff;
2092 p[2] = (rgba >> 8) & 0xff;
2093 p[3] = rgba & 0xff;
2094 }
6951c204 2095
56f19a82
GP
2096 /* Initialize a pixmap with the pixel data. */
2097 pixmap = gdk_pixbuf_new_from_data(
2098 pixdata,
2099 GDK_COLORSPACE_RGB,
2100 TRUE, 8, /* has_alpha, bits_per_sample */
2101 max_w, max_h, max_w * 4,
2102 (GdkPixbufDestroyNotify) g_free,
2103 NULL);
2104 possible_source = a_NET_WM_ICON;
2105 }
2106 else
2107 result = -1;
2108
2109 /* Free the X property data. */
2110 XFree(data);
2111 }
2112 }
2113
2114 /* No icon available from _NET_WM_ICON. Next try WM_HINTS, but do not overwrite _NET_WM_ICON. */
2115 if ((result != Success) && (*current_source != a_NET_WM_ICON)
2116 && ((source == None) || (source != a_NET_WM_ICON)))
2117 {
2118 XWMHints * hints = XGetWMHints(GDK_DISPLAY(), task_win);
2119 result = (hints != NULL) ? Success : -1;
2120 Pixmap xpixmap = None;
2121 Pixmap xmask = None;
2122
2123 if (result == Success)
2124 {
2125 /* WM_HINTS is available. Extract the X pixmap and mask. */
2126 if ((hints->flags & IconPixmapHint))
2127 xpixmap = hints->icon_pixmap;
2128 if ((hints->flags & IconMaskHint))
2129 xmask = hints->icon_mask;
2130 XFree(hints);
2131 if (xpixmap != None)
2132 {
2133 result = Success;
2134 possible_source = XA_WM_HINTS;
2135 }
2136 else
2137 result = -1;
2138 }
2139
2140 if (result != Success)
2141 {
2142 /* No icon available from _NET_WM_ICON or WM_HINTS. Next try KWM_WIN_ICON. */
2143 Atom type = None;
2144 int format;
2145 gulong nitems;
2146 gulong bytes_after;
2147 Pixmap *icons = NULL;
2148 Atom kwin_win_icon_atom = gdk_x11_get_xatom_by_name("KWM_WIN_ICON");
2149 result = XGetWindowProperty(
2150 GDK_DISPLAY(),
2151 task_win,
2152 kwin_win_icon_atom,
2153 0, G_MAXLONG,
2154 False, kwin_win_icon_atom,
2155 &type, &format, &nitems, &bytes_after, (void *) &icons);
2156
2157 /* Inspect the result to see if it is usable. If not, and we got data, free it. */
2158 if (type != kwin_win_icon_atom)
2159 {
2160 if (icons != NULL)
2161 XFree(icons);
2162 result = -1;
2163 }
2164
2165 /* If the result is usable, extract the X pixmap and mask from it. */
2166 if (result == Success)
2167 {
2168 xpixmap = icons[0];
2169 xmask = icons[1];
2170 if (xpixmap != None)
2171 {
2172 result = Success;
2173 possible_source = kwin_win_icon_atom;
2174 }
2175 else
2176 result = -1;
2177 }
2178 }
2179
2180 /* If we have an X pixmap, get its geometry.*/
2181 unsigned int w, h;
2182 if (result == Success)
2183 {
2184 Window unused_win;
2185 int unused;
2186 unsigned int unused_2;
2187 result = XGetGeometry(
2188 GDK_DISPLAY(), xpixmap,
2189 &unused_win, &unused, &unused, &w, &h, &unused_2, &unused_2) ? Success : -1;
2190 }
2191
2192 /* If we have an X pixmap and its geometry, convert it to a GDK pixmap. */
443a4c75 2193 if (result == Success)
56f19a82
GP
2194 {
2195 pixmap = _wnck_gdk_pixbuf_get_from_pixmap(xpixmap, w, h);
2196 result = ((pixmap != NULL) ? Success : -1);
2197 }
2198
2199 /* If we have success, see if the result needs to be masked.
2200 * Failures here are implemented as nonfatal. */
2201 if ((result == Success) && (xmask != None))
2202 {
2203 Window unused_win;
2204 int unused;
2205 unsigned int unused_2;
2206 if (XGetGeometry(
2207 GDK_DISPLAY(), xmask,
2208 &unused_win, &unused, &unused, &w, &h, &unused_2, &unused_2))
2209 {
2210 /* Convert the X mask to a GDK pixmap. */
2211 GdkPixbuf * mask = _wnck_gdk_pixbuf_get_from_pixmap(xmask, w, h);
2212 if (mask != NULL)
2213 {
2214 /* Apply the mask. */
2215 GdkPixbuf * masked_pixmap = apply_mask(pixmap, mask);
2216 g_object_unref(G_OBJECT(pixmap));
2217 g_object_unref(G_OBJECT(mask));
2218 pixmap = masked_pixmap;
2219 }
2220 }
2221 }
2222 }
2223
2224 /* If we got a pixmap, scale it and return it. */
2225 if (pixmap == NULL)
2226 return NULL;
2227 else
2228 {
2229 GdkPixbuf * ret = gdk_pixbuf_scale_simple(pixmap, required_width, required_height, GDK_INTERP_TILES);
2230 g_object_unref(pixmap);
2231 *current_source = possible_source;
2232 return ret;
2233 }
2234}
2235
2236/* Update the icon of a task. */
6951c204 2237static GdkPixbuf * task_update_icon(LaunchTaskBarPlugin * tb, Task * tk, Atom source)
56f19a82
GP
2238{
2239 /* Get the icon from the window's hints. */
6951c204
AG
2240 GdkPixbuf * pixbuf = get_wm_icon(tk->win, MAX(0, tb->icon_size - ICON_BUTTON_TRIM),
2241 MAX(0, tb->icon_size - ICON_BUTTON_TRIM),
2242 source, &tk->image_source);
56f19a82
GP
2243
2244 /* If that fails, and we have no other icon yet, return the fallback icon. */
2245 if ((pixbuf == NULL)
2246 && ((source == None) || (tk->image_source == None)))
2247 {
2248 /* Establish the fallback task icon. This is used when no other icon is available. */
2249 if (tb->fallback_pixbuf == NULL)
2250 tb->fallback_pixbuf = gdk_pixbuf_new_from_xpm_data((const char **) icon_xpm);
2251 g_object_ref(tb->fallback_pixbuf);
2252 pixbuf = tb->fallback_pixbuf;
2253 }
2254
2255 /* Return what we have. This may be NULL to indicate that no change should be made to the icon. */
2256 return pixbuf;
2257}
2258
2259/* Timer expiration for urgency notification. Also used to draw the button in setting and clearing urgency. */
3899f188 2260static void flash_window_update(Task * tk)
56f19a82
GP
2261{
2262 /* Set state on the button and redraw. */
2263 if ( ! tk->tb->flat_button)
2264 gtk_widget_set_state(tk->button, tk->flash_state ? GTK_STATE_SELECTED : GTK_STATE_NORMAL);
2265 task_draw_label(tk);
2266
2267 /* Complement the flashing context. */
2268 tk->flash_state = ! tk->flash_state;
3899f188
AG
2269}
2270
2271static gboolean flash_window_timeout(gpointer tk)
2272{
2273 if (g_source_is_destroyed(g_main_current_source()))
2274 return FALSE;
2275 flash_window_update(tk);
56f19a82
GP
2276 return TRUE;
2277}
2278
2279/* Set urgency notification. */
2280static void task_set_urgency(Task * tk)
2281{
6951c204 2282 LaunchTaskBarPlugin * tb = tk->tb;
56f19a82
GP
2283 TaskClass * tc = tk->p_taskclass;
2284 if ((tb->grouped_tasks) && (tc != NULL) && (tc->visible_count > 1))
2285 recompute_group_visibility_for_class(tk->tb, tk->p_taskclass);
2286 else
2287 {
2288 /* Set the flashing context and flash the window immediately. */
2289 tk->flash_state = TRUE;
3899f188 2290 flash_window_update(tk);
56f19a82
GP
2291
2292 /* Set the timer if none is set. */
2293 if (tk->flash_timeout == 0)
2294 set_timer_on_task(tk);
2295 }
2296}
2297
2298/* Clear urgency notification. */
2299static void task_clear_urgency(Task * tk)
2300{
6951c204 2301 LaunchTaskBarPlugin * tb = tk->tb;
56f19a82
GP
2302 TaskClass * tc = tk->p_taskclass;
2303 if ((tb->grouped_tasks) && (tc != NULL) && (tc->visible_count > 1))
2304 recompute_group_visibility_for_class(tk->tb, tk->p_taskclass);
2305 else
2306 {
2307 /* Remove the timer if one is set. */
2308 if (tk->flash_timeout != 0)
2309 {
2310 g_source_remove(tk->flash_timeout);
2311 tk->flash_timeout = 0;
2312 }
2313
2314 /* Clear the flashing context and unflash the window immediately. */
2315 tk->flash_state = FALSE;
3899f188 2316 flash_window_update(tk);
56f19a82
GP
2317 tk->flash_state = FALSE;
2318 }
2319}
2320
2321/* Do the proper steps to raise a window.
2322 * This means removing it from iconified state and bringing it to the front.
2323 * We also switch the active desktop and viewport if needed. */
2324static void task_raise_window(Task * tk, guint32 time)
2325{
2326 /* Change desktop if needed. */
2327 if ((tk->desktop != ALL_WORKSPACES) && (tk->desktop != tk->tb->current_desktop))
2328 Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, tk->desktop, 0, 0, 0, 0);
2329
2330 /* Evaluate use_net_active if not yet done. */
2331 if ( ! tk->tb->net_active_checked)
2332 {
6951c204 2333 LaunchTaskBarPlugin * tb = tk->tb;
56f19a82 2334 GdkAtom net_active_atom = gdk_x11_xatom_to_atom(a_NET_ACTIVE_WINDOW);
6951c204 2335 tb->use_net_active = gdk_x11_screen_supports_net_wm_hint(gtk_widget_get_screen(tb->plugin), net_active_atom);
56f19a82
GP
2336 tb->net_active_checked = TRUE;
2337 }
2338
2339 /* Raise the window. We can use NET_ACTIVE_WINDOW if the window manager supports it.
2340 * Otherwise, do it the old way with XMapRaised and XSetInputFocus. */
2341 if (tk->tb->use_net_active)
2342 Xclimsg(tk->win, a_NET_ACTIVE_WINDOW, 2, time, 0, 0, 0);
2343 else
2344 {
2345 GdkWindow * gdkwindow = gdk_xid_table_lookup(tk->win);
2346 if (gdkwindow != NULL)
2347 gdk_window_show(gdkwindow);
2348 else
2349 XMapRaised(GDK_DISPLAY(), tk->win);
2350
2351 /* There is a race condition between the X server actually executing the XMapRaised and this code executing XSetInputFocus.
2352 * If the window is not viewable, the XSetInputFocus will fail with BadMatch. */
2353 XWindowAttributes attr;
2354 XGetWindowAttributes(GDK_DISPLAY(), tk->win, &attr);
2355 if (attr.map_state == IsViewable)
2356 XSetInputFocus(GDK_DISPLAY(), tk->win, RevertToNone, time);
2357 }
2358
2359 /* Change viewport if needed. */
2360 XWindowAttributes xwa;
2361 XGetWindowAttributes(GDK_DISPLAY(), tk->win, &xwa);
2362 Xclimsg(tk->win, a_NET_DESKTOP_VIEWPORT, xwa.x, xwa.y, 0, 0, 0);
2363}
2364
2365/* Position-calculation callback for grouped-task and window-management popup menu. */
2366static void taskbar_popup_set_position(GtkWidget * menu, gint * px, gint * py, gboolean * push_in, gpointer data)
2367{
2368 Task * tk = (Task *) data;
2369
2370 /* Get the allocation of the popup menu. */
2371 GtkRequisition popup_req;
2372 gtk_widget_size_request(menu, &popup_req);
2373
2374 /* Determine the coordinates. */
6951c204
AG
2375 lxpanel_plugin_popup_set_position_helper(tk->tb->panel, tk->button, menu,
2376 &popup_req, px, py);
56f19a82
GP
2377 *push_in = TRUE;
2378}
2379
4bb5845f
GP
2380/* Handler for "activate" event from "close all windows" menu*/
2381static void taskbar_close_all_windows (GtkWidget * widget, Task * tk )
2382{
2383 Task * tk_cursor;
2384 for (tk_cursor = tk->p_taskclass->p_task_head; tk_cursor != NULL;
2385 tk_cursor = tk_cursor->p_task_flink_same_class)
2386 {
2387 if (task_is_visible_on_current_desktop(tk->tb, tk_cursor))
2388 {
2389 Xclimsgwm(tk_cursor->win, a_WM_PROTOCOLS, a_WM_DELETE_WINDOW);
2390 }
2391 }
2392 task_group_menu_destroy(tk->tb);
2393}
2394
56f19a82 2395/* Remove the grouped-task popup menu from the screen. */
6951c204 2396static void task_group_menu_destroy(LaunchTaskBarPlugin * tb)
56f19a82
GP
2397{
2398 if (tb->group_menu != NULL)
2399 {
2400 gtk_widget_destroy(tb->group_menu);
2401 tb->group_menu = NULL;
2402 }
2403}
2404
2405/* Handler for "button-press-event" event from taskbar button,
2406 * or "activate" event from grouped-task popup menu item. */
2407static gboolean taskbar_task_control_event(GtkWidget * widget, GdkEventButton * event, Task * tk, gboolean popup_menu)
2408{
6951c204 2409 LaunchTaskBarPlugin * tb = tk->tb;
56f19a82
GP
2410 TaskClass * tc = tk->p_taskclass;
2411 if ((tb->grouped_tasks) && (tc != NULL) && (tc->visible_count > 1) && (GTK_IS_BUTTON(widget)))
2412 {
4bb5845f
GP
2413 /* This is grouped-task representative, meaning that there is a class
2414 * with at least two windows. */
2415 GtkWidget * menu = NULL;
2416 if( event->button == 1 ) /* Left click */
56f19a82 2417 {
4bb5845f
GP
2418 menu = gtk_menu_new();
2419 /* Bring up a popup menu listing all the class members. */
2420 Task * tk_cursor;
2421 for (tk_cursor = tc->p_task_head; tk_cursor != NULL;
2422 tk_cursor = tk_cursor->p_task_flink_same_class)
56f19a82 2423 {
4bb5845f
GP
2424 if (task_is_visible_on_current_desktop(tb, tk_cursor))
2425 {
2426 /* The menu item has the name, or the iconified name, and
2427 * the icon of the application window. */
2428 GtkWidget * mi = gtk_image_menu_item_new_with_label(((tk_cursor->iconified) ?
2429 tk_cursor->name_iconified : tk_cursor->name));
2430 GtkWidget * im = gtk_image_new_from_pixbuf(gtk_image_get_pixbuf(
2431 GTK_IMAGE(tk_cursor->image)));
2432 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), im);
6951c204 2433 g_signal_connect(mi, "button-press-event",
4bb5845f
GP
2434 G_CALLBACK(taskbar_popup_activate_event), (gpointer) tk_cursor);
2435 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
2436 }
56f19a82
GP
2437 }
2438 }
4bb5845f
GP
2439 else if(event->button == 3) /* Right click */
2440 {
2441 menu = gtk_menu_new();
2442 GtkWidget * mi = gtk_menu_item_new_with_mnemonic (_("_Close all windows"));
2443 gtk_menu_shell_append ( GTK_MENU_SHELL(menu), mi);
2444 g_signal_connect( mi, "activate", G_CALLBACK(taskbar_close_all_windows), tk);
2445 }
56f19a82
GP
2446
2447 /* Show the menu. Set context so we can find the menu later to dismiss it.
4bb5845f
GP
2448 * Use a position-calculation callback to get the menu nicely
2449 * positioned with respect to the button. */
2450 if (menu) {
2451 gtk_widget_show_all(menu);
2452 tb->group_menu = menu;
2453 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
2454 (GtkMenuPositionFunc) taskbar_popup_set_position, (gpointer) tk,
2455 event->button, event->time);
2456 }
56f19a82
GP
2457 }
2458 else
2459 {
2460 /* Not a grouped-task representative, or entered from the grouped-task popup menu. */
2461 Task * visible_task = (((tk->p_taskclass == NULL) || ( ! tk->tb->grouped_tasks)) ? tk : tk->p_taskclass->p_task_visible);
2462 task_group_menu_destroy(tb);
2463
2464 if (event->button == 1)
2465 {
2466 /* Left button.
2467 * If the task is iconified, raise it.
2468 * If the task is not iconified and has focus, iconify it.
2469 * If the task is not iconified and does not have focus, raise it. */
2470 if (tk->iconified)
2471 task_raise_window(tk, event->time);
2472 else if ((tk->focused) || (tk == tb->focused_previous))
2473 XIconifyWindow(GDK_DISPLAY(), tk->win, DefaultScreen(GDK_DISPLAY()));
2474 else
2475 task_raise_window(tk, event->time);
2476 }
2477 else if (event->button == 2)
2478 {
2479 /* Middle button. Toggle the shaded state of the window. */
2480 Xclimsg(tk->win, a_NET_WM_STATE,
2481 2, /* a_NET_WM_STATE_TOGGLE */
2482 a_NET_WM_STATE_SHADED,
2483 0, 0, 0);
2484 }
fe60665f 2485 else if(event->button == 3)
56f19a82
GP
2486 {
2487 /* Right button. Bring up the window state popup menu. */
f3467dd0 2488 tk->tb->menutask = tk;
59475d30 2489#ifndef DISABLE_MENU
6951c204 2490 LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)tk->tb;
fe60665f 2491 if(ltbp->lb_on)
6d740d5c 2492 {
6951c204
AG
2493 FmFileInfo *fi = f_find_menu_launchbutton_recursive(tk->exec_bin);
2494 LaunchButton *btn = launchbar_exec_bin_exists(ltbp, fi);
2495 /* FIXME: shouldn't we make file info at task button creation? */
8aff4845
GP
2496#ifdef DEBUG_WITH_GPRINTS
2497 g_print("\nTB '%s' right-click, in LB: %c\n", tk->exec_bin, btn != NULL ? 'Y':'N');
2498#endif
fe60665f
GP
2499 if(btn != NULL)
2500 {
6951c204
AG
2501 gtk_widget_set_visible(ltbp->p_menuitem_lock_tbp, FALSE);
2502 gtk_widget_set_visible(ltbp->p_menuitem_unlock_tbp, TRUE);
2503 gtk_widget_set_visible(ltbp->p_menuitem_new_instance, TRUE);
fe60665f
GP
2504 }
2505 else
007abf16 2506 {
6951c204
AG
2507 gtk_widget_set_visible(ltbp->p_menuitem_lock_tbp, fi != NULL);
2508 gtk_widget_set_visible(ltbp->p_menuitem_unlock_tbp, FALSE);
2509 gtk_widget_set_visible(ltbp->p_menuitem_new_instance, fi != NULL);
007abf16 2510 }
6951c204
AG
2511 if (fi)
2512 fm_file_info_unref(fi);
fe60665f
GP
2513 }
2514 else
2515 {
6951c204
AG
2516 gtk_widget_set_visible(ltbp->p_menuitem_lock_tbp, FALSE);
2517 gtk_widget_set_visible(ltbp->p_menuitem_unlock_tbp, FALSE);
2518 gtk_widget_set_visible(ltbp->p_menuitem_new_instance, FALSE);
6d740d5c 2519 }
59475d30 2520#endif
56f19a82
GP
2521 gtk_menu_popup(
2522 GTK_MENU(tb->menu),
2523 NULL, NULL,
2524 (GtkMenuPositionFunc) taskbar_popup_set_position, (gpointer) visible_task,
2525 event->button, event->time);
2526 }
2527 }
2528
2529 /* As a matter of policy, avoid showing selected or prelight states on flat buttons. */
2530 if (tb->flat_button)
2531 gtk_widget_set_state(widget, GTK_STATE_NORMAL);
2532 return TRUE;
2533}
2534
2535/* Handler for "button-press-event" event from taskbar button. */
2536static gboolean taskbar_button_press_event(GtkWidget * widget, GdkEventButton * event, Task * tk)
2537{
2538 return taskbar_task_control_event(widget, event, tk, FALSE);
2539}
2540
2541/* Handler for "activate" event from grouped-task popup menu item. */
2542static gboolean taskbar_popup_activate_event(GtkWidget * widget, GdkEventButton * event, Task * tk)
2543{
2544 return taskbar_task_control_event(widget, event, tk, TRUE);
2545}
2546
2547/* Handler for "drag-motion" timeout. */
2548static gboolean taskbar_button_drag_motion_timeout(Task * tk)
2549{
3899f188
AG
2550 guint time;
2551 if (g_source_is_destroyed(g_main_current_source()))
2552 return FALSE;
2553 time = gtk_get_current_event_time();
56f19a82
GP
2554 task_raise_window(tk, ((time != 0) ? time : CurrentTime));
2555 tk->tb->dnd_delay_timer = 0;
2556 return FALSE;
2557}
2558
2559/* Handler for "drag-motion" event from taskbar button. */
2560static gboolean taskbar_button_drag_motion(GtkWidget * widget, GdkDragContext * drag_context, gint x, gint y, guint time, Task * tk)
2561{
2562 /* Prevent excessive motion notification. */
2563 if (tk->tb->dnd_delay_timer == 0)
2564 tk->tb->dnd_delay_timer = g_timeout_add(DRAG_ACTIVE_DELAY, (GSourceFunc) taskbar_button_drag_motion_timeout, tk);
2565 gdk_drag_status(drag_context, 0, time);
2566 return TRUE;
2567}
2568
2569/* Handler for "drag-leave" event from taskbar button. */
2570static void taskbar_button_drag_leave(GtkWidget * widget, GdkDragContext * drag_context, guint time, Task * tk)
2571{
2572 /* Cancel the timer if set. */
2573 if (tk->tb->dnd_delay_timer != 0)
2574 {
2575 g_source_remove(tk->tb->dnd_delay_timer);
2576 tk->tb->dnd_delay_timer = 0;
2577 }
2578 return;
2579}
2580
2581/* Handler for "enter" event from taskbar button. This indicates that the cursor position has entered the button. */
2582static void taskbar_button_enter(GtkWidget * widget, Task * tk)
2583{
2584 tk->entered_state = TRUE;
2585 if (tk->tb->flat_button)
2586 gtk_widget_set_state(widget, GTK_STATE_NORMAL);
2587 task_draw_label(tk);
2588}
2589
2590/* Handler for "leave" event from taskbar button. This indicates that the cursor position has left the button. */
2591static void taskbar_button_leave(GtkWidget * widget, Task * tk)
2592{
2593 tk->entered_state = FALSE;
2594 task_draw_label(tk);
2595}
2596
2597/* Handler for "scroll-event" event from taskbar button. */
2598static gboolean taskbar_button_scroll_event(GtkWidget * widget, GdkEventScroll * event, Task * tk)
2599{
6951c204 2600 LaunchTaskBarPlugin * tb = tk->tb;
56f19a82
GP
2601 TaskClass * tc = tk->p_taskclass;
2602 if ((tb->use_mouse_wheel)
2603 && (( ! tb->grouped_tasks) || (tc == NULL) || (tc->visible_count == 1)))
2604 {
2605 if ((event->direction == GDK_SCROLL_UP) || (event->direction == GDK_SCROLL_LEFT))
2606 task_raise_window(tk, event->time);
2607 else
2608 XIconifyWindow(GDK_DISPLAY(), tk->win, DefaultScreen(GDK_DISPLAY()));
2609 }
2610 return TRUE;
2611}
2612
2613/* Handler for "size-allocate" event from taskbar button. */
2614static void taskbar_button_size_allocate(GtkWidget * btn, GtkAllocation * alloc, Task * tk)
2615{
2616 if (GTK_WIDGET_REALIZED(btn))
2617 {
2618 /* Get the coordinates of the button. */
2619 int x, y;
2620#if GTK_CHECK_VERSION(2,22,0)
2621 gdk_window_get_origin(gtk_button_get_event_window(GTK_BUTTON(btn)), &x, &y);
2622#else
2623 gdk_window_get_origin(GTK_BUTTON(btn)->event_window, &x, &y);
2624#endif
2625
2626
2627 /* Send a NET_WM_ICON_GEOMETRY property change on the window. */
2628 guint32 data[4];
2629 data[0] = x;
2630 data[1] = y;
2631 data[2] = alloc->width;
2632 data[3] = alloc->height;
2633 XChangeProperty(GDK_DISPLAY(), tk->win,
2634 gdk_x11_get_xatom_by_name("_NET_WM_ICON_GEOMETRY"),
2635 XA_CARDINAL, 32, PropModeReplace, (guchar *) &data, 4);
2636 }
2637}
2638
2639/* Update style on the taskbar when created or after a configuration change. */
6951c204 2640static void taskbar_update_style(LaunchTaskBarPlugin * tb)
56f19a82 2641{
6951c204 2642 icon_grid_set_geometry(tb->tb_icon_grid, panel_get_orientation(tb->panel),
7f782142 2643 ((tb->icons_only) ? tb->icon_size + ICON_ONLY_EXTRA : tb->task_width_max),
6951c204 2644 tb->icon_size, tb->spacing, 0, panel_get_height(tb->panel));
56f19a82
GP
2645}
2646
2647/* Update style on a task button when created or after a configuration change. */
6951c204 2648static void task_update_style(Task * tk, LaunchTaskBarPlugin * tb)
56f19a82
GP
2649{
2650 if (tb->icons_only)
2651 gtk_widget_hide(tk->label);
2652 else
2653 gtk_widget_show(tk->label);
2654
2655 if( tb->flat_button )
2656 {
2657 gtk_toggle_button_set_active((GtkToggleButton*)tk->button, FALSE);
2658 gtk_button_set_relief(GTK_BUTTON(tk->button), GTK_RELIEF_NONE);
2659 }
2660 else
2661 {
2662 gtk_toggle_button_set_active((GtkToggleButton*)tk->button, tk->focused);
2663 gtk_button_set_relief(GTK_BUTTON(tk->button), GTK_RELIEF_NORMAL);
2664 }
2665
2666 task_draw_label(tk);
2667}
2668
2669/* Build graphic elements needed for a task button. */
6951c204 2670static void task_build_gui(LaunchTaskBarPlugin * tb, Task * tk)
56f19a82
GP
2671{
2672 /* NOTE
2673 * 1. the extended mask is sum of taskbar and pager needs
2674 * see bug [ 940441 ] pager loose track of windows
2675 *
2676 * Do not change event mask to gtk windows spawned by this gtk client
2677 * this breaks gtk internals */
6951c204 2678 if (! gdk_window_lookup(tk->win))
56f19a82
GP
2679 XSelectInput(GDK_DISPLAY(), tk->win, PropertyChangeMask | StructureNotifyMask);
2680
2681 /* Allocate a toggle button as the top level widget. */
2682 tk->button = gtk_toggle_button_new();
2683 gtk_container_set_border_width(GTK_CONTAINER(tk->button), 0);
2684 gtk_drag_dest_set(tk->button, 0, NULL, 0, 0);
2685
2686 /* Connect signals to the button. */
8488d13c 2687 g_signal_connect(tk->button, "button-press-event", G_CALLBACK(taskbar_button_press_event), (gpointer) tk);
56f19a82
GP
2688 g_signal_connect(G_OBJECT(tk->button), "drag-motion", G_CALLBACK(taskbar_button_drag_motion), (gpointer) tk);
2689 g_signal_connect(G_OBJECT(tk->button), "drag-leave", G_CALLBACK(taskbar_button_drag_leave), (gpointer) tk);
2690 g_signal_connect_after(G_OBJECT (tk->button), "enter", G_CALLBACK(taskbar_button_enter), (gpointer) tk);
2691 g_signal_connect_after(G_OBJECT (tk->button), "leave", G_CALLBACK(taskbar_button_leave), (gpointer) tk);
2692 g_signal_connect_after(G_OBJECT(tk->button), "scroll-event", G_CALLBACK(taskbar_button_scroll_event), (gpointer) tk);
2693 g_signal_connect(tk->button, "size-allocate", G_CALLBACK(taskbar_button_size_allocate), (gpointer) tk);
2694
2695 /* Create a box to contain the application icon and window title. */
2696 GtkWidget * container = gtk_hbox_new(FALSE, 1);
2697 gtk_container_set_border_width(GTK_CONTAINER(container), 0);
2698
2699 /* Create an image to contain the application icon and add it to the box. */
2700 GdkPixbuf* pixbuf = task_update_icon(tb, tk, None);
2701 tk->image = gtk_image_new_from_pixbuf(pixbuf);
2702 gtk_misc_set_padding(GTK_MISC(tk->image), 0, 0);
2703 g_object_unref(pixbuf);
2704 gtk_widget_show(tk->image);
2705 gtk_box_pack_start(GTK_BOX(container), tk->image, FALSE, FALSE, 0);
2706
2707 /* Create a label to contain the window title and add it to the box. */
2708 tk->label = gtk_label_new(NULL);
2709 gtk_misc_set_alignment(GTK_MISC(tk->label), 0.0, 0.5);
2710 gtk_label_set_ellipsize(GTK_LABEL(tk->label), PANGO_ELLIPSIZE_END);
2711 gtk_box_pack_start(GTK_BOX(container), tk->label, TRUE, TRUE, 0);
2712
2713 /* Add the box to the button. */
2714 gtk_widget_show(container);
2715 gtk_container_add(GTK_CONTAINER(tk->button), container);
2716 gtk_container_set_border_width(GTK_CONTAINER(tk->button), 0);
2717
2718 /* Add the button to the taskbar. */
6951c204 2719 icon_grid_add(tb->tb_icon_grid, tk->button, TRUE);
56f19a82
GP
2720#if GTK_CHECK_VERSION(2,18,0)
2721 gtk_widget_set_can_focus(GTK_WIDGET(tk->button),FALSE);
2722 gtk_widget_set_can_default(GTK_WIDGET(tk->button),FALSE);
2723#else
2724 GTK_WIDGET_UNSET_FLAGS(tk->button, GTK_CAN_FOCUS);
2725 GTK_WIDGET_UNSET_FLAGS(tk->button, GTK_CAN_DEFAULT);
2726#endif
2727
2728 /* Update styles on the button. */
2729 task_update_style(tk, tb);
2730
2731 /* Flash button for window with urgency hint. */
2732 if (tk->urgency)
2733 task_set_urgency(tk);
2734}
2735
4bb5845f
GP
2736/* Determine which monitor a given window is associated with */
2737static gint get_window_monitor(Window win)
2738{
2739 GdkDisplay *display;
2740 GdkWindow *gwin;
2741 gint m;
2742
2743 display = gdk_display_get_default();
2744 g_assert(display);
2745 gwin = gdk_x11_window_foreign_new_for_display(display,win);
2746 g_assert(gwin);
2747 m = gdk_screen_get_monitor_at_window(gdk_window_get_screen(gwin),gwin);
2748 gdk_window_unref(gwin);
2749 return m;
2750}
2751
56f19a82
GP
2752/*****************************************************
2753 * handlers for NET actions *
2754 *****************************************************/
2755
2756/* Handler for "client-list" event from root window listener. */
6951c204 2757static void taskbar_net_client_list(GtkWidget * widget, LaunchTaskBarPlugin * tb)
56f19a82 2758{
6951c204 2759 LaunchTaskBarPlugin *ltbp = tb;
b542e451 2760 if(!ltbp->tb_on) return;
443a4c75 2761
56f19a82
GP
2762 /* Get the NET_CLIENT_LIST property. */
2763 int client_count;
2764 Window * client_list = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_CLIENT_LIST, XA_WINDOW, &client_count);
2765 if (client_list != NULL)
2766 {
2767 /* Loop over client list, correlating it with task list. */
2768 int i;
2769 for (i = 0; i < client_count; i++)
2770 {
2771 /* Search for the window in the task list. Set up context to do an insert right away if needed. */
2772 Task * tk_pred = NULL;
2773 Task * tk_cursor;
2774 Task * tk = NULL;
2775 for (tk_cursor = tb->p_task_list; tk_cursor != NULL; tk_pred = tk_cursor, tk_cursor = tk_cursor->p_task_flink_xwid)
2776 {
2777 if (tk_cursor->win == client_list[i])
2778 {
2779 tk = tk_cursor;
2780 break;
2781 }
2782 if (tk_cursor->win > client_list[i])
2783 break;
2784 }
2785
2786 /* Task is already in task list. */
2787 if (tk != NULL)
2788 tk->present_in_client_list = TRUE;
2789
2790 /* Task is not in task list. */
2791 else
2792 {
2793 /* Evaluate window state and window type to see if it should be in task list. */
2794 NetWMWindowType nwwt;
2795 NetWMState nws;
2796 get_net_wm_state(client_list[i], &nws);
2797 get_net_wm_window_type(client_list[i], &nwwt);
2798 if ((accept_net_wm_state(&nws))
2799 && (accept_net_wm_window_type(&nwwt)))
2800 {
2801 /* Allocate and initialize new task structure. */
2802 tk = g_new0(Task, 1);
2803 tk->present_in_client_list = TRUE;
2804 tk->win = client_list[i];
2805 tk->tb = tb;
2806 tk->name_source = None;
2807 tk->image_source = None;
2808 tk->iconified = (get_wm_state(tk->win) == IconicState);
2809 tk->desktop = get_net_wm_desktop(tk->win);
4bb5845f
GP
2810 if (tb->same_monitor_only)
2811 tk->monitor = get_window_monitor(tk->win);
56f19a82
GP
2812 if (tb->use_urgency_hint)
2813 tk->urgency = task_has_urgency(tk);
2814 task_build_gui(tb, tk);
2815 task_set_names(tk, None);
2816 task_set_class(tk);
2817
2818 /* Link the task structure into the task list. */
2819 if (tk_pred == NULL)
2820 {
2821 tk->p_task_flink_xwid = tb->p_task_list;
2822 tb->p_task_list = tk;
2823 }
2824 else
2825 {
2826 tk->p_task_flink_xwid = tk_pred->p_task_flink_xwid;
2827 tk_pred->p_task_flink_xwid = tk;
2828 }
2829 }
2830 }
2831 }
2832 XFree(client_list);
2833 }
2834
2835 /* Remove windows from the task list that are not present in the NET_CLIENT_LIST. */
2836 Task * tk_pred = NULL;
2837 Task * tk = tb->p_task_list;
2838 while (tk != NULL)
2839 {
2840 Task * tk_succ = tk->p_task_flink_xwid;
2841 if (tk->present_in_client_list)
2842 {
2843 tk->present_in_client_list = FALSE;
2844 tk_pred = tk;
2845 }
2846 else
2847 {
2848 if (tk_pred == NULL)
2849 tb->p_task_list = tk_succ;
2850 else tk_pred->p_task_flink_xwid = tk_succ;
2851 task_delete(tb, tk, FALSE);
2852 }
2853 tk = tk_succ;
2854 }
2855
2856 /* Redraw the taskbar. */
2857 taskbar_redraw(tb);
2858}
2859
2860/* Handler for "current-desktop" event from root window listener. */
6951c204 2861static void taskbar_net_current_desktop(GtkWidget * widget, LaunchTaskBarPlugin * tb)
56f19a82 2862{
6951c204 2863 LaunchTaskBarPlugin *ltbp = tb;
b542e451 2864 if(!ltbp->tb_on) return;
443a4c75 2865
56f19a82
GP
2866 /* Store the local copy of current desktops. Redisplay the taskbar. */
2867 tb->current_desktop = get_net_current_desktop();
2868 recompute_group_visibility_on_current_desktop(tb);
2869 taskbar_redraw(tb);
2870}
2871
2872/* Handler for "number-of-desktops" event from root window listener. */
6951c204 2873static void taskbar_net_number_of_desktops(GtkWidget * widget, LaunchTaskBarPlugin * tb)
56f19a82 2874{
6951c204 2875 LaunchTaskBarPlugin *ltbp = tb;
b542e451 2876 if(!ltbp->tb_on) return;
443a4c75 2877
56f19a82
GP
2878 /* Store the local copy of number of desktops. Recompute the popup menu and redisplay the taskbar. */
2879 tb->number_of_desktops = get_net_number_of_desktops();
2880 taskbar_make_menu(tb);
2881 taskbar_redraw(tb);
2882}
2883
2884/* Handler for "active-window" event from root window listener. */
6951c204 2885static void taskbar_net_active_window(GtkWidget * widget, LaunchTaskBarPlugin * tb)
56f19a82 2886{
6951c204 2887 LaunchTaskBarPlugin *ltbp = tb;
b542e451 2888 if(!ltbp->tb_on) return;
443a4c75 2889
56f19a82
GP
2890 gboolean drop_old = FALSE;
2891 gboolean make_new = FALSE;
2892 Task * ctk = tb->focused;
2893 Task * ntk = NULL;
2894
2895 /* Get the window that has focus. */
2896 Window * f = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_ACTIVE_WINDOW, XA_WINDOW, 0);
2897 if (f == NULL)
2898 {
2899 /* No window has focus. */
2900 drop_old = TRUE;
2901 tb->focused_previous = NULL;
2902 }
2903 else
2904 {
6951c204 2905 if (*f == panel_get_xwindow(tb->panel))
56f19a82
GP
2906 {
2907 /* Taskbar window gained focus (this isn't supposed to be able to happen). Remember current focus. */
2908 if (ctk != NULL)
2909 {
2910 tb->focused_previous = ctk;
2911 drop_old = TRUE;
2912 }
2913 }
2914 else
2915 {
2916 /* Identify task that gained focus. */
2917 tb->focused_previous = NULL;
2918 ntk = task_lookup(tb, *f);
2919 if (ntk != ctk)
2920 {
2921 drop_old = TRUE;
2922 make_new = TRUE;
2923 }
2924 }
2925 XFree(f);
2926 }
2927
2928 /* If our idea of the current task lost focus, update data structures. */
2929 if ((ctk != NULL) && (drop_old))
2930 {
2931 ctk->focused = FALSE;
2932 tb->focused = NULL;
2933 if(!tb->flat_button) /* relieve the button if flat buttons is not used. */
2934 gtk_toggle_button_set_active((GtkToggleButton*)ctk->button, FALSE);
2935
2936 task_button_redraw(ctk, tb);
2937 }
2938
2939 /* If a task gained focus, update data structures. */
2940 if ((ntk != NULL) && (make_new))
2941 {
2942 if(!tb->flat_button) /* depress the button if flat buttons is not used. */
2943 gtk_toggle_button_set_active((GtkToggleButton*)ntk->button, TRUE);
2944 ntk->focused = TRUE;
2945 tb->focused = ntk;
2946 task_button_redraw(ntk, tb);
2947 }
2948}
2949
2950/* Determine if the "urgency" hint is set on a window. */
2951static gboolean task_has_urgency(Task * tk)
2952{
2953 gboolean result = FALSE;
2954 XWMHints * hints = (XWMHints *) get_xaproperty(tk->win, XA_WM_HINTS, XA_WM_HINTS, 0);
2955 if (hints != NULL)
2956 {
2957 if (hints->flags & XUrgencyHint)
2958 result = TRUE;
2959 XFree(hints);
2960 }
2961 return result;
2962}
2963
2964/* Handle PropertyNotify event.
2965 * http://tronche.com/gui/x/icccm/
2966 * http://standards.freedesktop.org/wm-spec/wm-spec-1.4.html */
6951c204 2967static void taskbar_property_notify_event(LaunchTaskBarPlugin *tb, XEvent *ev)
56f19a82
GP
2968{
2969 /* State may be PropertyNewValue, PropertyDeleted. */
2970 if (((XPropertyEvent*) ev)->state == PropertyNewValue)
2971 {
2972 Atom at = ev->xproperty.atom;
2973 Window win = ev->xproperty.window;
2974 if (win != GDK_ROOT_WINDOW())
2975 {
2976 /* Look up task structure by X window handle. */
2977 Task * tk = task_lookup(tb, win);
2978 if (tk != NULL)
2979 {
2980 /* Install an error handler that ignores BadWindow.
2981 * We frequently get a PropertyNotify event on deleted windows. */
2982 XErrorHandler previous_error_handler = XSetErrorHandler(panel_handle_x_error_swallow_BadWindow_BadDrawable);
2983
2984 /* Dispatch on atom. */
2985 if (at == a_NET_WM_DESKTOP)
2986 {
2987 /* Window changed desktop. */
2988 tk->desktop = get_net_wm_desktop(win);
2989 taskbar_redraw(tb);
2990 }
2991 else if ((at == XA_WM_NAME) || (at == a_NET_WM_NAME) || (at == a_NET_WM_VISIBLE_NAME))
2992 {
2993 /* Window changed name. */
2994 task_set_names(tk, at);
2995 if (tk->p_taskclass != NULL)
2996 {
2997 /* A change to the window name may change the visible name of the class. */
2998 recompute_group_visibility_for_class(tb, tk->p_taskclass);
2999 if (tk->p_taskclass->p_task_visible != NULL)
3000 task_draw_label(tk->p_taskclass->p_task_visible);
3001 }
3002 }
3003 else if (at == XA_WM_CLASS)
3004 {
3005 /* Window changed class. */
3006 task_set_class(tk);
3007 taskbar_redraw(tb);
3008 }
3009 else if (at == a_WM_STATE)
3010 {
3011 /* Window changed state. */
3012 tk->iconified = (get_wm_state(win) == IconicState);
3013 task_draw_label(tk);
3014 }
3015 else if (at == XA_WM_HINTS)
3016 {
3017 /* Window changed "window manager hints".
3018 * Some windows set their WM_HINTS icon after mapping. */
3019 GdkPixbuf * pixbuf = task_update_icon(tb, tk, XA_WM_HINTS);
3020 if (pixbuf != NULL)
3021 {
3022 gtk_image_set_from_pixbuf(GTK_IMAGE(tk->image), pixbuf);
3023 g_object_unref(pixbuf);
3024 }
3025
3026 if (tb->use_urgency_hint)
3027 {
3028 tk->urgency = task_has_urgency(tk);
3029 if (tk->urgency)
3030 task_set_urgency(tk);
3031 else
3032 task_clear_urgency(tk);
3033 }
3034 }
3035 else if (at == a_NET_WM_STATE)
3036 {
3037 /* Window changed EWMH state. */
3038 NetWMState nws;
3039 get_net_wm_state(tk->win, &nws);
3040 if ( ! accept_net_wm_state(&nws))
3041 {
3042 task_delete(tb, tk, TRUE);
3043 taskbar_redraw(tb);
3044 }
3045 }
3046 else if (at == a_NET_WM_ICON)
3047 {
3048 /* Window changed EWMH icon. */
3049 GdkPixbuf * pixbuf = task_update_icon(tb, tk, a_NET_WM_ICON);
3050 if (pixbuf != NULL)
3051 {
3052 gtk_image_set_from_pixbuf(GTK_IMAGE(tk->image), pixbuf);
3053 g_object_unref(pixbuf);
3054 }
3055 }
3056 else if (at == a_NET_WM_WINDOW_TYPE)
3057 {
3058 /* Window changed EWMH window type. */
3059 NetWMWindowType nwwt;
3060 get_net_wm_window_type(tk->win, &nwwt);
3061 if ( ! accept_net_wm_window_type(&nwwt))
3062 {
3063 task_delete(tb, tk, TRUE);
3064 taskbar_redraw(tb);
3065 }
3066 }
3067 XSetErrorHandler(previous_error_handler);
3068 }
3069 }
3070 }
3071}
3072
4bb5845f 3073/* Handle ConfigureNotify events */
6951c204 3074static void taskbar_configure_notify_event(LaunchTaskBarPlugin * tb, XConfigureEvent * ev)
4bb5845f
GP
3075{
3076 /* If the same_monitor_only option is set and the window is on a different
3077 monitor than before, redraw the taskbar */
3078 Task *task;
3079 gint m;
3080 if(tb->same_monitor_only && ev->window != GDK_ROOT_WINDOW())
3081 {
3082 task = task_lookup(tb, ev->window);
3083 if(task)
3084 {
3085 /* Deleted windows seem to get ConfigureNotify events too. */
3086 XErrorHandler previous_error_handler = XSetErrorHandler(panel_handle_x_error_swallow_BadWindow_BadDrawable);
3087
3088 m = get_window_monitor(task->win);
3089 if(m != task->monitor)
3090 {
3091 task->monitor = m;
3092 taskbar_redraw(tb);
3093 }
3094
3095 XSetErrorHandler(previous_error_handler);
3096 }
3097 }
3098}
3099
56f19a82 3100/* GDK event filter. */
6951c204 3101static GdkFilterReturn taskbar_event_filter(XEvent * xev, GdkEvent * event, LaunchTaskBarPlugin * tb)
56f19a82 3102{
6951c204 3103 LaunchTaskBarPlugin *ltbp = tb;
b542e451 3104 if(!ltbp->tb_on) return GDK_FILTER_CONTINUE;
443a4c75 3105
56f19a82
GP
3106 if (xev->type == PropertyNotify)
3107 taskbar_property_notify_event(tb, xev);
4bb5845f
GP
3108 else if (xev->type == ConfigureNotify)
3109 taskbar_configure_notify_event(tb, &xev->xconfigure);
3110
56f19a82
GP
3111 return GDK_FILTER_CONTINUE;
3112}
3113
3114/* Handler for "activate" event on Raise item of right-click menu for task buttons. */
6951c204 3115static void menu_raise_window(GtkWidget * widget, LaunchTaskBarPlugin * tb)
56f19a82
GP
3116{
3117 if ((tb->menutask->desktop != ALL_WORKSPACES) && (tb->menutask->desktop != tb->current_desktop))
3118 Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, tb->menutask->desktop, 0, 0, 0, 0);
3119 XMapRaised(GDK_DISPLAY(), tb->menutask->win);
3120 task_group_menu_destroy(tb);
3121}
3122
3123/* Handler for "activate" event on Restore item of right-click menu for task buttons. */
6951c204 3124static void menu_restore_window(GtkWidget * widget, LaunchTaskBarPlugin * tb)
56f19a82
GP
3125{
3126 GdkWindow * win = gdk_window_foreign_new(tb->menutask->win);
3127 gdk_window_unmaximize(win);
3128 gdk_window_unref(win);
3129 task_group_menu_destroy(tb);
3130}
3131
3132/* Handler for "activate" event on Maximize item of right-click menu for task buttons. */
6951c204 3133static void menu_maximize_window(GtkWidget * widget, LaunchTaskBarPlugin * tb)
56f19a82
GP
3134{
3135 GdkWindow * win = gdk_window_foreign_new(tb->menutask->win);
3136 gdk_window_maximize(win);
3137 gdk_window_unref(win);
3138 task_group_menu_destroy(tb);
3139}
3140
3141/* Handler for "activate" event on Iconify item of right-click menu for task buttons. */
6951c204 3142static void menu_iconify_window(GtkWidget * widget, LaunchTaskBarPlugin * tb)
56f19a82
GP
3143{
3144 XIconifyWindow(GDK_DISPLAY(), tb->menutask->win, DefaultScreen(GDK_DISPLAY()));
3145 task_group_menu_destroy(tb);
3146}
3147
3148/* Handler for "activate" event on Move to Workspace item of right-click menu for task buttons. */
6951c204 3149static void menu_move_to_workspace(GtkWidget * widget, LaunchTaskBarPlugin * tb)
56f19a82
GP
3150{
3151 int num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "num"));
3152 Xclimsg(tb->menutask->win, a_NET_WM_DESKTOP, num, 0, 0, 0, 0);
3153 task_group_menu_destroy(tb);
3154}
3155
3156/* Handler for "activate" event on Close item of right-click menu for task buttons. */
6951c204 3157static void menu_close_window(GtkWidget * widget, LaunchTaskBarPlugin * tb)
56f19a82
GP
3158{
3159 Xclimsgwm(tb->menutask->win, a_WM_PROTOCOLS, a_WM_DELETE_WINDOW);
3160 task_group_menu_destroy(tb);
3161}
3162
59475d30 3163#ifndef DISABLE_MENU
6951c204 3164static void on_menuitem_lock_tbp_clicked(GtkWidget * widget, LaunchTaskBarPlugin * tb)
6d740d5c 3165{
6951c204 3166 FmFileInfo *fi = f_find_menu_launchbu