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