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