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