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