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