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