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