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