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