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