Convert taskbar button into a widget TaskButton (from GtkToggleButton).
authorAndriy Grytsenko <andrej@rep.kiev.ua>
Thu, 17 Mar 2016 22:17:47 +0000 (00:17 +0200)
committerAndriy Grytsenko <andrej@rep.kiev.ua>
Thu, 17 Mar 2016 22:17:47 +0000 (00:17 +0200)
This conversion not just makes code more readable but also maked drag&drop
and menus operations more smooth and predictable.

ChangeLog
configure.ac
plugins/Makefile.am
plugins/launchtaskbar.c
plugins/task-button.c [new file with mode: 0644]
plugins/task-button.h [new file with mode: 0644]
po/POTFILES.in

index c82dcec..56ca8dc 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,8 @@
 * Bunch of fixes for GTK+ 3.0 compatibility.
 * Fixed outdated lxpanel man page.
 * Fixed weather locations query (since YQL was changed recently).
+* Converted space plugin into internal PanelSpace widget in liblxpanel.
+* Converted task button into widget class to avoid ambiguous code.
 
 0.8.2
 -------------------------------------------------------------------------
index 3f4880b..4ba5d85 100644 (file)
@@ -1,5 +1,5 @@
 AC_PREREQ(2.53)
-AC_INIT(lxpanel, 0.8.1, http://lxde.org/)
+AC_INIT(lxpanel, 0.8.99, http://lxde.org/)
 AM_INIT_AUTOMAKE([-Wall foreign subdir-objects no-dist-gzip dist-xz])
 AC_CONFIG_HEADER([config.h])
 AC_CONFIG_MACRO_DIR([m4])
index d6cb000..2a3b752 100644 (file)
@@ -35,6 +35,7 @@ PLUGINS_SOURCES = \
        dclock.c \
        dirmenu.c \
        launchtaskbar.c \
+       task-button.c \
        pager.c \
        separator.c \
        tray.c \
@@ -327,6 +328,7 @@ EXTRA_DIST = \
        xkb/xkb.h \
        $(flags_DATA) \
        $(xkeyboardconfig_DATA) \
+       task-button.h \
        icon.xpm
 
 install-exec-hook:
index 22d197f..a58755a 100644 (file)
@@ -12,7 +12,7 @@
  *               2012-2014 Giuseppe Penone <giuspen@gmail.com>
  *               2013 Vincenzo di Cicco <enzodicicco@gmail.com>
  *               2013 Rouslan <rouslan-k@users.sourceforge.net>
- *               2014-2015 Andriy Grytsenko <andrej@rep.kiev.ua>
+ *               2014-2016 Andriy Grytsenko <andrej@rep.kiev.ua>
  *               2014 Vladimír Pýcha <vpycha@gmail.com>
  *               2014 Raimar Bühmann <raimar@buehmann.de>
  *               2014 Andy Balaam <axis3x3@users.sf.net>
@@ -77,7 +77,7 @@
 #include "misc.h"
 #include "ev.h"
 #include "plugin.h"
-#include "icon.xpm"
+#include "task-button.h"
 #include "icon-grid.h"
 #ifndef DISABLE_MENU
 # include "menu-policy.h"
@@ -103,43 +103,6 @@ typedef enum {
 
 typedef struct LaunchTaskBarPlugin LaunchTaskBarPlugin;
 
-/* Structure representing a class.  This comes from WM_CLASS, and should identify windows that come from an application. */
-typedef struct _task_class {
-    struct _task_class *p_taskclass_flink; /* Forward link */
-    char * res_class;                      /* Class name */
-    struct _task * p_task_head;            /* Head of list of tasks with this class */
-    struct _task * p_task_visible;         /* Task that is visible in current desktop, if any */
-    char * visible_name;                   /* Name that will be visible for grouped tasks */
-    int visible_count;                     /* Count of tasks that are visible in current desktop */
-} TaskClass;
-
-/* Structure representing a "task", an open window. */
-typedef struct _task {
-    struct _task * p_task_flink_xwid;       /* Forward link to next task in X window ID order */
-    LaunchTaskBarPlugin * tb;               /* Back pointer to plugin */
-    Window win;                             /* X window ID */
-    char * name;                            /* Taskbar label when normal, from WM_NAME or NET_WM_NAME */
-    char * name_iconified;                  /* Taskbar label when iconified */
-    char * exec_bin;                        /* Exec bin associated to Window */
-    Atom name_source;                       /* Atom that is the source of taskbar label */
-    TaskClass * p_taskclass;                /* Class, from WM_CLASS */
-    struct _task * p_task_flink_same_class; /* Forward link to task in same class */
-    GtkWidget * button;                     /* Button representing task in taskbar */
-    GtkWidget * image;                      /* Icon for task, child of button */
-    Atom image_source;                      /* Atom that is the source of taskbar icon */
-    GtkWidget * label;                      /* Label for task, child of button */
-    GtkWidget * menu_item;                  /* Menu item for grouped task after click */
-    gint desktop;                           /* Desktop that contains task, needed to switch to it on Raise */
-    gint monitor;                           /* Monitor that the window is on or closest to */
-    guint flash_timeout;                    /* Timer for urgency notification */
-    unsigned int focused                :1; /* True if window has focus */
-    unsigned int iconified              :1; /* True if window is iconified, from WM_STATE */
-    unsigned int urgency                :1; /* True if window has an urgency hint, from WM_HINTS */
-    unsigned int flash_state            :1; /* One-bit counter to flash taskbar */
-    unsigned int entered_state          :1; /* True if cursor is inside taskbar button */
-    unsigned int present_in_client_list :1; /* State during WM_CLIENT_LIST processing to detect deletions */
-} Task; /* FIXME: convert it into GtkWidget, eliminate button and menu_item */
-
 /* Representative of one launch button.
  * Note that the launch parameters come from the specified desktop file, or from the configuration file.
  * This structure is also used during the "add to launchtaskbar" dialog to hold menu items. */
@@ -159,36 +122,21 @@ struct LaunchTaskBarPlugin {
     LaunchButton  *bootstrap_button; /* Bootstrapping button for empty launchtaskbar */
     GtkWidget     *p_button_add, *p_button_remove, *p_label_menu_app_exec, *p_label_def_app_exec;
     /* TASKBAR */
-    Task * p_task_list;            /* List of tasks to be displayed in taskbar */
-    TaskClass * p_taskclass_list;  /* Window class list */
     GtkWidget * tb_icon_grid;      /* Manager for taskbar buttons */
-    GtkWidget * menu;              /* Popup menu for task control (Close, Raise, etc.) */
-    GtkWidget * group_menu;        /* Popup menu for grouping selection */
-    GtkWidget * workspace_menu0;   /* "Workspace 1" menu item */
-    GdkPixbuf * fallback_pixbuf;   /* Fallback task icon when none is available */
     int number_of_desktops;        /* Number of desktops, from NET_WM_NUMBER_OF_DESKTOPS */
     int current_desktop;           /* Current desktop, from NET_WM_CURRENT_DESKTOP */
-    Task * focused;                /* Task that has focus */
-    Task * focused_previous;       /* Task that had focus just before panel got it */
-    Task * menutask;               /* Task for which popup menu is open */
     guint dnd_delay_timer;         /* Timer for drag and drop delay */
     gboolean dnd_task_moving;      /* User is currently moving a task button */
     int icon_size;                 /* Size of task icons */
-    gboolean show_all_desks;       /* User preference: show windows from all desktops */
-    gboolean tooltips;             /* User preference: show tooltips */
-    gboolean icons_only;           /* User preference: show icons only, omit name */
-    gboolean use_mouse_wheel;      /* User preference: scroll wheel does iconify and raise */
-    gboolean use_urgency_hint;     /* User preference: windows with urgency will flash */
-    gboolean flat_button;          /* User preference: taskbar buttons have visible background */
     gboolean grouped_tasks;        /* User preference: windows from same task are grouped onto a single button */
-    gboolean same_monitor_only;    /* User preference: only show windows that are in the same monitor as the taskbar */
-    gboolean disable_taskbar_upscale; /* User preference: don't upscale taskbar icons */
+    TaskShowFlags flags;        /* User preferences flags */
     int task_width_max;            /* Maximum width of a taskbar button in horizontal orientation */
     int spacing;                   /* Spacing between taskbar buttons */
-    gboolean use_net_active;       /* NET_WM_ACTIVE_WINDOW is supported by the window manager */
-    gboolean net_active_checked;   /* True if use_net_active is valid */
+    guint flash_timeout;        /* Timer for urgency notification */
+    gboolean flash_state;       /* One-bit counter to flash taskbar */
     /* COMMON */
 #ifndef DISABLE_MENU
+    FmFileInfo * fi;            /* Current menu item file info */
     GtkWidget       *p_menuitem_lock_tbp;
     GtkWidget       *p_menuitem_unlock_tbp;
     GtkWidget       *p_menuitem_new_instance;
@@ -229,51 +177,72 @@ static gchar *launchtaskbar_rc = "style 'launchtaskbar-style' = 'theme-panel'\n"
 static void launchtaskbar_destructor(gpointer user_data);
 
 static void taskbar_redraw(LaunchTaskBarPlugin * tb);
-static void task_delete(LaunchTaskBarPlugin * tb, Task * tk, gboolean unlink, gboolean remove);
-static GdkPixbuf * task_update_icon(LaunchTaskBarPlugin * tb, Task * tk, Atom source);
-static void flash_window_update(Task * tk);
-static void taskbar_button_enter(GtkWidget * widget, Task * tk);
-static void taskbar_button_leave(GtkWidget * widget, Task * tk);
-static gboolean flash_window_timeout(gpointer tk);
-static void task_group_menu_destroy(LaunchTaskBarPlugin * tb);
-static gboolean taskbar_popup_activate_event(GtkWidget * widget, GdkEventButton * event, Task * tk);
-static void taskbar_update_style(LaunchTaskBarPlugin * tb);
 static void taskbar_net_client_list(GtkWidget * widget, LaunchTaskBarPlugin * tb);
 static void taskbar_net_current_desktop(GtkWidget * widget, LaunchTaskBarPlugin * tb);
 static void taskbar_net_number_of_desktops(GtkWidget * widget, LaunchTaskBarPlugin * tb);
 static void taskbar_net_active_window(GtkWidget * widget, LaunchTaskBarPlugin * tb);
-static gboolean task_has_urgency(Task * tk);
 static GdkFilterReturn taskbar_event_filter(XEvent * xev, GdkEvent * event, LaunchTaskBarPlugin * tb);
-static void taskbar_make_menu(LaunchTaskBarPlugin * tb);
 static void taskbar_window_manager_changed(GdkScreen * screen, LaunchTaskBarPlugin * tb);
 static void taskbar_apply_configuration(LaunchTaskBarPlugin * ltbp);
+static void taskbar_add_task_button(LaunchTaskBarPlugin * tb, TaskButton * task);
 
-static void f_get_exec_cmd_from_pid(GPid pid, gchar *buffer_128, const gchar *proc_file)
-{
-    buffer_128[0] = '\0';
-    FILE *pipe;
-    gchar  command[64];
-    snprintf(command, 64, "cat /proc/%u/%s", pid, proc_file);
-    pipe = popen(command, "r");
-    if(pipe == NULL)
-        g_warning("ltbp: popen '%s'", command);
-    else if(fgets(buffer_128, 128, pipe) == NULL)
-        g_warning("ltbp: fgets '%s'", command);
-    else
+#define taskbar_reset_menu(tb) if (tb->tb_built) task_button_reset_menu(tb->tb_icon_grid)
+
+
+#ifndef DISABLE_MENU
+static char *task_get_cmdline(Window win, LaunchTaskBarPlugin *ltbp)
+{
+    GPid pid = get_net_wm_pid(win);
+    char proc_path[64];
+    gchar *cmdline = NULL;
+    gchar *p_char = NULL;
+
+    snprintf(proc_path, sizeof(proc_path),
+             G_DIR_SEPARATOR_S "proc" G_DIR_SEPARATOR_S "%lu" G_DIR_SEPARATOR_S "cmdline",
+             (gulong)pid);
+    g_file_get_contents(proc_path, &cmdline, NULL, NULL);
+    if (cmdline)
     {
-        gchar *p_char = strchr(buffer_128, '\n');
+        p_char = strchr(cmdline, '\n');
         if(p_char != NULL) *p_char = '\0';
+        p_char = strrchr(cmdline, G_DIR_SEPARATOR);
+        if (p_char != NULL) p_char++;
+        else p_char = cmdline;
+        if(strcmp(p_char, "python") == 0)
+        {
+            snprintf(proc_path, sizeof(proc_path),
+                     G_DIR_SEPARATOR_S "proc" G_DIR_SEPARATOR_S "%lu" G_DIR_SEPARATOR_S "comm",
+                     (gulong)pid);
+            g_free(cmdline);
+            cmdline = NULL;
+            g_file_get_contents(proc_path, &cmdline, NULL, NULL);
+            if (cmdline)
+            {
+                p_char = strchr(cmdline, '\n');
+                if(p_char != NULL) *p_char = '\0';
+            }
+        }
+        else
+        {
+            p_char = g_key_file_get_string(ltbp->p_key_file_special_cases,
+                                           "special_cases", p_char, NULL);
+            if (p_char != NULL) /* found this key */
+            {
+                g_free(cmdline);
+                cmdline = p_char;
+            }
+        }
     }
-    if(pipe != NULL) pclose(pipe);
+    return cmdline;
 }
 
-#ifndef DISABLE_MENU
-static FmFileInfo *f_find_menu_launchbutton_recursive(const char *exec_bin)
+static FmFileInfo *f_find_menu_launchbutton_recursive(Window win, LaunchTaskBarPlugin *ltbp)
 {
     MenuCache *mc;
     guint32 flags;
     GSList *apps, *l;
     size_t len;
+    char *exec_bin = task_get_cmdline(win, ltbp);
     const char *exec, *short_exec;
     char *str_path;
     FmPath *path;
@@ -340,6 +309,7 @@ static FmFileInfo *f_find_menu_launchbutton_recursive(const char *exec_bin)
     g_slist_free(apps);
     menu_cache_unref(mc);
     g_debug("f_find_menu_launchbutton_recursive: search '%s' found=%d", exec_bin, (fi != NULL));
+    g_free(exec_bin);
     return fi;
 }
 #endif
@@ -437,55 +407,6 @@ static LaunchButton *launchbar_exec_bin_exists(LaunchTaskBarPlugin *lb, FmFileIn
 }
 #endif
 
-static void launchbar_update_after_taskbar_class_added(LaunchTaskBarPlugin *ltbp, Task *tk)
-{
-    GPid   pid = get_net_wm_pid(tk->win);
-    gchar  exec_bin_full[128];
-    f_get_exec_cmd_from_pid(pid, exec_bin_full, "cmdline");
-    gchar *p_char = strrchr(exec_bin_full, '/');
-    if(p_char == NULL) p_char = exec_bin_full;
-    else p_char++;
-    g_free(tk->exec_bin);
-    if(strcmp(p_char, "python") == 0)
-    {
-        f_get_exec_cmd_from_pid(pid, exec_bin_full, "comm");
-    }
-    else
-    {
-        tk->exec_bin = g_key_file_get_string(ltbp->p_key_file_special_cases,
-                                             "special_cases", p_char, NULL);
-        if (tk->exec_bin != NULL) /* found this key */
-            return;
-    }
-    tk->exec_bin = g_strdup(exec_bin_full);
-
-#ifdef DEBUG
-    if(ltbp->mode == LAUNCHTASKBAR)
-    {
-        FmFileInfo *fi = f_find_menu_launchbutton_recursive(tk->exec_bin);
-        LaunchButton *btn = launchbar_exec_bin_exists(ltbp, fi);
-        g_print("\nTB '%s' OPEN (pid=%u), in LB: %c\n",
-            tk->exec_bin, pid, btn != NULL ? 'Y':'N');
-        if (fi)
-            fm_file_info_unref(fi);
-    }
-#endif
-}
-
-static void launchbar_update_after_taskbar_class_removed(LaunchTaskBarPlugin *ltbp, Task *tk)
-{
-#ifdef DEBUG
-    if(ltbp->mode == LAUNCHTASKBAR)
-    {
-        FmFileInfo *fi = f_find_menu_launchbutton_recursive(tk->exec_bin);
-        LaunchButton *btn = launchbar_exec_bin_exists(ltbp, fi);
-        g_print("\nTB '%s' CLOSE, in LB: %c\n", tk->exec_bin, btn != NULL ? 'Y':'N');
-        if (fi)
-            fm_file_info_unref(fi);
-    }
-#endif
-}
-
 /* Build the graphic elements for a launchtaskbar button.  The desktop_id field is already established. */
 /* NOTE: this func consumes reference on fi */
 static LaunchButton *launchbutton_for_file_info(LaunchTaskBarPlugin * lb, FmFileInfo * fi)
@@ -708,6 +629,63 @@ static void launchtaskbar_constructor_launch(LaunchTaskBarPlugin *ltbp, gboolean
     gtk_widget_set_visible(ltbp->lb_icon_grid, TRUE);
 }
 
+static void flash_window_update_iter(GtkWidget *widget, gpointer data)
+{
+    task_button_set_flash_state(PANEL_TASK_BUTTON(widget), GPOINTER_TO_INT(data));
+}
+
+static gboolean flash_window_timeout(gpointer user_data)
+{
+    LaunchTaskBarPlugin *tb;
+
+    if (g_source_is_destroyed(g_main_current_source()))
+        return FALSE;
+    tb = user_data;
+    tb->flash_state = !tb->flash_state;
+    gtk_container_foreach(GTK_CONTAINER(tb->tb_icon_grid),
+                          flash_window_update_iter, GINT_TO_POINTER(tb->flash_state));
+    return TRUE;
+}
+
+static void on_gtk_cursor_blink_time_changed(GObject *gsettings, GParamSpec *pspec,
+                                             LaunchTaskBarPlugin *tb)
+{
+    gint interval;
+
+    if (tb->flash_timeout == 0) /* nothing to do? */
+        return;
+    g_source_remove(tb->flash_timeout);
+    g_object_get(gtk_widget_get_settings(GTK_WIDGET(tb)), "gtk-cursor-blink-time",
+                 &interval, NULL);
+    tb->flash_timeout = g_timeout_add(interval / 2, flash_window_timeout, tb);
+}
+
+/* Set an urgency timer on a task. */
+static void set_timer_on_task(LaunchTaskBarPlugin *tb)
+{
+    gint interval;
+
+    if (tb->flash_timeout != 0)
+        return;
+    g_object_get(gtk_widget_get_settings(GTK_WIDGET(tb->plugin)),
+                 "gtk-cursor-blink-time", &interval, NULL);
+    g_signal_connect(gtk_widget_get_settings(GTK_WIDGET(tb->plugin)),
+                     "notify::gtk-cursor-blink-time",
+                     G_CALLBACK(on_gtk_cursor_blink_time_changed), tb);
+    tb->flash_timeout = g_timeout_add(interval / 2, flash_window_timeout, tb);
+}
+
+static void reset_timer_on_task(LaunchTaskBarPlugin *tb)
+{
+    if (tb->flash_timeout == 0)
+        return;
+    g_source_remove(tb->flash_timeout);
+    tb->flash_timeout = 0;
+    g_signal_handlers_disconnect_by_func(gtk_widget_get_settings(GTK_WIDGET(tb->plugin)),
+                                         on_gtk_cursor_blink_time_changed, tb);
+}
+
+
 static void launchtaskbar_constructor_task(LaunchTaskBarPlugin *ltbp)
 {
     if(!ltbp->tb_built)
@@ -719,23 +697,23 @@ static void launchtaskbar_constructor_task(LaunchTaskBarPlugin *ltbp)
 
         /* Parse configuration now */
         if (config_setting_lookup_int(s, "tooltips", &tmp_int))
-            ltbp->tooltips = (tmp_int != 0);
+            ltbp->flags.tooltips = (tmp_int != 0);
         if (config_setting_lookup_int(s, "IconsOnly", &tmp_int))
-            ltbp->icons_only = (tmp_int != 0);
+            ltbp->flags.icons_only = (tmp_int != 0);
         if (config_setting_lookup_int(s, "ShowAllDesks", &tmp_int))
-            ltbp->show_all_desks = (tmp_int != 0);
+            ltbp->flags.show_all_desks = (tmp_int != 0);
         if (config_setting_lookup_int(s, "SameMonitorOnly", &tmp_int))
-            ltbp->same_monitor_only = (tmp_int != 0);
+            ltbp->flags.same_monitor_only = (tmp_int != 0);
         if (config_setting_lookup_int(s, "DisableUpscale", &tmp_int))
-            ltbp->disable_taskbar_upscale = (tmp_int != 0);
+            ltbp->flags.disable_taskbar_upscale = (tmp_int != 0);
         config_setting_lookup_int(s, "MaxTaskWidth", &ltbp->task_width_max);
         config_setting_lookup_int(s, "spacing", &ltbp->spacing);
         if (config_setting_lookup_int(s, "UseMouseWheel", &tmp_int))
-            ltbp->use_mouse_wheel = (tmp_int != 0);
+            ltbp->flags.use_mouse_wheel = (tmp_int != 0);
         if (config_setting_lookup_int(s, "UseUrgencyHint", &tmp_int))
-            ltbp->use_urgency_hint = (tmp_int != 0);
+            ltbp->flags.use_urgency_hint = (tmp_int != 0);
         if (config_setting_lookup_int(s, "FlatButton", &tmp_int))
-            ltbp->flat_button = (tmp_int != 0);
+            ltbp->flags.flat_button = (tmp_int != 0);
         if (config_setting_lookup_int(s, "GroupedTasks", &tmp_int))
             ltbp->grouped_tasks = (tmp_int != 0);
 
@@ -746,7 +724,7 @@ static void launchtaskbar_constructor_task(LaunchTaskBarPlugin *ltbp)
                                                  panel_get_height(ltbp->panel));
         panel_icon_grid_set_constrain_width(PANEL_ICON_GRID(ltbp->tb_icon_grid), TRUE);
         gtk_box_pack_start(GTK_BOX(ltbp->plugin), ltbp->tb_icon_grid, TRUE, TRUE, 0);
-        taskbar_update_style(ltbp);
+        /* taskbar_update_style(ltbp); */
 
         /* Add GDK event filter. */
         gdk_window_add_filter(NULL, (GdkFilterFunc) taskbar_event_filter, ltbp);
@@ -759,14 +737,13 @@ static void launchtaskbar_constructor_task(LaunchTaskBarPlugin *ltbp)
         g_signal_connect(G_OBJECT(fbev), "number-of-desktops", G_CALLBACK(taskbar_net_number_of_desktops), (gpointer) ltbp);
         g_signal_connect(G_OBJECT(fbev), "client-list", G_CALLBACK(taskbar_net_client_list), (gpointer) ltbp);
 
-        /* Make right-click menu for task buttons.
-         * It is retained for the life of the taskbar and will be shown as needed.
-         * Number of desktops and edge is needed for this operation. */
-        taskbar_make_menu(ltbp);
-
         /* Connect a signal to be notified when the window manager changes.  This causes re-evaluation of the "use_net_active" status. */
         g_signal_connect(ltbp->screen, "window-manager-changed", G_CALLBACK(taskbar_window_manager_changed), ltbp);
 
+        /* Start blinking timeout if configured */
+        if (ltbp->flags.use_urgency_hint)
+            set_timer_on_task(ltbp);
+
         /* Fetch the client list and redraw the taskbar.  Then determine what window has focus. */
         taskbar_net_client_list(NULL, ltbp);
         taskbar_net_active_window(NULL, ltbp);
@@ -792,13 +769,13 @@ static GtkWidget *_launchtaskbar_constructor(LXPanel *panel, config_setting_t *s
 
     /* Initialize to defaults. */
     ltbp->icon_size         = panel_get_icon_size(panel);
-    ltbp->tooltips          = TRUE;
-    ltbp->icons_only        = FALSE;
-    ltbp->show_all_desks    = TRUE;
+    ltbp->flags.tooltips    = TRUE;
+    ltbp->flags.icons_only  = FALSE;
+    ltbp->flags.show_all_desks = TRUE;
     ltbp->task_width_max    = TASK_WIDTH_MAX;
     ltbp->spacing           = 1;
-    ltbp->use_mouse_wheel   = TRUE;
-    ltbp->use_urgency_hint  = TRUE;
+    ltbp->flags.use_mouse_wheel = TRUE;
+    ltbp->flags.use_urgency_hint = TRUE;
     ltbp->grouped_tasks     = FALSE;
     ltbp->fixed_mode        = (mode == LAUNCHBAR) || (mode == TASKBAR);
 
@@ -881,22 +858,12 @@ static void launchtaskbar_destructor_task(LaunchTaskBarPlugin *ltbp)
     /* Remove "window-manager-changed" handler. */
     g_signal_handlers_disconnect_by_func(ltbp->screen, taskbar_window_manager_changed, ltbp);
 
-    /* Deallocate task list - widgets are already destroyed there. */
-    while(ltbp->p_task_list != NULL)
-        task_delete(ltbp, ltbp->p_task_list, TRUE, FALSE);
-
-    /* Deallocate class list. */
-    while(ltbp->p_taskclass_list != NULL)
-    {
-        TaskClass * tc = ltbp->p_taskclass_list;
-        ltbp->p_taskclass_list = tc->p_taskclass_flink;
-        g_free(tc->res_class);
-        g_free(tc);
-    }
-
-    /* Deallocate other memory. */
-    gtk_widget_destroy(ltbp->menu);
-    task_group_menu_destroy(ltbp);
+    /* Stop blinking timeout */
+    reset_timer_on_task(ltbp);
+#ifndef DISABLE_MENU
+    if (ltbp->fi)
+        fm_file_info_unref(ltbp->fi);
+#endif
 }
 
 /* Plugin destructor. */
@@ -1170,73 +1137,78 @@ static void on_combobox_mode_changed(GtkComboBox *p_combobox, gpointer p_data)
 static void on_checkbutton_show_tooltips_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
 {
     LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
-    ltbp->tooltips = gtk_toggle_button_get_active(p_togglebutton);
-    //g_print("\nltbp->tooltips upd\n");
-    config_group_set_int(ltbp->settings, "tooltips", ltbp->tooltips);
+    ltbp->flags.tooltips = gtk_toggle_button_get_active(p_togglebutton);
+    //g_print("\nltbp->flags.tooltips upd\n");
+    config_group_set_int(ltbp->settings, "tooltips", ltbp->flags.tooltips);
     taskbar_apply_configuration(ltbp);
 }
 
 static void on_checkbutton_icons_only_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
 {
     LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
-    ltbp->icons_only = gtk_toggle_button_get_active(p_togglebutton);
-    //g_print("\ntb->icons_only upd\n");
-    config_group_set_int(ltbp->settings, "IconsOnly", ltbp->icons_only);
+    ltbp->flags.icons_only = gtk_toggle_button_get_active(p_togglebutton);
+    //g_print("\ntb->flags.icons_only upd\n");
+    config_group_set_int(ltbp->settings, "IconsOnly", ltbp->flags.icons_only);
     taskbar_apply_configuration(ltbp);
 }
 
 static void on_checkbutton_flat_buttons_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
 {
     LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
-    ltbp->flat_button = gtk_toggle_button_get_active(p_togglebutton);
-    //g_print("\ntb->flat_button upd\n");
-    config_group_set_int(ltbp->settings, "FlatButton", ltbp->flat_button);
+    ltbp->flags.flat_button = gtk_toggle_button_get_active(p_togglebutton);
+    //g_print("\ntb->flags.flat_button upd\n");
+    config_group_set_int(ltbp->settings, "FlatButton", ltbp->flags.flat_button);
     taskbar_apply_configuration(ltbp);
 }
 
 static void on_checkbutton_show_all_desks_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
 {
     LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
-    ltbp->show_all_desks = gtk_toggle_button_get_active(p_togglebutton);
-    //g_print("\ntb->show_all_desks upd\n");
-    config_group_set_int(ltbp->settings, "ShowAllDesks", ltbp->show_all_desks);
+    ltbp->flags.show_all_desks = gtk_toggle_button_get_active(p_togglebutton);
+    //g_print("\ntb->flags.show_all_desks upd\n");
+    config_group_set_int(ltbp->settings, "ShowAllDesks", ltbp->flags.show_all_desks);
     taskbar_apply_configuration(ltbp);
 }
 
 static void on_checkbutton_same_monitor_only_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
 {
     LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
-    ltbp->same_monitor_only = gtk_toggle_button_get_active(p_togglebutton);
-    //g_print("\ntb->same_monitor_only upd\n");
-    config_group_set_int(ltbp->settings, "SameMonitorOnly", ltbp->same_monitor_only);
+    ltbp->flags.same_monitor_only = gtk_toggle_button_get_active(p_togglebutton);
+    //g_print("\ntb->flags.same_monitor_only upd\n");
+    config_group_set_int(ltbp->settings, "SameMonitorOnly", ltbp->flags.same_monitor_only);
     taskbar_apply_configuration(ltbp);
 }
 
 static void on_checkbutton_disable_taskbar_upscale_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
 {
     LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
-    ltbp->disable_taskbar_upscale = gtk_toggle_button_get_active(p_togglebutton);
-    //g_print("\ntb->disable_taskbar_upscale upd\n");
-    config_group_set_int(ltbp->settings, "DisableUpscale", ltbp->disable_taskbar_upscale);
+    ltbp->flags.disable_taskbar_upscale = gtk_toggle_button_get_active(p_togglebutton);
+    //g_print("\ntb->flags.disable_taskbar_upscale upd\n");
+    config_group_set_int(ltbp->settings, "DisableUpscale", ltbp->flags.disable_taskbar_upscale);
     taskbar_apply_configuration(ltbp);
 }
 
 static void on_checkbutton_mouse_wheel_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
 {
     LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
-    ltbp->use_mouse_wheel = gtk_toggle_button_get_active(p_togglebutton);
-    //g_print("\ntb->use_mouse_wheel upd\n");
-    config_group_set_int(ltbp->settings, "UseMouseWheel", ltbp->use_mouse_wheel);
+    ltbp->flags.use_mouse_wheel = gtk_toggle_button_get_active(p_togglebutton);
+    //g_print("\ntb->flags.use_mouse_wheel upd\n");
+    config_group_set_int(ltbp->settings, "UseMouseWheel", ltbp->flags.use_mouse_wheel);
     taskbar_apply_configuration(ltbp);
 }
 
 static void on_checkbutton_urgency_hint_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
 {
     LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)p_data;
-    ltbp->use_urgency_hint = gtk_toggle_button_get_active(p_togglebutton);
-    //g_print("\ntb->use_urgency_hint upd\n");
-    config_group_set_int(ltbp->settings, "UseUrgencyHint", ltbp->use_urgency_hint);
+    ltbp->flags.use_urgency_hint = gtk_toggle_button_get_active(p_togglebutton);
+    //g_print("\ntb->flags.use_urgency_hint upd\n");
+    config_group_set_int(ltbp->settings, "UseUrgencyHint", ltbp->flags.use_urgency_hint);
     taskbar_apply_configuration(ltbp);
+    /* Start/stop blinking timeout if configured */
+    if (ltbp->flags.use_urgency_hint)
+        set_timer_on_task(ltbp);
+    else
+        reset_timer_on_task(ltbp);
 }
 
 static void on_checkbutton_grouped_tasks_toggled(GtkToggleButton *p_togglebutton, gpointer p_data)
@@ -1246,6 +1218,54 @@ static void on_checkbutton_grouped_tasks_toggled(GtkToggleButton *p_togglebutton
     //g_print("\ntb->grouped_tasks upd\n");
     config_group_set_int(ltbp->settings, "GroupedTasks", ltbp->grouped_tasks);
     taskbar_apply_configuration(ltbp);
+    if (ltbp->grouped_tasks)
+    {
+        gboolean changed = FALSE;
+        GList *children, *this, *l;
+        int i = 0;
+
+        children = gtk_container_get_children(GTK_CONTAINER(ltbp->tb_icon_grid));
+        /* merge buttons with the same class into first of that class */
+        while ((this = g_list_nth(children, i++)))
+        {
+            for (l = this->next; l; l = l->next)
+                if (task_button_merge(this->data, l->data))
+                    changed = TRUE;
+            if (changed)
+            {
+                /* some button was consumed, need to reload buttons list */
+                g_list_free(children);
+                children = gtk_container_get_children(GTK_CONTAINER(ltbp->tb_icon_grid));
+                changed = FALSE;
+            }
+        }
+        g_list_free(children);
+    }
+    else
+    {
+        TaskButton *old_btn, *new_btn;
+        GList *children, *this;
+        int i;
+
+        children = gtk_container_get_children(GTK_CONTAINER(ltbp->tb_icon_grid));
+        /* split each button starting from last one */
+        for (this = g_list_last(children); this; this = this->prev)
+        {
+            old_btn = this->data;
+            i = panel_icon_grid_get_child_position(PANEL_ICON_GRID(ltbp->tb_icon_grid),
+                                                   GTK_WIDGET(old_btn));
+            while ((new_btn = task_button_split(old_btn)))
+            {
+                /* insert rest before old_btn */
+                taskbar_add_task_button(ltbp, new_btn);
+                panel_icon_grid_reorder_child(PANEL_ICON_GRID(ltbp->tb_icon_grid),
+                                              GTK_WIDGET(new_btn), i);
+                /* continue split with the rest */
+                old_btn = new_btn;
+            }
+        }
+        g_list_free(children);
+    }
 }
 
 static void on_spinbutton_max_width_value_changed(GtkSpinButton *p_spinbutton, gpointer p_data)
@@ -1393,7 +1413,7 @@ static GtkWidget *launchtaskbar_configure(LXPanel *panel, GtkWidget *p)
 
 #define SETUP_TOGGLE_BUTTON(button,member) \
         object = gtk_builder_get_object(builder, #button); \
-        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(object), ltbp->member); \
+        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(object), ltbp->flags.member); \
         g_signal_connect(object, "toggled", G_CALLBACK(on_##button##_toggled), ltbp)
 
         SETUP_TOGGLE_BUTTON(checkbutton_show_tooltips, tooltips);
@@ -1403,14 +1423,16 @@ static GtkWidget *launchtaskbar_configure(LXPanel *panel, GtkWidget *p)
         SETUP_TOGGLE_BUTTON(checkbutton_same_monitor_only, same_monitor_only);
         SETUP_TOGGLE_BUTTON(checkbutton_mouse_wheel, use_mouse_wheel);
         SETUP_TOGGLE_BUTTON(checkbutton_urgency_hint, use_urgency_hint);
-        SETUP_TOGGLE_BUTTON(checkbutton_grouped_tasks, grouped_tasks);
         //SETUP_TOGGLE_BUTTON(checkbutton_disable_taskbar_upscale, disable_taskbar_upscale);
 #undef SETUP_TOGGLE_BUTTON
+        object = gtk_builder_get_object(builder, "checkbutton_grouped_tasks");
+        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(object), ltbp->grouped_tasks);
+        g_signal_connect(object, "toggled", G_CALLBACK(on_checkbutton_grouped_tasks_toggled), ltbp);
         /* FIXME: for transitional period, turn into SETUP_TOGGLE_BUTTON later */
         object = gtk_builder_get_object(builder, "checkbutton_disable_taskbar_upscale");
         if (object)
         {
-            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(object), ltbp->disable_taskbar_upscale); \
+            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(object), ltbp->flags.disable_taskbar_upscale);
             g_signal_connect(object, "toggled", G_CALLBACK(on_checkbutton_disable_taskbar_upscale_toggled), ltbp);
         }
 
@@ -1465,191 +1487,31 @@ static void launchtaskbar_panel_configuration_changed(LXPanel *panel, GtkWidget
                                      new_icon_size, new_icon_size, 3, 0,
                                      panel_get_height(panel));
 
-    /* If the icon size changed, refetch all the icons. */
-    if (new_icon_size != ltbp->icon_size)
-    {
-        Task * tk;
-        ltbp->icon_size = new_icon_size;
-        for (tk = ltbp->p_task_list; tk != NULL; tk = tk->p_task_flink_xwid)
-        {
-            GdkPixbuf * pixbuf = task_update_icon(ltbp, tk, None);
-            if (pixbuf != NULL)
-            {
-                gtk_image_set_from_pixbuf(GTK_IMAGE(tk->image), pixbuf);
-                g_object_unref(pixbuf);
-            }
-        }
-    }
-
     /* Redraw all the labels.  Icon size or font color may have changed. */
     if (ltbp->tb_built)
     {
-        taskbar_update_style(ltbp);
-        taskbar_make_menu(ltbp);
+        ltbp->icon_size = new_icon_size;
+        panel_icon_grid_set_geometry(PANEL_ICON_GRID(ltbp->tb_icon_grid),
+            panel_get_orientation(ltbp->panel),
+            ((ltbp->flags.icons_only) ? ltbp->icon_size + ICON_ONLY_EXTRA : ltbp->task_width_max),
+            ltbp->icon_size, ltbp->spacing, 0, panel_get_height(ltbp->panel));
+        taskbar_reset_menu(ltbp);
         taskbar_redraw(ltbp);
     }
 }
 
-/* Set an urgency timer on a task. */
-static void set_timer_on_task(Task * tk)
-{
-    gint interval;
-    g_return_if_fail(tk->flash_timeout == 0);
-    g_object_get(gtk_widget_get_settings(tk->button), "gtk-cursor-blink-time", &interval, NULL);
-    tk->flash_timeout = g_timeout_add(interval / 2, flash_window_timeout, tk);
-}
-
-/* Determine if a task is visible considering only its desktop placement. */
-static gboolean task_is_visible_on_current_desktop(LaunchTaskBarPlugin * tb, Task * tk)
-{
-    return ((tk->desktop == ALL_WORKSPACES) || (tk->desktop == tb->current_desktop) || (tb->show_all_desks) || (tk->urgency));
-}
-
-/* Recompute the visible task for a class when the class membership changes.
- * Also transfer the urgency state to the visible task if necessary. */
-static void recompute_group_visibility_for_class(LaunchTaskBarPlugin * tb, TaskClass * tc)
-{
-    tc->visible_count = 0;
-    tc->p_task_visible = NULL;
-    tc->visible_name = NULL;
-    Task * flashing_task = NULL;
-    gboolean class_has_urgency = FALSE;
-    Task * tk;
-    for (tk = tc->p_task_head; tk != NULL; tk = tk->p_task_flink_same_class)
-    {
-        if (task_is_visible_on_current_desktop(tb, tk))
-        {
-            /* Count visible tasks and make the first visible task the one that is used for display. */
-            if (tc->visible_count == 0)
-                tc->p_task_visible = tk;
-            tc->visible_count += 1;
-
-            /* Compute summary bit for urgency anywhere in the class. */
-            if (tk->urgency && !tk->focused)
-                class_has_urgency = TRUE;
-
-            /* If there is urgency, record the currently flashing task. */
-            if (tk->flash_timeout != 0)
-                flashing_task = tk;
-
-            /* Compute the visible name.  If all visible windows have the same title, use that.
-             * Otherwise, use the class name.  This follows WNCK.
-             * Note that the visible name is not a separate string, but is set to point to one of the others. */
-            if (tc->visible_name == NULL)
-                tc->visible_name = tk->name;
-            else if ((tc->visible_name != tc->res_class)
-            && (tc->visible_name != NULL) && (tk->name != NULL)
-            && (strcmp(tc->visible_name, tk->name) != 0))
-                tc->visible_name = tc->res_class;
-        }
-    }
-
-    /* Transfer the flash timeout to the visible task. */
-    if (class_has_urgency)
-    {
-        if (flashing_task == NULL)
-        {
-            /* Set the flashing context and flash the window immediately. */
-            tc->p_task_visible->flash_state = TRUE;
-            flash_window_update(tc->p_task_visible);
-        }
-        else if (flashing_task != tc->p_task_visible)
-        {
-            /* Reset the timer on the new representative.
-             * There will be a slight hiccup on the flash cadence. */
-            g_source_remove(flashing_task->flash_timeout);
-            flashing_task->flash_timeout = 0;
-            tc->p_task_visible->flash_state = flashing_task->flash_state;
-            flashing_task->flash_state = FALSE;
-            if (tc->p_task_visible->menu_item != NULL)
-                g_object_unref(tc->p_task_visible->menu_item);
-            tc->p_task_visible->menu_item = flashing_task->menu_item;
-            flashing_task->menu_item = NULL;
-        }
-        if (tc->p_task_visible->flash_timeout == 0)
-            set_timer_on_task(tc->p_task_visible);
-    }
-    else
-    {
-        /* No task has urgency.  Cancel the timer if one is set. */
-        if (flashing_task != NULL)
-        {
-            g_source_remove(flashing_task->flash_timeout);
-            flashing_task->flash_state = FALSE;
-            flashing_task->flash_timeout = 0;
-        }
-    }
-}
-
-/* Recompute the visible task for all classes when the desktop changes. */
-static void recompute_group_visibility_on_current_desktop(LaunchTaskBarPlugin * tb)
-{
-    TaskClass * tc;
-    for (tc = tb->p_taskclass_list; tc != NULL; tc = tc->p_taskclass_flink)
-    {
-        recompute_group_visibility_for_class(tb, tc);
-    }
-}
-
-/* Draw the label and tooltip on a taskbar button. */
-static void task_draw_label(Task * tk)
-{
-    TaskClass * tc = tk->p_taskclass;
-    gboolean bold_style = (((tk->entered_state) || (tk->flash_state)) && (tk->tb->flat_button));
-    char *label;
-
-    if ((tk->tb->grouped_tasks) && (tc != NULL) && (tc->p_task_visible == tk) && (tc->visible_count > 1))
-    {
-        label = g_strdup_printf("(%d) %s", tc->visible_count, tc->visible_name);
-    }
-    else
-    {
-        label = g_strdup(tk->iconified ? tk->name_iconified : tk->name);
-    }
-
-    if (tk->tb->tooltips)
-        gtk_widget_set_tooltip_text(tk->button, label);
-
-    lxpanel_draw_label_text(tk->tb->panel, tk->label, label, bold_style, 1,
-            tk->tb->flat_button);
-
-    g_free(label);
-}
-
-/* Determine if a task is visible. */
-static gboolean task_is_visible(LaunchTaskBarPlugin * tb, Task * tk)
-{
-    /* Not visible due to grouping. */
-    if ((tb->grouped_tasks) && (tk->p_taskclass != NULL) && (tk->p_taskclass->p_task_visible != tk))
-        return FALSE;
-
-    /* Not on same monitor */
-    if (tb->same_monitor_only && panel_get_monitor(tb->panel) != tk->monitor
-        && panel_get_monitor(tb->panel) >= 0)
-        return FALSE;
-
-    /* Desktop placement. */
-    return task_is_visible_on_current_desktop(tb, tk);
-}
-
-/* Redraw a task button. */
-static void task_button_redraw(Task * tk, LaunchTaskBarPlugin * tb)
-{
-    if (task_is_visible(tb, tk))
-    {
-        task_draw_label(tk);
-        gtk_widget_set_visible(tk->button, TRUE);
-    }
-    else
-        gtk_widget_set_visible(tk->button, FALSE);
-}
-
 /* Redraw all tasks in the taskbar. */
 static void taskbar_redraw(LaunchTaskBarPlugin * tb)
 {
-    Task * tk;
-    for (tk = tb->p_task_list; tk != NULL; tk = tk->p_task_flink_xwid)
-        task_button_redraw(tk, tb);
+    GList *children = gtk_container_get_children(GTK_CONTAINER(tb->tb_icon_grid));
+    GList *l;
+    guint mon = panel_get_monitor(tb->panel);
+    guint icon_size = panel_get_icon_size(tb->panel);
+
+    for (l = children; l; l = l->next)
+        task_button_update(l->data, tb->current_desktop, tb->number_of_desktops,
+                           mon, icon_size, tb->flags);
+    g_list_free(children);
 }
 
 /* Determine if a task should be visible given its NET_WM_STATE. */
@@ -1664,149 +1526,15 @@ static gboolean accept_net_wm_window_type(NetWMWindowType * nwwt)
     return ( ! ((nwwt->desktop) || (nwwt->dock) || (nwwt->splash)));
 }
 
-/* Free the names associated with a task. */
-static void task_free_names(Task * tk)
-{
-    TaskClass * tc = tk->p_taskclass;
-
-    if (tc != NULL && tk->name != NULL)
-        /* Reset the name from class */
-        if (tc->visible_name == tk->name)
-            tc->visible_name = tc->res_class;
-    g_free(tk->name);
-    g_free(tk->name_iconified);
-    tk->name = tk->name_iconified = NULL;
-}
-
-/* Set the names associated with a task.
- * This is expected to be the same as the title the window manager is displaying. */
-static void task_set_names(Task * tk, Atom source)
-{
-    char * name = NULL;
-
-    /* Try _NET_WM_VISIBLE_NAME, which supports UTF-8.
-     * If it is set, the window manager is displaying it as the window title. */
-    if ((source == None) || (source == a_NET_WM_VISIBLE_NAME))
-    {
-        name = get_utf8_property(tk->win,  a_NET_WM_VISIBLE_NAME);
-        if (name != NULL)
-            tk->name_source = a_NET_WM_VISIBLE_NAME;
-    }
-
-    /* Try _NET_WM_NAME, which supports UTF-8, but do not overwrite _NET_WM_VISIBLE_NAME. */
-    if ((name == NULL)
-    && ((source == None) || (source == a_NET_WM_NAME))
-    && ((tk->name_source == None) || (tk->name_source == a_NET_WM_NAME) || (tk->name_source == XA_WM_NAME)))
-    {
-        name = get_utf8_property(tk->win,  a_NET_WM_NAME);
-        if (name != NULL)
-            tk->name_source = a_NET_WM_NAME;
-    }
-
-    /* Try WM_NAME, which supports only ISO-8859-1, but do not overwrite _NET_WM_VISIBLE_NAME or _NET_WM_NAME. */
-    if ((name == NULL)
-    && ((source == None) || (source == XA_WM_NAME))
-    && ((tk->name_source == None) || (tk->name_source == XA_WM_NAME)))
-    {
-        name = get_textproperty(tk->win,  XA_WM_NAME);
-        if (name != NULL)
-            tk->name_source = XA_WM_NAME;
-    }
-
-    /* Set the name into the task context, and also on the tooltip. */
-    if (name != NULL)
-    {
-        task_free_names(tk);
-        tk->name = name;
-        tk->name_iconified = g_strdup_printf("[%s]", name);
-
-        /* Redraw the button. */
-        task_button_redraw(tk, tk->tb);
-    }
-}
-
-/* Unlink a task from the class list because its class changed or it was deleted. */
-static void task_unlink_class(Task * tk)
-{
-    TaskClass * tc = tk->p_taskclass;
-    if (tc != NULL)
-    {
-        /* Reset the name from class */
-        if (tc->visible_name == tk->name)
-            tc->visible_name = tc->res_class;
-
-        /* Action in Launchbar after class removed */
-        launchbar_update_after_taskbar_class_removed(tk->tb, tk);
-
-        /* Remove from per-class task list. */
-        if (tc->p_task_head == tk)
-        {
-            /* Removing the head of the list.  This causes a new task to be the visible task, so we redraw. */
-            tc->p_task_head = tk->p_task_flink_same_class;
-            if (tc->p_task_head != NULL)
-                task_button_redraw(tc->p_task_head, tk->tb);
-        }
-        else
-        {
-            /* Locate the task and its predecessor in the list and then remove it.  For safety, ensure it is found. */
-            Task * tk_pred = NULL;
-            Task * tk_cursor;
-            for (
-              tk_cursor = tc->p_task_head;
-              ((tk_cursor != NULL) && (tk_cursor != tk));
-              tk_pred = tk_cursor, tk_cursor = tk_cursor->p_task_flink_same_class) ;
-            if (tk_cursor == tk)
-                tk_pred->p_task_flink_same_class = tk->p_task_flink_same_class;
-        }
-        tk->p_task_flink_same_class = NULL;
-        tk->p_taskclass = NULL;
-
-        /* Recompute group visibility. */
-        recompute_group_visibility_for_class(tk->tb, tc);
-    }
-}
-
-/* Enter class with specified name. */
-static TaskClass * taskbar_enter_res_class(LaunchTaskBarPlugin * tb, char * res_class, gboolean * p_name_consumed)
-{
-    /* Find existing entry or insertion point. */
-    *p_name_consumed = FALSE;
-    TaskClass * tc_pred = NULL;
-    TaskClass * tc;
-    for (tc = tb->p_taskclass_list; tc != NULL; tc_pred = tc, tc = tc->p_taskclass_flink)
-    {
-        int status = strcmp(res_class, tc->res_class);
-        if (status == 0)
-            return tc;
-        if (status < 0)
-            break;
-    }
-
-    /* Insert new entry. */
-    tc = g_new0(TaskClass, 1);
-    tc->res_class = res_class;
-    *p_name_consumed = TRUE;
-    if (tc_pred == NULL)
-    {
-        tc->p_taskclass_flink = tb->p_taskclass_list;
-        tb->p_taskclass_list = tc;
-    }
-    else
-    {
-        tc->p_taskclass_flink = tc_pred->p_taskclass_flink;
-        tc_pred->p_taskclass_flink = tc;
-    }
-    return tc;
-}
-
 /* Set the class associated with a task. */
-static void task_set_class(Task * tk)
+static char *task_get_class(Window win)
 {
     /* Read the WM_CLASS property. */
     XClassHint ch;
     ch.res_name = NULL;
     ch.res_class = NULL;
-    XGetClassHint(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), tk->win, &ch);
+    char *res_class = NULL;
+    XGetClassHint(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), win, &ch);
 
     /* If the res_name was returned, free it.  We make no use of it at this time. */
     if (ch.res_name != NULL)
@@ -1819,792 +1547,74 @@ static void task_set_class(Task * tk)
     if (ch.res_class != NULL)
     {
         /* Convert the class to UTF-8 and enter it in the class table. */
-        gchar * res_class = g_locale_to_utf8(ch.res_class, -1, NULL, NULL, NULL);
-        if (res_class != NULL)
-        {
-            gboolean name_consumed;
-            TaskClass * tc = taskbar_enter_res_class(tk->tb, res_class, &name_consumed);
-            if ( ! name_consumed) g_free(res_class);
-
-            /* If the task changed class, update data structures. */
-            TaskClass * old_tc = tk->p_taskclass;
-            if (old_tc != tc)
-            {
-                /* Unlink from previous class, if any. */
-                task_unlink_class(tk);
-
-                /* Add to end of per-class task list.  Do this to keep the popup menu in order of creation. */
-                if (tc->p_task_head == NULL)
-                    tc->p_task_head = tk;
-                else
-                {
-                    Task * tk_pred;
-                    for (tk_pred = tc->p_task_head; tk_pred->p_task_flink_same_class != NULL; tk_pred = tk_pred->p_task_flink_same_class) ;
-                    tk_pred->p_task_flink_same_class = tk;
-                    task_button_redraw(tk, tk->tb);
-                }
-                tk->p_taskclass = tc;
-
-                /* Recompute group visibility. */
-                recompute_group_visibility_for_class(tk->tb, tc);
-
-                /* Action in Launchbar after class added */
-                launchbar_update_after_taskbar_class_added(tk->tb, tk);
-            }
-        }
+        res_class = g_locale_to_utf8(ch.res_class, -1, NULL, NULL, NULL);
         XFree(ch.res_class);
     }
+    return res_class;
 }
 
 /* Look up a task in the task list. */
-static Task * task_lookup(LaunchTaskBarPlugin * tb, Window win)
+static TaskButton *task_lookup(LaunchTaskBarPlugin * tb, Window win)
 {
-    Task * tk;
-    for (tk = tb->p_task_list; tk != NULL; tk = tk->p_task_flink_xwid)
+    TaskButton *task = NULL;
+    GList *children = gtk_container_get_children(GTK_CONTAINER(tb->tb_icon_grid));
+    GList *l;
+
+    for (l = children; l; l = l->next)
+        if (task_button_has_window(l->data, win))
         {
-        if (tk->win == win)
-        return tk;
-        if (tk->win > win)
+            task = l->data;
             break;
         }
-    return NULL;
+    g_list_free(children);
+    return task;
 }
 
-/* Delete a task and optionally unlink it from the task list. */
-static void task_delete(LaunchTaskBarPlugin * tb, Task * tk, gboolean unlink, gboolean remove)
-{
-    /* If we think this task had focus, remove that. */
-    if (tb->focused == tk)
-        tb->focused = NULL;
-
-    if (tb->menutask == tk)
-        tb->menutask = NULL;
 
-    /* If there is an urgency timeout, remove it. */
-    if (tk->flash_timeout != 0) {
-        g_source_remove(tk->flash_timeout);
-        tk->flash_timeout = 0;
-    }
-
-    if (tk->menu_item)
-    {
-        g_object_unref(tk->menu_item);
-        tk->menu_item = NULL;
-    }
+#ifndef DISABLE_MENU
+static void  on_menuitem_lock_tbp_clicked(GtkWidget * widget, LaunchTaskBarPlugin * tb);
+static void  on_menuitem_unlock_tbp_clicked(GtkWidget * widget, LaunchTaskBarPlugin * tb);
+static void  on_menuitem_new_instance_clicked(GtkWidget * widget, LaunchTaskBarPlugin * tb);
+#endif
 
-    /* Deallocate structures. */
-    if (remove)
-    {
-        g_signal_handlers_disconnect_by_func(tk->button, taskbar_button_enter, tk);
-        g_signal_handlers_disconnect_by_func(tk->button, taskbar_button_leave, tk);
-        gtk_widget_destroy(tk->button);
-        task_unlink_class(tk);
-    }
-    task_free_names(tk);
-    g_free(tk->exec_bin);
+static void on_task_menu_built(GtkWidget *unused, GtkMenu *menu, LaunchTaskBarPlugin *tb)
+{
+#ifndef DISABLE_MENU
+    /* add callbacks for task-to-launcher items with weak pointers */
+    void (*_m_add)(GtkMenuShell *self, GtkWidget* child);
 
-    /* If requested, unlink the task from the task list.
-     * If not requested, the caller will do this. */
-    if (unlink)
-    {
-        if (tb->p_task_list == tk)
-            tb->p_task_list = tk->p_task_flink_xwid;
-        else
-        {
-            /* Locate the task and its predecessor in the list and then remove it.  For safety, ensure it is found. */
-            Task * tk_pred = NULL;
-            Task * tk_cursor;
-            for (
-              tk_cursor = tb->p_task_list;
-              ((tk_cursor != NULL) && (tk_cursor != tk));
-              tk_pred = tk_cursor, tk_cursor = tk_cursor->p_task_flink_xwid) ;
-            if (tk_cursor == tk)
-                tk_pred->p_task_flink_xwid = tk->p_task_flink_xwid;
-        }
-    }
+    if (panel_is_at_bottom(tb->panel))
+        _m_add = gtk_menu_shell_append;
+    else
+        _m_add = gtk_menu_shell_prepend;
 
-    /* Deallocate the task structure. */
-    g_free(tk);
+    tb->p_menuitem_lock_tbp = gtk_menu_item_new_with_mnemonic(_("A_dd to Launcher"));
+    g_object_add_weak_pointer(G_OBJECT(menu), (void **)&tb->p_menuitem_lock_tbp);
+    tb->p_menuitem_unlock_tbp = gtk_menu_item_new_with_mnemonic(_("Rem_ove from Launcher"));
+    g_object_add_weak_pointer(G_OBJECT(menu), (void **)&tb->p_menuitem_lock_tbp);
+    tb->p_menuitem_new_instance = gtk_menu_item_new_with_mnemonic(_("_New Instance"));
+    g_object_add_weak_pointer(G_OBJECT(menu), (void **)&tb->p_menuitem_lock_tbp);
+    tb->p_menuitem_separator = gtk_separator_menu_item_new();
+    _m_add(GTK_MENU_SHELL(menu), tb->p_menuitem_separator);
+    _m_add(GTK_MENU_SHELL(menu), tb->p_menuitem_lock_tbp);
+    _m_add(GTK_MENU_SHELL(menu), tb->p_menuitem_unlock_tbp);
+    _m_add(GTK_MENU_SHELL(menu), tb->p_menuitem_new_instance);
+    g_signal_connect(G_OBJECT(tb->p_menuitem_lock_tbp), "activate", (GCallback)on_menuitem_lock_tbp_clicked, tb);
+    g_signal_connect(G_OBJECT(tb->p_menuitem_unlock_tbp), "activate", (GCallback)on_menuitem_unlock_tbp_clicked, tb);
+    g_signal_connect(G_OBJECT(tb->p_menuitem_new_instance), "activate", (GCallback)on_menuitem_new_instance_clicked, tb);
+#endif
 }
 
-/* Get a pixbuf from a pixmap.
- * Originally from libwnck, Copyright (C) 2001 Havoc Pennington. */
-#if !GTK_CHECK_VERSION(3, 0, 0)
-static GdkPixbuf * _wnck_gdk_pixbuf_get_from_pixmap(GdkScreen *screen, Pixmap xpixmap, int width, int height)
+static void on_task_menu_target_set(TaskButton *btn, gulong win, LaunchTaskBarPlugin *ltbp)
 {
-    /* Get the drawable. */
-#if GTK_CHECK_VERSION(2, 24, 0)
-    GdkDrawable * drawable = gdk_x11_window_lookup_for_display(gdk_display_get_default(), xpixmap);
-#else
-    GdkDrawable * drawable = gdk_xid_table_lookup(xpixmap);
-#endif
-    if (drawable != NULL)
-        g_object_ref(G_OBJECT(drawable));
-    else
-        drawable = gdk_pixmap_foreign_new(xpixmap);
-
-    GdkColormap * colormap = NULL;
-    GdkPixbuf * retval = NULL;
-    if (drawable != NULL)
-    {
-        /* Get the colormap.
-         * If the drawable has no colormap, use no colormap or the system colormap as recommended in the documentation of gdk_drawable_get_colormap. */
-        colormap = gdk_drawable_get_colormap(drawable);
-        gint depth = gdk_drawable_get_depth(drawable);
-        if (colormap != NULL)
-            g_object_ref(G_OBJECT(colormap));
-        else if (depth == 1)
-            colormap = NULL;
-        else
-        {
-            colormap = gdk_screen_get_system_colormap(screen);
-            g_object_ref(G_OBJECT(colormap));
-        }
-
-        /* Be sure we aren't going to fail due to visual mismatch. */
-        if ((colormap != NULL) && (gdk_visual_get_depth(gdk_colormap_get_visual(colormap)) != depth))
-        {
-            g_object_unref(G_OBJECT(colormap));
-            colormap = NULL;
-        }
-
-        /* Do the major work. */
-        retval = gdk_pixbuf_get_from_drawable(NULL, drawable, colormap, 0, 0, 0, 0, width, height);
-    }
-
-    /* Clean up and return. */
-    if (colormap != NULL)
-        g_object_unref(G_OBJECT(colormap));
-    if (drawable != NULL)
-        g_object_unref(G_OBJECT(drawable));
-    return retval;
-}
-#else
-static GdkPixbuf * _wnck_gdk_pixbuf_get_from_pixmap(GdkScreen *screen, Pixmap xpixmap, int width, int height)
-{
-  cairo_surface_t *surface;
-  GdkPixbuf *pixbuf;
-  Display *xdisplay;
-  Window root_return;
-  XWindowAttributes attrs;
-
-  surface = NULL;
-  xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
-
-
-  gdk_error_trap_push();
-
-  if (!XGetWindowAttributes (xdisplay, root_return, &attrs))
-    goto TRAP_POP;
-
-  if (attrs.depth == 1)
-    {
-      surface = cairo_xlib_surface_create_for_bitmap (xdisplay,
-                                                      xpixmap,
-                                                      attrs.screen,
-                                                      width,
-                                                      height);
-    }
-  else
-    {
-      surface = cairo_xlib_surface_create (xdisplay,
-                                           xpixmap,
-                                           attrs.visual,
-                                           width, height);
-    }
-
-  pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, width, height);
-  cairo_surface_destroy (surface);
-
-TRAP_POP:
-  gdk_flush();
-  gdk_error_trap_pop();
-
-  return pixbuf;
-}
-#endif
-
-/* Apply a mask to a pixbuf.
- * Originally from libwnck, Copyright (C) 2001 Havoc Pennington. */
-static GdkPixbuf * apply_mask(GdkPixbuf * pixbuf, GdkPixbuf * mask)
-{
-    /* Initialize. */
-    int w = MIN(gdk_pixbuf_get_width(mask), gdk_pixbuf_get_width(pixbuf));
-    int h = MIN(gdk_pixbuf_get_height(mask), gdk_pixbuf_get_height(pixbuf));
-    GdkPixbuf * with_alpha = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
-    guchar * dst = gdk_pixbuf_get_pixels(with_alpha);
-    guchar * src = gdk_pixbuf_get_pixels(mask);
-    int dst_stride = gdk_pixbuf_get_rowstride(with_alpha);
-    int src_stride = gdk_pixbuf_get_rowstride(mask);
-
-    /* Loop to do the work. */
-    int i;
-    for (i = 0; i < h; i += 1)
-    {
-        int j;
-        for (j = 0; j < w; j += 1)
-        {
-            guchar * s = src + i * src_stride + j * 3;
-            guchar * d = dst + i * dst_stride + j * 4;
-
-            /* s[0] == s[1] == s[2], they are 255 if the bit was set, 0 otherwise. */
-            d[3] = ((s[0] == 0) ? 0 : 255); /* 0 = transparent, 255 = opaque */
-        }
-    }
-
-    return with_alpha;
-}
-
-/* Get an icon from the window manager for a task, and scale it to a specified size. */
-static GdkPixbuf * get_wm_icon(Window task_win, guint required_width,
-                               guint required_height, Atom source,
-                               Atom * current_source, LaunchTaskBarPlugin * tb)
-{
-    /* The result. */
-    GdkPixbuf * pixmap = NULL;
-    Atom possible_source = None;
-    int result = -1;
-    Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
-    GdkScreen *screen = gtk_widget_get_screen(tb->plugin);
-
-    if ((source == None) || (source == a_NET_WM_ICON))
-    {
-        /* Important Notes:
-         * According to freedesktop.org document:
-         * http://standards.freedesktop.org/wm-spec/wm-spec-1.4.html#id2552223
-         * _NET_WM_ICON contains an array of 32-bit packed CARDINAL ARGB.
-         * However, this is incorrect. Actually it's an array of long integers.
-         * Toolkits like gtk+ use unsigned long here to store icons.
-         * Besides, according to manpage of XGetWindowProperty, when returned format,
-         * is 32, the property data will be stored as an array of longs
-         * (which in a 64-bit application will be 64-bit values that are
-         * padded in the upper 4 bytes).
-         */
-
-        /* Get the window property _NET_WM_ICON, if possible. */
-        Atom type = None;
-        int format;
-        gulong nitems;
-        gulong bytes_after;
-        gulong * data = NULL;
-        result = XGetWindowProperty(
-            xdisplay,
-            task_win,
-            a_NET_WM_ICON,
-            0, G_MAXLONG,
-            False, XA_CARDINAL,
-            &type, &format, &nitems, &bytes_after, (void *) &data);
-
-        /* Inspect the result to see if it is usable.  If not, and we got data, free it. */
-        if ((type != XA_CARDINAL) || (nitems <= 0))
-        {
-            if (data != NULL)
-                XFree(data);
-            result = -1;
-        }
-
-        /* If the result is usable, extract the icon from it. */
-        if (result == Success)
-        {
-            /* Get the largest icon available, unless there is one that is the desired size. */
-            /* FIXME: should we try to find an icon whose size is closest to
-             * required_width and required_height to reduce unnecessary resizing? */
-            gulong * pdata = data;
-            gulong * pdata_end = data + nitems;
-            gulong * max_icon = NULL;
-            gulong max_w = 0;
-            gulong max_h = 0;
-            while ((pdata + 2) < pdata_end)
-            {
-                /* Extract the width and height. */
-                guint w = pdata[0];
-                guint h = pdata[1];
-                gulong size = w * h;
-                pdata += 2;
-
-                /* Bounds check the icon. Also check for invalid width and height,
-                   see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=801319 */
-                if (size == 0 || w > 1024 || h > 1024 || pdata + size > pdata_end)
-                    break;
-
-                /* Rare special case: the desired size is the same as icon size. */
-                if ((required_width == w) && (required_height == h))
-                {
-                    max_icon = pdata;
-                    max_w = w;
-                    max_h = h;
-                    break;
-                }
-
-                /* If the icon is the largest so far, capture it. */
-                if ((w > max_w) && (h > max_h))
-                {
-                    max_icon = pdata;
-                    max_w = w;
-                    max_h = h;
-                }
-                pdata += size;
-            }
-
-            /* If an icon was extracted, convert it to a pixbuf.
-             * Its size is max_w and max_h. */
-            if (max_icon != NULL)
-            {
-                /* Allocate enough space for the pixel data. */
-                gulong len = max_w * max_h;
-                guchar * pixdata = g_new(guchar, len * 4);
-
-                /* Loop to convert the pixel data. */
-                guchar * p = pixdata;
-                gulong i;
-                for (i = 0; i < len; p += 4, i += 1)
-                {
-                    guint argb = max_icon[i];
-                    guint rgba = (argb << 8) | (argb >> 24);
-                    p[0] = rgba >> 24;
-                    p[1] = (rgba >> 16) & 0xff;
-                    p[2] = (rgba >> 8) & 0xff;
-                    p[3] = rgba & 0xff;
-                }
-
-                /* Initialize a pixmap with the pixel data. */
-                pixmap = gdk_pixbuf_new_from_data(
-                    pixdata,
-                    GDK_COLORSPACE_RGB,
-                    TRUE, 8,    /* has_alpha, bits_per_sample */
-                    max_w, max_h, max_w * 4,
-                    (GdkPixbufDestroyNotify) g_free,
-                    NULL);
-                possible_source = a_NET_WM_ICON;
-            }
-        else
-            result = -1;
-
-            /* Free the X property data. */
-            XFree(data);
-        }
-    }
-
-    /* No icon available from _NET_WM_ICON.  Next try WM_HINTS, but do not overwrite _NET_WM_ICON. */
-    if ((result != Success) && (*current_source != a_NET_WM_ICON)
-    && ((source == None) || (source != a_NET_WM_ICON)))
-    {
-        XWMHints * hints = XGetWMHints(xdisplay, task_win);
-        result = (hints != NULL) ? Success : -1;
-        Pixmap xpixmap = None;
-        Pixmap xmask = None;
-
-        if (result == Success)
-        {
-            /* WM_HINTS is available.  Extract the X pixmap and mask. */
-            if ((hints->flags & IconPixmapHint))
-                xpixmap = hints->icon_pixmap;
-            if ((hints->flags & IconMaskHint))
-                xmask = hints->icon_mask;
-            XFree(hints);
-            if (xpixmap != None)
-            {
-                result = Success;
-                possible_source = XA_WM_HINTS;
-            }
-            else
-                result = -1;
-        }
-
-        if (result != Success)
-        {
-            /* No icon available from _NET_WM_ICON or WM_HINTS.  Next try KWM_WIN_ICON. */
-            Atom type = None;
-            int format;
-            gulong nitems;
-            gulong bytes_after;
-            Pixmap *icons = NULL;
-            Atom kwin_win_icon_atom = gdk_x11_get_xatom_by_name("KWM_WIN_ICON");
-            result = XGetWindowProperty(
-                xdisplay,
-                task_win,
-                kwin_win_icon_atom,
-                0, G_MAXLONG,
-                False, kwin_win_icon_atom,
-                &type, &format, &nitems, &bytes_after, (void *) &icons);
-
-            /* Inspect the result to see if it is usable.  If not, and we got data, free it. */
-            if (type != kwin_win_icon_atom)
-            {
-                if (icons != NULL)
-                    XFree(icons);
-                result = -1;
-            }
-
-            /* If the result is usable, extract the X pixmap and mask from it. */
-            if (result == Success)
-            {
-                xpixmap = icons[0];
-                xmask = icons[1];
-                if (xpixmap != None)
-                {
-                    result = Success;
-                    possible_source = kwin_win_icon_atom;
-                }
-                else
-                    result = -1;
-            }
-        }
-
-        /* If we have an X pixmap, get its geometry.*/
-        unsigned int w, h;
-        if (result == Success)
-        {
-            Window unused_win;
-            int unused;
-            unsigned int unused_2;
-            result = XGetGeometry(
-                xdisplay, xpixmap,
-                &unused_win, &unused, &unused, &w, &h, &unused_2, &unused_2) ? Success : -1;
-        }
-
-        /* If we have an X pixmap and its geometry, convert it to a GDK pixmap. */
-        if (result == Success)
-        {
-            pixmap = _wnck_gdk_pixbuf_get_from_pixmap(screen, xpixmap, w, h);
-            result = ((pixmap != NULL) ? Success : -1);
-        }
-
-        /* If we have success, see if the result needs to be masked.
-         * Failures here are implemented as nonfatal. */
-        if ((result == Success) && (xmask != None))
-        {
-            Window unused_win;
-            int unused;
-            unsigned int unused_2;
-            if (XGetGeometry(
-                xdisplay, xmask,
-                &unused_win, &unused, &unused, &w, &h, &unused_2, &unused_2))
-            {
-                /* Convert the X mask to a GDK pixmap. */
-                GdkPixbuf * mask = _wnck_gdk_pixbuf_get_from_pixmap(screen, xmask, w, h);
-                if (mask != NULL)
-                {
-                    /* Apply the mask. */
-                    GdkPixbuf * masked_pixmap = apply_mask(pixmap, mask);
-                    g_object_unref(G_OBJECT(pixmap));
-                    g_object_unref(G_OBJECT(mask));
-                    pixmap = masked_pixmap;
-                }
-            }
-        }
-    }
-
-    /* If we got a pixmap, scale it and return it. */
-    if (pixmap == NULL)
-        return NULL;
-    else
-    {
-        GdkPixbuf * ret;
-
-        *current_source = possible_source;
-        if (tb->disable_taskbar_upscale)
-        {
-            guint w = gdk_pixbuf_get_width (pixmap);
-            guint h = gdk_pixbuf_get_height (pixmap);
-            if (w <= required_width || h <= required_height)
-            {
-                return pixmap;
-            }
-        }
-        ret = gdk_pixbuf_scale_simple(pixmap, required_width, required_height,
-                                      GDK_INTERP_BILINEAR);
-        g_object_unref(pixmap);
-        return ret;
-    }
-}
-
-/* Update the icon of a task. */
-static GdkPixbuf * task_update_icon(LaunchTaskBarPlugin * tb, Task * tk, Atom source)
-{
-    /* Get the icon from the window's hints. */
-    GdkPixbuf * pixbuf = get_wm_icon(tk->win, MAX(0, tb->icon_size - ICON_BUTTON_TRIM),
-                                     MAX(0, tb->icon_size - ICON_BUTTON_TRIM),
-                                     source, &tk->image_source, tb);
-
-    /* If that fails, and we have no other icon yet, return the fallback icon. */
-    if ((pixbuf == NULL)
-    && ((source == None) || (tk->image_source == None)))
-    {
-        /* Establish the fallback task icon.  This is used when no other icon is available. */
-        if (tb->fallback_pixbuf == NULL)
-            tb->fallback_pixbuf = gdk_pixbuf_new_from_xpm_data((const char **) icon_xpm);
-        g_object_ref(tb->fallback_pixbuf);
-        pixbuf = tb->fallback_pixbuf;
-    }
-
-    /* Return what we have.  This may be NULL to indicate that no change should be made to the icon. */
-    return pixbuf;
-}
-
-/* Timer expiration for urgency notification.  Also used to draw the button in setting and clearing urgency. */
-static void flash_window_update(Task * tk)
-{
-    /* Set state on the button and redraw. */
-    if ( ! tk->tb->flat_button)
-        gtk_widget_set_state(tk->button, tk->flash_state ? GTK_STATE_SELECTED : GTK_STATE_NORMAL);
-    task_draw_label(tk);
-    if (tk->menu_item != NULL && gtk_widget_get_mapped(tk->menu_item))
-        /* if submenu exists and mapped then set state too */
-        gtk_widget_set_state(tk->menu_item, tk->flash_state ? GTK_STATE_SELECTED : GTK_STATE_NORMAL);
-
-    /* Complement the flashing context. */
-    tk->flash_state = ! tk->flash_state;
-}
-
-static gboolean flash_window_timeout(gpointer tk)
-{
-    if (g_source_is_destroyed(g_main_current_source()))
-        return FALSE;
-    flash_window_update(tk);
-    return TRUE;
-}
-
-/* Set urgency notification. */
-static void task_set_urgency(Task * tk)
-{
-    LaunchTaskBarPlugin * tb = tk->tb;
-    TaskClass * tc = tk->p_taskclass;
-    if ((tb->grouped_tasks) && (tc != NULL) && (tc->visible_count > 1))
-        recompute_group_visibility_for_class(tk->tb, tk->p_taskclass);
-    else
-    {
-        /* Set the flashing context and flash the window immediately. */
-        tk->flash_state = TRUE;
-        flash_window_update(tk);
-
-        /* Set the timer if none is set. */
-        if (tk->flash_timeout == 0)
-            set_timer_on_task(tk);
-    }
-}
-
-/* Clear urgency notification. */
-static void task_clear_urgency(Task * tk)
-{
-    LaunchTaskBarPlugin * tb = tk->tb;
-    TaskClass * tc = tk->p_taskclass;
-    if ((tb->grouped_tasks) && (tc != NULL) && (tc->visible_count > 1))
-        recompute_group_visibility_for_class(tk->tb, tk->p_taskclass);
-    else
-    {
-        /* Remove the timer if one is set. */
-        if (tk->flash_timeout != 0)
-        {
-            g_source_remove(tk->flash_timeout);
-            tk->flash_timeout = 0;
-        }
-        if (tk->menu_item)
-        {
-            g_object_unref(tk->menu_item);
-            tk->menu_item = NULL;
-        }
-
-        /* Clear the flashing context and unflash the window immediately. */
-        tk->flash_state = FALSE;
-        flash_window_update(tk);
-        tk->flash_state = FALSE;
-    }
-}
-
-/* Do the proper steps to raise a window.
- * This means removing it from iconified state and bringing it to the front.
- * We also switch the active desktop and viewport if needed. */
-static void task_raise_window(Task * tk, guint32 time)
-{
-    Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
-
-    /* Change desktop if needed. */
-    if ((tk->desktop != ALL_WORKSPACES) && (tk->desktop != tk->tb->current_desktop))
-        Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, tk->desktop, 0, 0, 0, 0);
-
-    /* Evaluate use_net_active if not yet done. */
-    if ( ! tk->tb->net_active_checked)
-    {
-        LaunchTaskBarPlugin * tb = tk->tb;
-        GdkAtom net_active_atom = gdk_x11_xatom_to_atom(a_NET_ACTIVE_WINDOW);
-        tb->use_net_active = gdk_x11_screen_supports_net_wm_hint(tb->screen, net_active_atom);
-        tb->net_active_checked = TRUE;
-    }
-
-    /* Raise the window.  We can use NET_ACTIVE_WINDOW if the window manager supports it.
-     * Otherwise, do it the old way with XMapRaised and XSetInputFocus. */
-    if (tk->tb->use_net_active)
-        Xclimsg(tk->win, a_NET_ACTIVE_WINDOW, 2, time, 0, 0, 0);
-    else
-    {
-#if GTK_CHECK_VERSION(2, 24, 0)
-        GdkWindow * gdkwindow = gdk_x11_window_lookup_for_display(gdk_display_get_default(), tk->win);
-#else
-        GdkWindow * gdkwindow = gdk_xid_table_lookup(tk->win);
-#endif
-        if (gdkwindow != NULL)
-            gdk_window_show(gdkwindow);
-        else
-            XMapRaised(xdisplay, tk->win);
-
-    /* There is a race condition between the X server actually executing the XMapRaised and this code executing XSetInputFocus.
-     * If the window is not viewable, the XSetInputFocus will fail with BadMatch. */
-    XWindowAttributes attr;
-    Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
-    XGetWindowAttributes(xdisplay, tk->win, &attr);
-    if (attr.map_state == IsViewable)
-            XSetInputFocus(xdisplay, tk->win, RevertToNone, time);
-    }
-
-    /* Change viewport if needed. */
-    XWindowAttributes xwa;
-    XGetWindowAttributes(xdisplay, tk->win, &xwa);
-    Xclimsg(tk->win, a_NET_DESKTOP_VIEWPORT, xwa.x, xwa.y, 0, 0, 0);
-}
-
-/* Position-calculation callback for grouped-task and window-management popup menu. */
-static void taskbar_popup_set_position(GtkWidget * menu, gint * px, gint * py, gboolean * push_in, gpointer data)
-{
-    Task * tk = (Task *) data;
-
-    /* Determine the coordinates. */
-    lxpanel_plugin_popup_set_position_helper(tk->tb->panel, tk->button, menu,
-                                             px, py);
-    *push_in = TRUE;
-}
-
-/* Handler for "activate" event from "close all windows" menu*/
-static void taskbar_close_all_windows (GtkWidget * widget, Task * tk )
-{
-    Task * tk_cursor;
-    for (tk_cursor = tk->p_taskclass->p_task_head; tk_cursor != NULL;
-            tk_cursor = tk_cursor->p_task_flink_same_class)
-    {
-        if (task_is_visible_on_current_desktop(tk->tb, tk_cursor))
-        {
-            Xclimsgwm(tk_cursor->win, a_WM_PROTOCOLS, a_WM_DELETE_WINDOW);
-        }
-    }
-    task_group_menu_destroy(tk->tb);
-}
-
-/* Remove the grouped-task popup menu from the screen. */
-static void task_group_menu_destroy(LaunchTaskBarPlugin * tb)
-{
-    if (tb->group_menu != NULL)
-    {
-        gtk_widget_destroy(tb->group_menu);
-        tb->group_menu = NULL;
-    }
-}
-
-/* Handler for "button-press-event" event from taskbar button,
- * or "activate" event from grouped-task popup menu item. */
-static gboolean taskbar_task_control_event(GtkWidget * widget, GdkEventButton * event, Task * tk, gboolean popup_menu)
-{
-    LaunchTaskBarPlugin * tb = tk->tb;
-    TaskClass * tc = tk->p_taskclass;
-    if ((tb->grouped_tasks) && (tc != NULL) && (tc->visible_count > 1) && (GTK_IS_BUTTON(widget)))
-    {
-        /* This is grouped-task representative, meaning that there is a class
-         * with at least two windows. */
-        GtkWidget * menu = NULL;
-        if( event->button == 1 ) /* Left click */
-        {
-            menu = gtk_menu_new();
-            /* Bring up a popup menu listing all the class members. */
-            Task * tk_cursor;
-            GtkWidget * flashing_menu = NULL;
-            for (tk_cursor = tc->p_task_head; tk_cursor != NULL;
-                    tk_cursor = tk_cursor->p_task_flink_same_class)
-            {
-                if (task_is_visible_on_current_desktop(tb, tk_cursor))
-                {
-                    /* The menu item has the name, or the iconified name, and
-                     * the icon of the application window. */
-                    GtkWidget * mi = gtk_image_menu_item_new_with_label(((tk_cursor->iconified) ?
-                                tk_cursor->name_iconified : tk_cursor->name));
-                    GtkWidget * im = gtk_image_new_from_pixbuf(gtk_image_get_pixbuf(
-                                GTK_IMAGE(tk_cursor->image)));
-                    gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), im);
-                    g_signal_connect(mi, "button-press-event",
-                            G_CALLBACK(taskbar_popup_activate_event), (gpointer) tk_cursor);
-                    gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
-                    /* set mi as if it's urgent with reference */
-                    if (tk_cursor->menu_item != NULL)
-                        g_object_unref(tk_cursor->menu_item);
-                    tk_cursor->menu_item = NULL;
-                    if (tk_cursor->urgency && !tk_cursor->focused && flashing_menu == NULL)
-                        flashing_menu = g_object_ref_sink(mi);
-                }
-            }
-            /* since tc->visible_count > 1, tc->p_task_visible cannot be NULL */
-            g_assert(tc->p_task_visible != NULL);
-            g_assert(tc->p_task_visible->menu_item == NULL);
-            tc->p_task_visible->menu_item = flashing_menu;
-        }
-        else if(event->button == 3) /* Right click */
-        {
-            menu = gtk_menu_new();
-            GtkWidget * mi = gtk_menu_item_new_with_mnemonic (_("_Close all windows"));
-            gtk_menu_shell_append ( GTK_MENU_SHELL(menu), mi);
-            g_signal_connect( mi, "activate", G_CALLBACK(taskbar_close_all_windows), tk);
-        }
-
-        /* Show the menu.  Set context so we can find the menu later to dismiss it.
-         * Use a position-calculation callback to get the menu nicely
-         * positioned with respect to the button. */
-        if (menu) {
-            gtk_widget_show_all(menu);
-            task_group_menu_destroy(tb);
-            tb->group_menu = menu;
-            gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
-                    (GtkMenuPositionFunc) taskbar_popup_set_position, (gpointer) tk,
-                    event->button, event->time);
-        }
-    }
-    else
-    {
-        /* Not a grouped-task representative, or entered from the grouped-task popup menu. */
-        Task * visible_task = (((tk->p_taskclass == NULL) || ( ! tk->tb->grouped_tasks)) ? tk : tk->p_taskclass->p_task_visible);
-        task_group_menu_destroy(tb);
-
-        if (event->button == 1)
-        {
-            Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
-            /* Left button.
-             * If the task is iconified, raise it.
-             * If the task is not iconified and has focus, iconify it.
-             * If the task is not iconified and does not have focus, raise it. */
-            if (tk->iconified)
-                task_raise_window(tk, event->time);
-            else if ((tk->focused) || (tk == tb->focused_previous))
-                XIconifyWindow(xdisplay, tk->win, DefaultScreen(xdisplay));
-            else
-                task_raise_window(tk, event->time);
-        }
-        else if (event->button == 2)
-        {
-            /* Middle button.  Toggle the shaded state of the window. */
-            Xclimsg(tk->win, a_NET_WM_STATE,
-                2,      /* a_NET_WM_STATE_TOGGLE */
-                a_NET_WM_STATE_SHADED,
-                0, 0, 0);
-        }
-        else if(event->button == 3)
-        {
-            /* Right button.  Bring up the window state popup menu. */
-            tk->tb->menutask = tk;
-#ifndef DISABLE_MENU
-            LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)tk->tb;
-            if(ltbp->mode == LAUNCHTASKBAR)
-            {
-                FmFileInfo *fi = f_find_menu_launchbutton_recursive(tk->exec_bin);
-                LaunchButton *btn = launchbar_exec_bin_exists(ltbp, fi);
-                /* FIXME: shouldn't we make file info at task button creation? */
-#ifdef DEBUG
-                g_print("\nTB '%s' right-click, in LB: %c\n", tk->exec_bin, btn != NULL ? 'Y':'N');
+#ifndef DISABLE_MENU
+            if(ltbp->mode == LAUNCHTASKBAR)
+            {
+                FmFileInfo *fi = f_find_menu_launchbutton_recursive(win, ltbp);
+                LaunchButton *btn = launchbar_exec_bin_exists(ltbp, fi);
+                /* FIXME: shouldn't we make file info at task button creation? */
+#ifdef DEBUG
+                g_print("\nTB '%s' right-click, in LB: %c\n", tk->exec_bin, btn != NULL ? 'Y':'N');
 #endif
                 if(btn != NULL)
                 {
@@ -2619,8 +1629,9 @@ static gboolean taskbar_task_control_event(GtkWidget * widget, GdkEventButton *
                     gtk_widget_set_visible(ltbp->p_menuitem_new_instance, fi != NULL);
                 }
                 gtk_widget_set_visible(ltbp->p_menuitem_separator, TRUE);
-                if (fi)
-                    fm_file_info_unref(fi);
+                if (ltbp->fi)
+                    fm_file_info_unref(ltbp->fi);
+                ltbp->fi = fi;
             }
             else
             {
@@ -2630,80 +1641,34 @@ static gboolean taskbar_task_control_event(GtkWidget * widget, GdkEventButton *
                 gtk_widget_set_visible(ltbp->p_menuitem_separator, FALSE);
             }
 #endif
-            if (tb->workspace_menu0)
-            {
-                GList *items = gtk_container_get_children(GTK_CONTAINER(gtk_widget_get_parent(tb->workspace_menu0)));
-                GList *item = g_list_find(items, tb->workspace_menu0);
-                int i;
-                if (item != NULL) /* else error */
-                    for (i = 0; i < tb->number_of_desktops; i++, item = item->next)
-                        gtk_widget_set_sensitive(item->data, i != tk->desktop);
-                g_list_free(items);
-            }
-            gtk_menu_popup(
-                GTK_MENU(tb->menu),
-                NULL, NULL,
-                (GtkMenuPositionFunc) taskbar_popup_set_position, (gpointer) visible_task,
-                0, event->time);
-        }
-    }
-
-    /* As a matter of policy, avoid showing selected or prelight states on flat buttons. */
-    if (tb->flat_button)
-        gtk_widget_set_state(widget, GTK_STATE_NORMAL);
-    return TRUE;
-}
-
-/* Handler for "button-press-event" event from taskbar button. */
-static gboolean taskbar_button_press_event(GtkWidget * widget, GdkEventButton * event, Task * tk)
-{
-    // needed to prevent releasing focused task button
-    return TRUE;
-}
-
-/* Handler for "button-release-event" event from taskbar button. */
-static gboolean taskbar_button_release_event(GtkWidget * widget, GdkEventButton * event, Task * tk)
-{
-    if (!tk->tb->dnd_task_moving && tk->entered_state)
-        /* SF bug#731: don't process button release with DND. Also if button was
-           released outside of widget but DND wasn't activated: this might happen
-           if drag started at edge of button so drag treshold wasn't reached. */
-        return taskbar_task_control_event(widget, event, tk, FALSE);
-    return TRUE;
-}
-
-/* Handler for "activate" event from grouped-task popup menu item. */
-static gboolean taskbar_popup_activate_event(GtkWidget * widget, GdkEventButton * event, Task * tk)
-{
-    return taskbar_task_control_event(widget, event, tk, TRUE);
 }
 
 /* Handler for "drag-motion" timeout. */
-static gboolean taskbar_button_drag_motion_timeout(Task * tk)
+static gboolean taskbar_button_drag_motion_timeout(LaunchTaskBarPlugin * tb)
 {
-    guint time;
+    //guint time;
     if (g_source_is_destroyed(g_main_current_source()))
         return FALSE;
-    time = gtk_get_current_event_time();
-    task_raise_window(tk, ((time != 0) ? time : CurrentTime));
-    tk->tb->dnd_delay_timer = 0;
+    //time = gtk_get_current_event_time();
+    //task_raise_window(tk, ((time != 0) ? time : CurrentTime)); // ???
+    tb->dnd_delay_timer = 0;
     return FALSE;
 }
 
 /* Handler for "drag-motion" event from taskbar button. */
-static gboolean taskbar_button_drag_motion(GtkWidget * widget, GdkDragContext * drag_context, gint x, gint y, guint time, Task * tk)
+static gboolean taskbar_button_drag_motion(GtkWidget * widget, GdkDragContext * drag_context, gint x, gint y, guint time, LaunchTaskBarPlugin * tb)
 {
     GtkWidget * drag_source = gtk_drag_get_source_widget(drag_context);
-    if (drag_source != NULL && gtk_widget_get_parent(drag_source) == gtk_widget_get_parent(tk->button))
+    if (drag_source != NULL && gtk_widget_get_parent(drag_source) == gtk_widget_get_parent(widget))
     {
-        tk->tb->dnd_task_moving = TRUE;
+        tb->dnd_task_moving = TRUE;
         gdk_drag_status(drag_context, GDK_ACTION_MOVE, time);
     }
     else
     {
         /* Prevent excessive motion notification. */
-        if (tk->tb->dnd_delay_timer == 0)
-            tk->tb->dnd_delay_timer = g_timeout_add(DRAG_ACTIVE_DELAY, (GSourceFunc) taskbar_button_drag_motion_timeout, tk);
+        if (tb->dnd_delay_timer == 0)
+            tb->dnd_delay_timer = g_timeout_add(DRAG_ACTIVE_DELAY, (GSourceFunc) taskbar_button_drag_motion_timeout, tb);
 
         gdk_drag_status(drag_context, 0, time);
     }
@@ -2711,16 +1676,16 @@ static gboolean taskbar_button_drag_motion(GtkWidget * widget, GdkDragContext *
 }
 
 /* Handler for "drag-drop" event from taskbar button. */
-static gboolean taskbar_button_drag_drop(GtkWidget * widget, GdkDragContext * drag_context, gint x, gint y, guint time, Task * tk)
+static gboolean taskbar_button_drag_drop(GtkWidget * widget, GdkDragContext * drag_context, gint x, gint y, guint time, LaunchTaskBarPlugin * tb)
 {
-    tk->tb->dnd_task_moving = FALSE;
+    tb->dnd_task_moving = FALSE;
     GtkWidget * drag_source = gtk_drag_get_source_widget(drag_context);
-    if (drag_source != NULL && gtk_widget_get_parent(drag_source) == gtk_widget_get_parent(tk->button))
+    if (drag_source != NULL && gtk_widget_get_parent(drag_source) == gtk_widget_get_parent(widget))
     {
-        if (drag_source != tk->button)
+        if (drag_source != widget)
         {
-            PanelIconGrid *ig = PANEL_ICON_GRID(tk->tb->tb_icon_grid);
-            gint i = panel_icon_grid_get_child_position(ig, tk->button);
+            PanelIconGrid *ig = PANEL_ICON_GRID(tb->tb_icon_grid);
+            gint i = panel_icon_grid_get_child_position(ig, widget);
             panel_icon_grid_reorder_child(ig, drag_source, i);
         }
         gtk_drag_finish(drag_context, TRUE, TRUE, time);
@@ -2731,104 +1696,32 @@ static gboolean taskbar_button_drag_drop(GtkWidget * widget, GdkDragContext * dr
 }
 
 /* Handler for "drag-leave" event from taskbar button. */
-static void taskbar_button_drag_leave(GtkWidget * widget, GdkDragContext * drag_context, guint time, Task * tk)
+static void taskbar_button_drag_leave(GtkWidget * widget, GdkDragContext * drag_context, guint time, LaunchTaskBarPlugin * tb)
 {
     /* Cancel the timer if set. */
-    if (tk->tb->dnd_delay_timer != 0)
+    if (tb->dnd_delay_timer != 0)
     {
-        g_source_remove(tk->tb->dnd_delay_timer);
-        tk->tb->dnd_delay_timer = 0;
+        g_source_remove(tb->dnd_delay_timer);
+        tb->dnd_delay_timer = 0;
     }
     return;
 }
 
 /* Handler for "enter" event from taskbar button.  This indicates that the cursor position has entered the button. */
-static void taskbar_button_enter(GtkWidget * widget, Task * tk)
-{
-    tk->tb->dnd_task_moving = FALSE;
-    tk->entered_state = TRUE;
-    if (tk->tb->flat_button)
-        gtk_widget_set_state(widget, GTK_STATE_NORMAL);
-    task_draw_label(tk);
-}
-
-/* Handler for "leave" event from taskbar button.  This indicates that the cursor position has left the button. */
-static void taskbar_button_leave(GtkWidget * widget, Task * tk)
-{
-    tk->entered_state = FALSE;
-    task_draw_label(tk);
-}
-
-/* Handler for "scroll-event" event from taskbar button. */
-static gboolean taskbar_button_scroll_event(GtkWidget * widget, GdkEventScroll * event, Task * tk)
-{
-    LaunchTaskBarPlugin * tb = tk->tb;
-    TaskClass * tc = tk->p_taskclass;
-    if ((tb->use_mouse_wheel)
-    && (( ! tb->grouped_tasks) || (tc == NULL) || (tc->visible_count == 1)))
-    {
-        if ((event->direction == GDK_SCROLL_UP) || (event->direction == GDK_SCROLL_LEFT))
-            task_raise_window(tk, event->time);
-        else
-        {
-            Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
-            XIconifyWindow(xdisplay, tk->win, DefaultScreen(xdisplay));
-        }
-    }
-    return TRUE;
-}
-
-/* Handler for "size-allocate" event from taskbar button. */
-static void taskbar_button_size_allocate(GtkWidget * btn, GtkAllocation * alloc, Task * tk)
+static void taskbar_button_enter(GtkWidget * widget, GdkEvent *event, LaunchTaskBarPlugin * tb)
 {
-    if (gtk_widget_get_realized(btn))
-    {
-        /* Get the coordinates of the button. */
-        int x, y;
-        gdk_window_get_origin(gtk_button_get_event_window(GTK_BUTTON(btn)), &x, &y);
-
-
-        /* Send a NET_WM_ICON_GEOMETRY property change on the window. */
-        gulong data[4];
-        data[0] = x;
-        data[1] = y;
-        data[2] = alloc->width;
-        data[3] = alloc->height;
-        XChangeProperty(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), tk->win,
-            gdk_x11_get_xatom_by_name("_NET_WM_ICON_GEOMETRY"),
-            XA_CARDINAL, 32, PropModeReplace, (guchar *) &data, 4);
-    }
-}
-
-/* Update style on the taskbar when created or after a configuration change. */
-static void taskbar_update_style(LaunchTaskBarPlugin * tb)
-{
-    panel_icon_grid_set_geometry(PANEL_ICON_GRID(tb->tb_icon_grid),
-        panel_get_orientation(tb->panel),
-        ((tb->icons_only) ? tb->icon_size + ICON_ONLY_EXTRA : tb->task_width_max),
-        tb->icon_size, tb->spacing, 0, panel_get_height(tb->panel));
+    tb->dnd_task_moving = FALSE;
 }
 
-/* Update style on a task button when created or after a configuration change. */
-static void task_update_style(Task * tk, LaunchTaskBarPlugin * tb)
+/* Handler for "button-release-event" event from taskbar button. */
+static gboolean taskbar_button_release_event(GtkWidget * widget, GdkEventButton * event, LaunchTaskBarPlugin * tb)
 {
-    if (tb->icons_only)
-        gtk_widget_hide(tk->label);
-    else
-        gtk_widget_show(tk->label);
-
-    if( tb->flat_button )
-    {
-        gtk_toggle_button_set_active((GtkToggleButton*)tk->button, FALSE);
-        gtk_button_set_relief(GTK_BUTTON(tk->button), GTK_RELIEF_NONE);
-    }
-    else
-    {
-        gtk_toggle_button_set_active((GtkToggleButton*)tk->button, tk->focused);
-        gtk_button_set_relief(GTK_BUTTON(tk->button), GTK_RELIEF_NORMAL);
-    }
-
-    task_draw_label(tk);
+    if (tb->dnd_task_moving)
+        /* SF bug#731: don't process button release with DND. Also if button was
+           released outside of widget but DND wasn't activated: this might happen
+           if drag started at edge of button so drag treshold wasn't reached. */
+        return TRUE;
+    return FALSE;
 }
 
 enum {
@@ -2842,95 +1735,52 @@ static GtkTargetEntry task_button_target_list[] = {
 static guint task_button_n_targets = G_N_ELEMENTS(task_button_target_list);
 
 /* Build graphic elements needed for a task button. */
-static void task_build_gui(LaunchTaskBarPlugin * tb, Task * tk)
-{
-    GdkDisplay *display = gdk_display_get_default();
-    /* NOTE
-     * 1. the extended mask is sum of taskbar and pager needs
-     * see bug [ 940441 ] pager loose track of windows
-     *
-     * Do not change event mask to gtk windows spawned by this gtk client
-     * this breaks gtk internals */
-#if GTK_CHECK_VERSION(2, 24, 0)
-    if (!gdk_x11_window_lookup_for_display(display, tk->win))
-#else
-    if (! gdk_window_lookup(tk->win))
-#endif
-        XSelectInput(GDK_DISPLAY_XDISPLAY(display), tk->win,
-                     PropertyChangeMask | StructureNotifyMask);
-
+static void taskbar_add_task_button(LaunchTaskBarPlugin * tb, TaskButton * task)
+{
     /* Allocate a toggle button as the top level widget. */
-    tk->button = gtk_toggle_button_new();
-    gtk_container_set_border_width(GTK_CONTAINER(tk->button), 0);
-    if (!tb->flat_button)
-        gtk_widget_set_state(tk->button, GTK_STATE_NORMAL);
-    gtk_drag_dest_set(tk->button, 0, NULL, 0, 0);
-    gtk_drag_source_set(tk->button, GDK_BUTTON1_MASK, task_button_target_list, task_button_n_targets, GDK_ACTION_MOVE);
-#if GTK_CHECK_VERSION(3, 0, 0)
-    gtk_widget_add_events(tk->button, GDK_SCROLL_MASK);
-#endif
+    gtk_container_add(GTK_CONTAINER(tb->tb_icon_grid), GTK_WIDGET(task));
+    gtk_widget_show(GTK_WIDGET(task));
 
     /* Connect signals to the button. */
-    g_signal_connect(tk->button, "button-press-event", G_CALLBACK(taskbar_button_press_event), (gpointer) tk);
-    g_signal_connect(tk->button, "button-release-event", G_CALLBACK(taskbar_button_release_event), (gpointer) tk);
-    g_signal_connect(G_OBJECT(tk->button), "drag-motion", G_CALLBACK(taskbar_button_drag_motion), (gpointer) tk);
-    g_signal_connect(G_OBJECT(tk->button), "drag-leave", G_CALLBACK(taskbar_button_drag_leave), (gpointer) tk);
-    g_signal_connect(G_OBJECT(tk->button), "drag-drop", G_CALLBACK(taskbar_button_drag_drop), (gpointer) tk);
-    g_signal_connect_after(G_OBJECT (tk->button), "enter", G_CALLBACK(taskbar_button_enter), (gpointer) tk);
-    g_signal_connect_after(G_OBJECT (tk->button), "leave", G_CALLBACK(taskbar_button_leave), (gpointer) tk);
-    g_signal_connect_after(G_OBJECT(tk->button), "scroll-event", G_CALLBACK(taskbar_button_scroll_event), (gpointer) tk);
-    g_signal_connect(tk->button, "size-allocate", G_CALLBACK(taskbar_button_size_allocate), (gpointer) tk);
-
-    /* Create a box to contain the application icon and window title. */
-    GtkWidget * container = gtk_hbox_new(FALSE, 1);
-    gtk_container_set_border_width(GTK_CONTAINER(container), 0);
-
-    /* Create an image to contain the application icon and add it to the box. */
-    GdkPixbuf* pixbuf = task_update_icon(tb, tk, None);
-    tk->image = gtk_image_new_from_pixbuf(pixbuf);
-    gtk_misc_set_padding(GTK_MISC(tk->image), 0, 0);
-    g_object_unref(pixbuf);
-    gtk_widget_show(tk->image);
-    gtk_box_pack_start(GTK_BOX(container), tk->image, FALSE, FALSE, 0);
-
-    /* Create a label to contain the window title and add it to the box. */
-    tk->label = gtk_label_new(NULL);
-    gtk_misc_set_alignment(GTK_MISC(tk->label), 0.0, 0.5);
-    gtk_label_set_ellipsize(GTK_LABEL(tk->label), PANGO_ELLIPSIZE_END);
-    gtk_box_pack_start(GTK_BOX(container), tk->label, TRUE, TRUE, 0);
-
-    /* Add the box to the button. */
-    gtk_container_add(GTK_CONTAINER(tk->button), container);
-    gtk_container_set_border_width(GTK_CONTAINER(tk->button), 0);
-
-    /* Add the button to the taskbar. */
-    gtk_container_add(GTK_CONTAINER(tb->tb_icon_grid), tk->button);
-    gtk_widget_show_all(tk->button);
-    gtk_widget_set_can_focus(GTK_WIDGET(tk->button),FALSE);
-    gtk_widget_set_can_default(GTK_WIDGET(tk->button),FALSE);
-
-    /* Update styles on the button. */
-    task_update_style(tk, tb);
-
-    /* Flash button for window with urgency hint. */
-    if (tk->urgency && !tk->focused)
-        task_set_urgency(tk);
-}
-
-/* Determine which monitor a given window is associated with */
-static gint get_window_monitor(Window win)
-{
-    GdkDisplay *display;
-    GdkWindow *gwin;
-    gint m;
-
-    display = gdk_display_get_default();
-    g_assert(display);
-    gwin = gdk_x11_window_foreign_new_for_display(display,win);
-    g_assert(gwin);
-    m = gdk_screen_get_monitor_at_window(gdk_window_get_screen(gwin),gwin);
-    g_object_unref(gwin);
-    return m;
+    /* handle menu callbacks */
+    g_signal_connect(G_OBJECT(task), "menu-built",
+                     (GCallback)on_task_menu_built, tb);
+    g_signal_connect(G_OBJECT(task), "menu-target-set",
+                     (GCallback)on_task_menu_target_set, tb);
+    /* handle drag & drop on task buttons */
+    gtk_drag_dest_set(GTK_WIDGET(task), 0, NULL, 0, 0);
+    gtk_drag_source_set(GTK_WIDGET(task), GDK_BUTTON1_MASK,
+                        task_button_target_list, task_button_n_targets,
+                        GDK_ACTION_MOVE);
+    g_signal_connect(G_OBJECT(task), "drag-motion",
+                     G_CALLBACK(taskbar_button_drag_motion), tb);
+    g_signal_connect(G_OBJECT(task), "drag-leave",
+                     G_CALLBACK(taskbar_button_drag_leave), tb);
+    g_signal_connect(G_OBJECT(task), "drag-drop",
+                     G_CALLBACK(taskbar_button_drag_drop), tb);
+    g_signal_connect(task, "button-release-event",
+                     G_CALLBACK(taskbar_button_release_event), tb);
+    g_signal_connect_after(G_OBJECT(task), "enter-notify-event",
+                           G_CALLBACK(taskbar_button_enter), tb);
+}
+
+/* add win to tb, using list of task buttons */
+static void taskbar_add_new_window(LaunchTaskBarPlugin * tb, Window win, GList *list)
+{
+    gchar *res_class = task_get_class(win);
+    TaskButton *task;
+
+    if (!tb->grouped_tasks || res_class == NULL)
+        list = NULL;
+    else for (; list; list = list->next)
+        if (task_button_add_window(list->data, win, res_class))
+            break;
+    if (list != NULL)
+        return; /* some button accepted it, done */
+
+    task = task_button_new(win, tb->current_desktop, tb->number_of_desktops,
+                           tb->panel, res_class, tb->flags);
+    taskbar_add_task_button(tb, task);
 }
 
 /*****************************************************
@@ -2946,36 +1796,27 @@ static void taskbar_net_client_list(GtkWidget * widget, LaunchTaskBarPlugin * tb
     /* Get the NET_CLIENT_LIST property. */
     int client_count;
     Window * client_list = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_CLIENT_LIST, XA_WINDOW, &client_count);
-    Task * tk;
-    for (tk = tb->p_task_list; tk != NULL; tk = tk->p_task_flink_xwid)
-        tk->present_in_client_list = FALSE;
     if (client_list != NULL)
     {
+        GList *children = gtk_container_get_children(GTK_CONTAINER(tb->tb_icon_grid)), *l;
+        /* Remove windows from the task list that are not present in the NET_CLIENT_LIST. */
+        for (l = children; l; l = l->next)
+            task_button_update_windows_list(l->data, client_list, client_count);
+        g_list_free(children);
+        children = gtk_container_get_children(GTK_CONTAINER(tb->tb_icon_grid));
         /* Loop over client list, correlating it with task list. */
         int i;
         for (i = 0; i < client_count; i++)
         {
             /* Search for the window in the task list.  Set up context to do an insert right away if needed. */
-            Task * tk_pred = NULL;
-            Task * tk_cursor;
-            Task * tk = NULL;
-            for (tk_cursor = tb->p_task_list; tk_cursor != NULL; tk_pred = tk_cursor, tk_cursor = tk_cursor->p_task_flink_xwid)
+            for (l = children; l; l = l->next)
             {
-                if (tk_cursor->win == client_list[i])
-                {
-                    tk = tk_cursor;
-                    break;
-                }
-                if (tk_cursor->win > client_list[i])
+                if (task_button_has_window(l->data, client_list[i]))
                     break;
             }
 
-            /* Task is already in task list. */
-            if (tk != NULL)
-                tk->present_in_client_list = TRUE;
-
             /* Task is not in task list. */
-            else
+            if (l == NULL)
             {
                 /* Evaluate window state and window type to see if it should be in task list. */
                 NetWMWindowType nwwt;
@@ -2986,60 +1827,19 @@ static void taskbar_net_client_list(GtkWidget * widget, LaunchTaskBarPlugin * tb
                 && (accept_net_wm_window_type(&nwwt)))
                 {
                     /* Allocate and initialize new task structure. */
-                    tk = g_new0(Task, 1);
-                    tk->present_in_client_list = TRUE;
-                    tk->win = client_list[i];
-                    tk->tb = tb;
-                    tk->name_source = None;
-                    tk->image_source = None;
-                    tk->iconified = (get_wm_state(tk->win) == IconicState);
-                    tk->desktop = get_net_wm_desktop(tk->win);
-                    if (tb->same_monitor_only)
-                        tk->monitor = get_window_monitor(tk->win);
-                    if (tb->use_urgency_hint)
-                        tk->urgency = task_has_urgency(tk);
-                    task_build_gui(tb, tk);
-                    task_set_names(tk, None);
-                    task_set_class(tk);
-
-                    /* Link the task structure into the task list. */
-                    if (tk_pred == NULL)
-                    {
-                        tk->p_task_flink_xwid = tb->p_task_list;
-                        tb->p_task_list = tk;
-                    }
-                    else
-                    {
-                        tk->p_task_flink_xwid = tk_pred->p_task_flink_xwid;
-                        tk_pred->p_task_flink_xwid = tk;
-                    }
+                    taskbar_add_new_window(tb, client_list[i], children);
+                    g_list_free(children);
+                    children = gtk_container_get_children(GTK_CONTAINER(tb->tb_icon_grid));
                 }
             }
         }
+        g_list_free(children);
         XFree(client_list);
     }
 
-    /* Remove windows from the task list that are not present in the NET_CLIENT_LIST. */
-    Task * tk_pred = NULL;
-    tk = tb->p_task_list;
-    while (tk != NULL)
-    {
-        Task * tk_succ = tk->p_task_flink_xwid;
-        if (tk->present_in_client_list)
-            tk_pred = tk;
-        else
-        {
-            if (tk_pred == NULL)
-                tb->p_task_list = tk_succ;
-            else
-                tk_pred->p_task_flink_xwid = tk_succ;
-            task_delete(tb, tk, FALSE, TRUE);
-        }
-        tk = tk_succ;
-    }
-
-    /* Redraw the taskbar. */
-    taskbar_redraw(tb);
+    else /* clear taskbar */
+        gtk_container_foreach(GTK_CONTAINER(tb->tb_icon_grid),
+                              (GtkCallback)gtk_widget_destroy, NULL);
 }
 
 /* Handler for "current-desktop" event from root window listener. */
@@ -3050,7 +1850,6 @@ static void taskbar_net_current_desktop(GtkWidget * widget, LaunchTaskBarPlugin
 
     /* Store the local copy of current desktops.  Redisplay the taskbar. */
     tb->current_desktop = get_net_current_desktop();
-    recompute_group_visibility_on_current_desktop(tb);
     taskbar_redraw(tb);
 }
 
@@ -3062,7 +1861,7 @@ static void taskbar_net_number_of_desktops(GtkWidget * widget, LaunchTaskBarPlug
 
     /* Store the local copy of number of desktops.  Recompute the popup menu and redisplay the taskbar. */
     tb->number_of_desktops = get_net_number_of_desktops();
-    taskbar_make_menu(tb);
+    taskbar_reset_menu(tb);
     taskbar_redraw(tb);
 }
 
@@ -3072,82 +1871,13 @@ static void taskbar_net_active_window(GtkWidget * widget, LaunchTaskBarPlugin *
     LaunchTaskBarPlugin *ltbp = tb;
     if(ltbp->mode == LAUNCHBAR) return;
 
-    gboolean drop_old = FALSE;
-    gboolean make_new = FALSE;
-    Task * ctk = tb->focused;
-    Task * ntk = NULL;
-
     /* Get the window that has focus. */
     Window * f = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_ACTIVE_WINDOW, XA_WINDOW, 0);
-    if (f == NULL)
-    {
-        /* No window has focus. */
-        drop_old = TRUE;
-        tb->focused_previous = NULL;
-    }
-    else
-    {
-        if (*f == panel_get_xwindow(tb->panel))
-        {
-        /* Taskbar window gained focus (this isn't supposed to be able to happen).  Remember current focus. */
-            if (ctk != NULL)
-            {
-                tb->focused_previous = ctk;
-                drop_old = TRUE;
-            }
-        }
-        else
-        {
-            /* Identify task that gained focus. */
-            tb->focused_previous = NULL;
-            ntk = task_lookup(tb, *f);
-            if (ntk != ctk)
-            {
-                drop_old = TRUE;
-                make_new = TRUE;
-            }
-        }
-        XFree(f);
-    }
-
-    /* If our idea of the current task lost focus, update data structures. */
-    if ((ctk != NULL) && (drop_old))
-    {
-        ctk->focused = FALSE;
-        if (ctk->urgency)
-            task_set_urgency(ctk);
-        tb->focused = NULL;
-        if(!tb->flat_button) /* relieve the button if flat buttons is not used. */
-            gtk_toggle_button_set_active((GtkToggleButton*)ctk->button, FALSE);
-
-        task_button_redraw(ctk, tb);
-    }
 
-    /* If a task gained focus, update data structures. */
-    if ((ntk != NULL) && (make_new))
-    {
-        if(!tb->flat_button) /* depress the button if flat buttons is not used. */
-            gtk_toggle_button_set_active((GtkToggleButton*)ntk->button, TRUE);
-        ntk->focused = TRUE;
-        if (ntk->urgency)
-            task_clear_urgency(ntk);
-        tb->focused = ntk;
-        task_button_redraw(ntk, tb);
-    }
-}
-
-/* Determine if the "urgency" hint is set on a window. */
-static gboolean task_has_urgency(Task * tk)
-{
-    gboolean result = FALSE;
-    XWMHints * hints = (XWMHints *) get_xaproperty(tk->win, XA_WM_HINTS, XA_WM_HINTS, 0);
-    if (hints != NULL)
-    {
-        if (hints->flags & XUrgencyHint)
-            result = TRUE;
-        XFree(hints);
-    }
-    return result;
+    gtk_container_foreach(GTK_CONTAINER(tb->tb_icon_grid),
+                          (GtkCallback)task_button_window_focus_changed, f);
+    if (f != NULL)
+        XFree(f);
 }
 
 /* Handle PropertyNotify event.
@@ -3163,7 +1893,7 @@ static void taskbar_property_notify_event(LaunchTaskBarPlugin *tb, XEvent *ev)
         if (win != GDK_ROOT_WINDOW())
         {
             /* Look up task structure by X window handle. */
-            Task * tk = task_lookup(tb, win);
+            TaskButton * tk = task_lookup(tb, win);
             if (tk != NULL)
             {
                 /* Install an error handler that ignores BadWindow.
@@ -3171,88 +1901,37 @@ static void taskbar_property_notify_event(LaunchTaskBarPlugin *tb, XEvent *ev)
                 XErrorHandler previous_error_handler = XSetErrorHandler(panel_handle_x_error_swallow_BadWindow_BadDrawable);
 
                 /* Dispatch on atom. */
-                if (at == a_NET_WM_DESKTOP)
-                {
-                    /* Window changed desktop. */
-                    tk->desktop = get_net_wm_desktop(win);
-                    taskbar_redraw(tb);
-                }
-                else if ((at == XA_WM_NAME) || (at == a_NET_WM_NAME) || (at == a_NET_WM_VISIBLE_NAME))
-                {
-                    /* Window changed name. */
-                    task_set_names(tk, at);
-                    if (tk->p_taskclass != NULL)
-                    {
-                        /* A change to the window name may change the visible name of the class. */
-                        recompute_group_visibility_for_class(tb, tk->p_taskclass);
-                        if (tk->p_taskclass->p_task_visible != NULL)
-                            task_draw_label(tk->p_taskclass->p_task_visible);
-                    }
-                }
-                else if (at == XA_WM_CLASS)
-                {
-                    /* Window changed class. */
-                    task_set_class(tk);
-                    taskbar_redraw(tb);
-                }
-                else if (at == a_WM_STATE)
-                {
-                    /* Window changed state. */
-                    tk->iconified = (get_wm_state(win) == IconicState);
-                    task_draw_label(tk);
-                }
-                else if (at == XA_WM_HINTS)
-                {
-                    /* Window changed "window manager hints".
-                     * Some windows set their WM_HINTS icon after mapping. */
-                    GdkPixbuf * pixbuf = task_update_icon(tb, tk, XA_WM_HINTS);
-                    if (pixbuf != NULL)
-                    {
-                        gtk_image_set_from_pixbuf(GTK_IMAGE(tk->image), pixbuf);
-                        g_object_unref(pixbuf);
-                    }
-
-                    if (tb->use_urgency_hint)
-                    {
-                        tk->urgency = task_has_urgency(tk);
-                        if (tk->urgency && !tk->focused)
-                            task_set_urgency(tk);
-                        else
-                            task_clear_urgency(tk);
-                    }
-                }
-                else if (at == a_NET_WM_STATE)
+                if (at == a_NET_WM_STATE)
                 {
                     /* Window changed EWMH state. */
                     NetWMState nws;
-                    get_net_wm_state(tk->win, &nws);
+                    get_net_wm_state(win, &nws);
                     if ( ! accept_net_wm_state(&nws))
-                    {
-                        task_delete(tb, tk, TRUE, TRUE);
-                        taskbar_redraw(tb);
-                    }
-                }
-                else if (at == a_NET_WM_ICON)
-                {
-                    /* Window changed EWMH icon. */
-                    GdkPixbuf * pixbuf = task_update_icon(tb, tk, a_NET_WM_ICON);
-                    if (pixbuf != NULL)
-                    {
-                        gtk_image_set_from_pixbuf(GTK_IMAGE(tk->image), pixbuf);
-                        g_object_unref(pixbuf);
-                    }
+                        task_button_drop_window(tk, win, FALSE);
                 }
                 else if (at == a_NET_WM_WINDOW_TYPE)
                 {
                     /* Window changed EWMH window type. */
                     NetWMWindowType nwwt;
-                    get_net_wm_window_type(tk->win, &nwwt);
+                    get_net_wm_window_type(win, &nwwt);
                     if ( ! accept_net_wm_window_type(&nwwt))
-                    {
-                        task_delete(tb, tk, TRUE, TRUE);
-                        taskbar_redraw(tb);
-                    }
+                        task_button_drop_window(tk, win, FALSE);
+                }
+                else if (at == XA_WM_CLASS && tb->grouped_tasks
+                         && task_button_drop_window(tk, win, TRUE))
+                {
+                    GList *children = gtk_container_get_children(GTK_CONTAINER(tb->tb_icon_grid));
+                    /* if Window was not single window of that class then
+                       add it to another class or make another button */
+                    taskbar_add_new_window(tb, win, children);
+                    g_list_free(children);
+                }
+                else
+                {
+                    /* simply notify button, it will handle the event */
+                    task_button_window_xprop_changed(tk, win, at);
                 }
+
                 XSetErrorHandler(previous_error_handler);
             }
         }
@@ -3264,22 +1943,18 @@ static void taskbar_configure_notify_event(LaunchTaskBarPlugin * tb, XConfigureE
 {
     /* If the same_monitor_only option is set and the window is on a different
        monitor than before, redraw the taskbar */
-    Task *task;
-    gint m;
-    if(tb->same_monitor_only && ev->window != GDK_ROOT_WINDOW())
+    TaskButton *task;
+
+    if (ev->window != GDK_ROOT_WINDOW())
     {
         task = task_lookup(tb, ev->window);
-        if(task)
+        if (task)
         {
             /* Deleted windows seem to get ConfigureNotify events too. */
             XErrorHandler previous_error_handler = XSetErrorHandler(panel_handle_x_error_swallow_BadWindow_BadDrawable);
 
-            m = get_window_monitor(task->win);
-            if(m != task->monitor)
-            {
-                task->monitor = m;
-                taskbar_redraw(tb);
-            }
+            /* Monitor might be changed so button might need update */
+            task_button_window_reconfigured(task, ev->window);
 
             XSetErrorHandler(previous_error_handler);
         }
@@ -3289,8 +1964,8 @@ static void taskbar_configure_notify_event(LaunchTaskBarPlugin * tb, XConfigureE
 /* GDK event filter. */
 static GdkFilterReturn taskbar_event_filter(XEvent * xev, GdkEvent * event, LaunchTaskBarPlugin * tb)
 {
-    LaunchTaskBarPlugin *ltbp = tb;
-    if(ltbp->mode == LAUNCHBAR) return GDK_FILTER_CONTINUE;
+    if (tb->mode == LAUNCHBAR)
+        return GDK_FILTER_CONTINUE;
 
     if (xev->type == PropertyNotify)
         taskbar_property_notify_event(tb, xev);
@@ -3300,78 +1975,17 @@ static GdkFilterReturn taskbar_event_filter(XEvent * xev, GdkEvent * event, Laun
     return GDK_FILTER_CONTINUE;
 }
 
-/* Handler for "activate" event on Raise item of right-click menu for task buttons. */
-static void menu_raise_window(GtkWidget * widget, LaunchTaskBarPlugin * tb)
-{
-    if ((tb->menutask->desktop != ALL_WORKSPACES) && (tb->menutask->desktop != tb->current_desktop))
-        Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, tb->menutask->desktop, 0, 0, 0, 0);
-    XMapRaised(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), tb->menutask->win);
-    task_group_menu_destroy(tb);
-}
-
-/* Handler for "activate" event on Restore item of right-click menu for task buttons. */
-static void menu_restore_window(GtkWidget * widget, LaunchTaskBarPlugin * tb)
-{
-#if GTK_CHECK_VERSION(2, 24, 0)
-    GdkWindow * win = gdk_x11_window_foreign_new_for_display(gdk_display_get_default(),
-                                                             tb->menutask->win);
-#else
-    GdkWindow * win = gdk_window_foreign_new(tb->menutask->win);
-#endif
-
-    gdk_window_unmaximize(win);
-    g_object_unref(win);
-    task_group_menu_destroy(tb);
-}
-
-/* Handler for "activate" event on Maximize item of right-click menu for task buttons. */
-static void menu_maximize_window(GtkWidget * widget, LaunchTaskBarPlugin * tb)
-{
-#if GTK_CHECK_VERSION(2, 24, 0)
-    GdkWindow * win = gdk_x11_window_foreign_new_for_display(gdk_display_get_default(),
-                                                             tb->menutask->win);
-#else
-    GdkWindow * win = gdk_window_foreign_new(tb->menutask->win);
-#endif
-
-    gdk_window_maximize(win);
-    g_object_unref(win);
-    task_group_menu_destroy(tb);
-}
-
-/* Handler for "activate" event on Iconify item of right-click menu for task buttons. */
-static void menu_iconify_window(GtkWidget * widget, LaunchTaskBarPlugin * tb)
-{
-    Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
-    XIconifyWindow(xdisplay, tb->menutask->win, DefaultScreen(xdisplay));
-    task_group_menu_destroy(tb);
-}
-
-/* Handler for "activate" event on Move to Workspace item of right-click menu for task buttons. */
-static void menu_move_to_workspace(GtkWidget * widget, LaunchTaskBarPlugin * tb)
-{
-    int num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "num"));
-    Xclimsg(tb->menutask->win, a_NET_WM_DESKTOP, num, 0, 0, 0, 0);
-    task_group_menu_destroy(tb);
-}
-
-/* Handler for "activate" event on Close item of right-click menu for task buttons. */
-static void menu_close_window(GtkWidget * widget, LaunchTaskBarPlugin * tb)
-{
-    Xclimsgwm(tb->menutask->win, a_WM_PROTOCOLS, a_WM_DELETE_WINDOW);
-    task_group_menu_destroy(tb);
-}
-
 #ifndef DISABLE_MENU
 static void  on_menuitem_lock_tbp_clicked(GtkWidget * widget, LaunchTaskBarPlugin * tb)
 {
-    FmFileInfo *fi = f_find_menu_launchbutton_recursive(tb->menutask->exec_bin);
+    FmFileInfo *fi = tb->fi;
     LaunchButton *btn;
     char *path;
 
     if (fi)
     {
         /* Create a button and add settings for it */
+        fm_file_info_ref(fi);
         btn = launchbutton_for_file_info(tb, fi);
         path = fm_path_to_str(fm_file_info_get_path(fi));
         /* g_debug("*** path '%s'",path); */
@@ -3384,178 +1998,39 @@ static void  on_menuitem_lock_tbp_clicked(GtkWidget * widget, LaunchTaskBarPlugi
 
 static void  on_menuitem_unlock_tbp_clicked(GtkWidget * widget, LaunchTaskBarPlugin * tb)
 {
-    LaunchTaskBarPlugin *ltbp = (LaunchTaskBarPlugin *)tb;
-    FmFileInfo *fi = f_find_menu_launchbutton_recursive(tb->menutask->exec_bin);
-    LaunchButton *btn = launchbar_exec_bin_exists(ltbp, fi);
+    FmFileInfo *fi = tb->fi;
+    LaunchButton *btn = launchbar_exec_bin_exists(tb, fi);
     if(btn != NULL)
     {
-        launchbar_remove_button(ltbp, btn);
+        launchbar_remove_button(tb, btn);
         lxpanel_config_save(tb->panel);
     }
-    if (fi)
-        fm_file_info_unref(fi);
 }
 
 static void  on_menuitem_new_instance_clicked(GtkWidget * widget, LaunchTaskBarPlugin * tb)
 {
-    FmFileInfo *fi = f_find_menu_launchbutton_recursive(tb->menutask->exec_bin);
+    FmFileInfo *fi = tb->fi;
 
     if (fi)
     {
         lxpanel_launch_path(tb->panel, fm_file_info_get_path(fi));
-        fm_file_info_unref(fi);
     }
 }
 #endif
 
-/* Make right-click menu for task buttons.
- * This depends on number of desktops and edge. */
-static void taskbar_make_menu(LaunchTaskBarPlugin * tb)
-{
-    void (*_m_add)(GtkMenuShell *self, GtkWidget* child);
-
-    /* Deallocate old menu if present. */
-    if (tb->menu != NULL)
-        gtk_widget_destroy(tb->menu);
-    /* The pointer to menu became invalid, reset it now. */
-    tb->workspace_menu0 = NULL;
-
-    /* Allocate menu. */
-    GtkWidget * menu = gtk_menu_new();
-
-    /* Add Raise menu item. */
-    GtkWidget *mi = gtk_menu_item_new_with_mnemonic(_("_Raise"));
-    gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
-    g_signal_connect(G_OBJECT(mi), "activate", (GCallback) menu_raise_window, tb);
-
-    /* Add Restore menu item. */
-    mi = gtk_menu_item_new_with_mnemonic(_("R_estore"));
-    gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
-    g_signal_connect(G_OBJECT(mi), "activate", (GCallback) menu_restore_window, tb);
-
-    /* Add Maximize menu item. */
-    mi = gtk_menu_item_new_with_mnemonic(_("Ma_ximize"));
-    gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
-    g_signal_connect(G_OBJECT(mi), "activate", (GCallback) menu_maximize_window, tb);
-
-    /* Add Iconify menu item. */
-    mi = gtk_menu_item_new_with_mnemonic(_("Ico_nify"));
-    gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
-    g_signal_connect(G_OBJECT(mi), "activate", (GCallback) menu_iconify_window, tb);
-
-    /* FIXME: if WM is Openbox then add "Window special parameters" submenu */
-
-    /* If multiple desktops are supported, add menu items to select them. */
-    if (tb->number_of_desktops > 1)
-    {
-        char label[128];
-
-        /* Allocate submenu. */
-        GtkWidget * workspace_menu = gtk_menu_new();
-
-        /* Loop over all desktops. */
-        int i;
-        for (i = 1; i <= tb->number_of_desktops; i++)
-        {
-            /* For the first 9 desktops, allow the desktop number as a keyboard shortcut. */
-            if (i <= 9)
-            {
-                g_snprintf(label, sizeof(label), _("Workspace _%d"), i);
-                mi = gtk_menu_item_new_with_mnemonic(label);
-            }
-            else
-            {
-                g_snprintf(label, sizeof(label), _("Workspace %d"), i);
-                mi = gtk_menu_item_new_with_label(label);
-            }
-
-            /* Set the desktop number as a property on the menu item. */
-            g_object_set_data(G_OBJECT(mi), "num", GINT_TO_POINTER(i - 1));
-            g_signal_connect(mi, "activate", G_CALLBACK(menu_move_to_workspace), tb);
-            gtk_menu_shell_append(GTK_MENU_SHELL(workspace_menu), mi);
-            if (G_UNLIKELY(tb->workspace_menu0 == NULL))
-                tb->workspace_menu0 = mi;
-        }
-
-        /* Add a separator. */
-        gtk_menu_shell_append(GTK_MENU_SHELL(workspace_menu), gtk_separator_menu_item_new());
-
-        /* Add "move to all workspaces" item.  This causes the window to be visible no matter what desktop is active. */
-        mi = gtk_menu_item_new_with_mnemonic(_("_All workspaces"));
-        g_object_set_data(G_OBJECT(mi), "num", GINT_TO_POINTER(ALL_WORKSPACES));
-        g_signal_connect(mi, "activate", G_CALLBACK(menu_move_to_workspace), tb);
-        gtk_menu_shell_append(GTK_MENU_SHELL(workspace_menu), mi);
-
-        /* FIXME: add "Current workspace" item, active if not on a current */
-
-        /* Add Move to Workspace menu item as a submenu. */
-        mi = gtk_menu_item_new_with_mnemonic(_("_Move to Workspace"));
-        gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
-        gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), workspace_menu);
-    }
-
-    /* Add Close menu item.  By popular demand, we place this menu item closest to the cursor. */
-    mi = gtk_menu_item_new_with_mnemonic (_("_Close Window"));
-#ifndef DISABLE_MENU
-    tb->p_menuitem_lock_tbp = gtk_menu_item_new_with_mnemonic(_("A_dd to Launcher"));
-    tb->p_menuitem_unlock_tbp = gtk_menu_item_new_with_mnemonic(_("Rem_ove from Launcher"));
-    tb->p_menuitem_new_instance = gtk_menu_item_new_with_mnemonic(_("_New Instance"));
-    tb->p_menuitem_separator = gtk_separator_menu_item_new();
-#endif
-
-    if (panel_is_at_bottom(tb->panel))
-        _m_add = gtk_menu_shell_append;
-    else
-        _m_add = gtk_menu_shell_prepend;
-
-#ifndef DISABLE_MENU
-    _m_add(GTK_MENU_SHELL(menu), tb->p_menuitem_separator);
-    _m_add(GTK_MENU_SHELL(menu), tb->p_menuitem_lock_tbp);
-    _m_add(GTK_MENU_SHELL(menu), tb->p_menuitem_unlock_tbp);
-    _m_add(GTK_MENU_SHELL(menu), tb->p_menuitem_new_instance);
-#endif
-    _m_add(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
-    _m_add(GTK_MENU_SHELL(menu), mi);
-
-    g_signal_connect(G_OBJECT(mi), "activate", (GCallback)menu_close_window, tb);
-#ifndef DISABLE_MENU
-    g_signal_connect(G_OBJECT(tb->p_menuitem_lock_tbp), "activate", (GCallback)on_menuitem_lock_tbp_clicked, tb);
-    g_signal_connect(G_OBJECT(tb->p_menuitem_unlock_tbp), "activate", (GCallback)on_menuitem_unlock_tbp_clicked, tb);
-    g_signal_connect(G_OBJECT(tb->p_menuitem_new_instance), "activate", (GCallback)on_menuitem_new_instance_clicked, tb);
-#endif
-    gtk_widget_show_all(menu);
-    tb->menu = menu;
-}
-
 /* Handler for "window-manager-changed" event. */
 static void taskbar_window_manager_changed(GdkScreen * screen, LaunchTaskBarPlugin * tb)
 {
     /* Force re-evaluation of use_net_active. */
-    tb->net_active_checked = FALSE;
+    GdkAtom net_active_atom = gdk_x11_xatom_to_atom(a_NET_ACTIVE_WINDOW);
+    tb->flags.use_net_active = gdk_x11_screen_supports_net_wm_hint(tb->screen, net_active_atom);
+    taskbar_redraw(tb);
 }
 
 /* Callback from configuration dialog mechanism to apply the configuration. */
 static void taskbar_apply_configuration(LaunchTaskBarPlugin *ltbp)
 {
-    Task * tk;
-
-    /* Update style on taskbar. */
-    taskbar_update_style(ltbp);
-
-    /* Update styles on each button. */
-    for (tk = ltbp->p_task_list; tk != NULL; tk = tk->p_task_flink_xwid)
-    {
-        /* If same_monitor_only wasn't set before, the monitor information
-           wasn't tracked, so update it now. */
-        if (ltbp->same_monitor_only)
-            tk->monitor = get_window_monitor(tk->win);
-
-        task_update_style(tk, ltbp);
-    }
-
-    /* Refetch the client list and redraw. */
-    recompute_group_visibility_on_current_desktop(ltbp);
-    taskbar_net_client_list(NULL, ltbp);
+    taskbar_redraw(ltbp);
 }
 
 static GtkWidget *launchbar_constructor(LXPanel *panel, config_setting_t *settings)
diff --git a/plugins/task-button.c b/plugins/task-button.c
new file mode 100644 (file)
index 0000000..9e1f8ab
--- /dev/null
@@ -0,0 +1,1895 @@
+/*
+ * Copyright (C) 2006-2009 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
+ *               2006-2008 Jim Huang <jserv.tw@gmail.com>
+ *               2008 Fred Chien <fred@lxde.org>
+ *               2009 Jürgen Hötzel <juergen@archlinux.org>
+ *               2009 Ying-Chun Liu (PaulLiu) <grandpaul@gmail.com>
+ *               2009-2010 Marty Jack <martyj19@comcast.net>
+ *               2010 Julien Lavergne <julien.lavergne@gmail.com>
+ *               2011-2014 Henry Gebhardt <hsggebhardt@gmail.com>
+ *               2012 Piotr Sipika <Piotr.Sipika@gmail.com>
+ *               2013 Vincenzo di Cicco <enzodicicco@gmail.com>
+ *               2013 Rouslan <rouslan-k@users.sourceforge.net>
+ *               2014-2016 Andriy Grytsenko <andrej@rep.kiev.ua>
+ *               2014 Andy Balaam <axis3x3@users.sf.net>
+ *               2015 Balló György <ballogyor@gmail.com>
+ *               2015 Rafał Mużyło <galtgendo@gmail.com>
+ *
+ * This file is a part of LXPanel project.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "task-button.h"
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
+#include <gdk/gdk.h>
+#include <glib/gi18n.h>
+
+#include "plugin.h"
+#include "misc.h"
+#include "icon.xpm"
+#include "gtk-compat.h"
+
+#define ALL_WORKSPACES       -1
+#define ICON_BUTTON_TRIM      4      /* Amount needed to have button remain on panel */
+
+/* -----------------------------------------------------------------------------
+ * Class data
+ */
+
+/* individual task data */
+typedef struct
+{
+    Window win;                             /* X window ID */
+    gint desktop;                           /* Desktop that contains task, needed to switch to it on Raise */
+    gint monitor;                           /* Monitor that the window is on or closest to */
+    char * name;                            /* Taskbar label when normal, from WM_NAME or NET_WM_NAME */
+    GdkPixbuf * icon;           /* the taskbar icon */
+    GtkWidget * menu_item;      /* if menu_list exists then it's an item in it */
+    Atom name_source;                       /* Atom that is the source of taskbar label */
+    Atom image_source;                      /* Atom that is the source of taskbar icon */
+    unsigned int visible :1;    /* TRUE if window is shown in taskbar */
+    unsigned int focused                :1; /* True if window has focus */
+    unsigned int iconified              :1; /* True if window is iconified, from WM_STATE */
+    unsigned int urgency                :1; /* True if window has an urgency hint, from WM_HINTS */
+} TaskDetails;
+
+/* widget data */
+struct _TaskButton
+{
+    GtkToggleButton parent;
+    char * res_class;                      /* Class name */
+    GtkWidget * image;                      /* Icon for task, child of button */
+    GtkWidget * label;                      /* Label for task, child of button */
+    LXPanel * panel;            /* points to panel (grandparent widget) */
+    TaskDetails * last_focused; /* points to details of last focused task */
+    GtkMenu * menu_list;        /* list of tasks on menu activation */
+    Window menu_target;         /* window which activated menu */
+    guint n_visible;            /* number of windows that are shown */
+    guint idle_loader;          /* id of icons loader */
+    GList * details;            /* details for each window, TaskDetails */
+    gint desktop;               /* Current desktop of the button */
+    gint n_desktops;            /* total number of desktops */
+    gint monitor;               /* current monitor for the panel */
+    guint icon_size;            /* Current value from last update */
+    TaskShowFlags flags;        /* flags to show */
+    unsigned int set_bold :1;   /* flat buttons only: TRUE if set bold */
+    unsigned int visible :1;    /* TRUE if any window shown on current desktop */
+    unsigned int same_name :1;  /* TRUE if all visible windows have the same name */
+    unsigned int entered_state          :1; /* True if cursor is inside taskbar button */
+};
+
+enum {
+    MENU_BUILT,
+    MENU_TARGET_SET,
+    N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+
+static void task_update_icon(TaskButton *task, TaskDetails *details, Atom source);
+
+/* -----------------------------------------------------------------------------
+ * Internal functions
+ */
+
+/* Determine which monitor a given window is associated with */
+static gint get_window_monitor(Window win)
+{
+    GdkDisplay *display;
+    GdkWindow *gwin;
+    gint m;
+
+    display = gdk_display_get_default();
+    g_assert(display);
+    gwin = gdk_x11_window_foreign_new_for_display(display,win);
+    g_assert(gwin);
+    m = gdk_screen_get_monitor_at_window(gdk_window_get_screen(gwin),gwin);
+    g_object_unref(gwin);
+    return m;
+}
+
+/* Determine if the "urgency" hint is set on a window. */
+static gboolean task_has_urgency(Window win)
+{
+    gboolean result = FALSE;
+    XWMHints * hints = (XWMHints *) get_xaproperty(win, XA_WM_HINTS, XA_WM_HINTS, 0);
+    if (hints != NULL)
+    {
+        if (hints->flags & XUrgencyHint)
+            result = TRUE;
+        XFree(hints);
+    }
+    return result;
+}
+
+/* Returns TRUE if change name affects button name */
+static gboolean task_set_names(TaskDetails *tk, Atom source)
+{
+    char * name = NULL;
+
+    /* Try _NET_WM_VISIBLE_NAME, which supports UTF-8.
+     * If it is set, the window manager is displaying it as the window title. */
+    if ((source == None) || (source == a_NET_WM_VISIBLE_NAME))
+    {
+        name = get_utf8_property(tk->win,  a_NET_WM_VISIBLE_NAME);
+        if (name != NULL)
+            tk->name_source = a_NET_WM_VISIBLE_NAME;
+    }
+
+    /* Try _NET_WM_NAME, which supports UTF-8, but do not overwrite _NET_WM_VISIBLE_NAME. */
+    if ((name == NULL)
+    && ((source == None) || (source == a_NET_WM_NAME))
+    && ((tk->name_source == None) || (tk->name_source == a_NET_WM_NAME) || (tk->name_source == XA_WM_NAME)))
+    {
+        name = get_utf8_property(tk->win,  a_NET_WM_NAME);
+        if (name != NULL)
+            tk->name_source = a_NET_WM_NAME;
+    }
+
+    /* Try WM_NAME, which supports only ISO-8859-1, but do not overwrite _NET_WM_VISIBLE_NAME or _NET_WM_NAME. */
+    if ((name == NULL)
+    && ((source == None) || (source == XA_WM_NAME))
+    && ((tk->name_source == None) || (tk->name_source == XA_WM_NAME)))
+    {
+        name = get_textproperty(tk->win,  XA_WM_NAME);
+        if (name != NULL)
+            tk->name_source = XA_WM_NAME;
+    }
+
+    /* Set the name into the task context, and also on the tooltip. */
+    if (name != NULL)
+    {
+        if (g_strcmp0(name, tk->name) != 0)
+        {
+            g_free(tk->name);
+            tk->name = name;
+            return TRUE;
+        }
+        g_free(name);
+    }
+    return FALSE;
+}
+
+static gboolean task_is_visible(TaskButton *b, TaskDetails *task)
+{
+    /* Not on same monitor */
+    if (b->flags.same_monitor_only && b->monitor != task->monitor && b->monitor >= 0)
+        return FALSE;
+
+    /* Desktop placement. */
+    return ((task->desktop == ALL_WORKSPACES) ||
+            (task->desktop == b->desktop) ||
+            (b->flags.show_all_desks) ||
+            (b->flags.use_urgency_hint && task->urgency));
+}
+
+static TaskDetails *task_details_for_window(TaskButton *button, Window win)
+{
+    TaskDetails *details = g_slice_new0(TaskDetails);
+    GdkDisplay *display = gdk_display_get_default();
+    /* NOTE
+     * 1. the extended mask is sum of taskbar and pager needs
+     * see bug [ 940441 ] pager loose track of windows
+     *
+     * Do not change event mask to gtk windows spawned by this gtk client
+     * this breaks gtk internals */
+#if GTK_CHECK_VERSION(2, 24, 0)
+    if (!gdk_x11_window_lookup_for_display(display, win))
+#else
+    if (!gdk_window_lookup(win))
+#endif
+        XSelectInput(GDK_DISPLAY_XDISPLAY(display), win,
+                     PropertyChangeMask | StructureNotifyMask);
+
+    /* fetch task details */
+    details->win = win;
+    details->desktop = get_net_wm_desktop(win);
+    details->monitor = get_window_monitor(win);
+    task_set_names(details, None);
+    task_update_icon(button, details, None);
+    details->urgency = task_has_urgency(win);
+    details->iconified = (get_wm_state(win) == IconicState);
+    // FIXME: check if task is focused
+    /* check task visibility by flags */
+    details->visible = task_is_visible(button, details);
+    return details;
+}
+
+static void free_task_details(TaskDetails *details)
+{
+    g_free(details->name);
+    if (details->icon)
+        g_object_unref(details->icon);
+    g_slice_free(TaskDetails, details);
+}
+
+static TaskDetails *task_details_lookup(TaskButton *task, Window win)
+{
+    GList *l;
+
+    for (l = task->details; l; l = l->next)
+        if (((TaskDetails *)l->data)->win == win)
+            return l->data;
+    return NULL;
+}
+
+/* Position-calculation callback for grouped-task and window-management popup menu. */
+static void taskbar_popup_set_position(GtkMenu * menu, gint * px, gint * py, gboolean * push_in, gpointer data)
+{
+    TaskButton * tb = (TaskButton *) data;
+
+    /* Determine the coordinates. */
+    lxpanel_plugin_popup_set_position_helper(tb->panel, data, GTK_WIDGET(menu), px, py);
+    *push_in = TRUE;
+}
+
+/* Handler for "activate" event on Raise item of right-click menu for task buttons. */
+static void menu_raise_window(GtkWidget * widget, TaskButton * tb)
+{
+    TaskDetails *tk = task_details_lookup(tb, tb->menu_target);
+
+    if ((tk->desktop != ALL_WORKSPACES) && (tk->desktop != tb->desktop))
+        Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, tk->desktop, 0, 0, 0, 0);
+    XMapRaised(GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(widget)), tk->win);
+}
+
+/* Handler for "activate" event on Restore item of right-click menu for task buttons. */
+static void menu_restore_window(GtkWidget * widget, TaskButton * tb)
+{
+#if GTK_CHECK_VERSION(2, 24, 0)
+    GdkWindow * win = gdk_x11_window_foreign_new_for_display(gtk_widget_get_display(widget),
+                                                             tb->menu_target);
+#else
+    GdkWindow * win = gdk_window_foreign_new(tb->menu_target);
+#endif
+
+    gdk_window_unmaximize(win);
+    g_object_unref(win);
+}
+
+/* Handler for "activate" event on Maximize item of right-click menu for task buttons. */
+static void menu_maximize_window(GtkWidget * widget, TaskButton * tb)
+{
+#if GTK_CHECK_VERSION(2, 24, 0)
+    GdkWindow * win = gdk_x11_window_foreign_new_for_display(gtk_widget_get_display(widget),
+                                                             tb->menu_target);
+#else
+    GdkWindow * win = gdk_window_foreign_new(tb->menu_target);
+#endif
+
+    gdk_window_maximize(win);
+    g_object_unref(win);
+}
+
+/* Handler for "activate" event on Iconify item of right-click menu for task buttons. */
+static void menu_iconify_window(GtkWidget * widget, TaskButton * tb)
+{
+    Display *xdisplay = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(widget));
+    XIconifyWindow(xdisplay, tb->menu_target, DefaultScreen(xdisplay));
+}
+
+/* Handler for "activate" event on Move to Workspace item of right-click menu for task buttons. */
+static void menu_move_to_workspace(GtkWidget * widget, TaskButton * tb)
+{
+    int num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "num"));
+    Xclimsg(tb->menu_target, a_NET_WM_DESKTOP, num, 0, 0, 0, 0);
+}
+
+/* Handler for "activate" event on Close item of right-click menu for task buttons. */
+static void menu_close_window(GtkWidget * widget, TaskButton * tb)
+{
+    Xclimsgwm(tb->menu_target, a_WM_PROTOCOLS, a_WM_DELETE_WINDOW);
+}
+
+/* Make right-click menu for task buttons.
+ * This depends on number of desktops and edge. */
+static GtkWidget *taskbar_make_menu(TaskButton *tb)
+{
+    /* Function to iterate in direction */
+    void (*_m_add)(GtkMenuShell *self, GtkWidget* child);
+    /* Allocate menu. */
+    GtkWidget *menu = gtk_menu_new();
+    GtkWidget *mi;
+
+    /* Add Raise menu item. */
+    mi = gtk_menu_item_new_with_mnemonic(_("_Raise"));
+    gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
+    g_signal_connect(G_OBJECT(mi), "activate", (GCallback) menu_raise_window, tb);
+
+    /* Add Restore menu item. */
+    mi = gtk_menu_item_new_with_mnemonic(_("R_estore"));
+    gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
+    g_signal_connect(G_OBJECT(mi), "activate", (GCallback) menu_restore_window, tb);
+
+    /* Add Maximize menu item. */
+    mi = gtk_menu_item_new_with_mnemonic(_("Ma_ximize"));
+    gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
+    g_signal_connect(G_OBJECT(mi), "activate", (GCallback) menu_maximize_window, tb);
+
+    /* Add Iconify menu item. */
+    mi = gtk_menu_item_new_with_mnemonic(_("Ico_nify"));
+    gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
+    g_signal_connect(G_OBJECT(mi), "activate", (GCallback) menu_iconify_window, tb);
+
+    /* FIXME: if WM is Openbox then add "Window special parameters" submenu */
+
+    /* If multiple desktops are supported, add menu items to select them. */
+    if (tb->n_desktops > 1)
+    {
+        char label[128];
+        /* Allocate submenu. */
+        GtkWidget * workspace_menu = gtk_menu_new();
+        GtkWidget * workspace_menu0 = NULL;
+
+        /* Loop over all desktops. */
+        int i;
+        for (i = 1; i <= tb->n_desktops; i++)
+        {
+            /* For the first 9 desktops, allow the desktop number as a keyboard shortcut. */
+            if (i <= 9)
+            {
+                g_snprintf(label, sizeof(label), _("Workspace _%d"), i);
+                mi = gtk_menu_item_new_with_mnemonic(label);
+            }
+            else
+            {
+                g_snprintf(label, sizeof(label), _("Workspace %d"), i);
+                mi = gtk_menu_item_new_with_label(label);
+            }
+
+            /* Set the desktop number as a property on the menu item. */
+            g_object_set_data(G_OBJECT(mi), "num", GINT_TO_POINTER(i - 1));
+            g_signal_connect(mi, "activate", G_CALLBACK(menu_move_to_workspace), tb);
+            gtk_menu_shell_append(GTK_MENU_SHELL(workspace_menu), mi);
+            if (G_UNLIKELY(workspace_menu0 == NULL))
+                workspace_menu0 = mi;
+        }
+        g_object_set_data(G_OBJECT(menu), "task-menu-workspace0", workspace_menu0);
+
+        /* Add a separator. */
+        gtk_menu_shell_append(GTK_MENU_SHELL(workspace_menu), gtk_separator_menu_item_new());
+
+        /* Add "move to all workspaces" item.  This causes the window to be visible no matter what desktop is active. */
+        mi = gtk_menu_item_new_with_mnemonic(_("_All workspaces"));
+        g_object_set_data(G_OBJECT(mi), "num", GINT_TO_POINTER(ALL_WORKSPACES));
+        g_signal_connect(mi, "activate", G_CALLBACK(menu_move_to_workspace), tb);
+        gtk_menu_shell_append(GTK_MENU_SHELL(workspace_menu), mi);
+
+        /* FIXME: add "Current workspace" item, active if not on a current */
+
+        /* Add Move to Workspace menu item as a submenu. */
+        mi = gtk_menu_item_new_with_mnemonic(_("_Move to Workspace"));
+        gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
+        gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), workspace_menu);
+    }
+    gtk_widget_show_all(menu);
+
+    /* Extend the menu by callbacks */
+    g_signal_emit(tb, signals[MENU_BUILT], 0, menu);
+
+    /* Add Close menu item.  By popular demand, we place this menu item closest to the cursor. */
+    if (panel_is_at_bottom(tb->panel))
+        _m_add = gtk_menu_shell_append;
+    else
+        _m_add = gtk_menu_shell_prepend;
+
+    mi = gtk_separator_menu_item_new();
+    gtk_widget_show(mi);
+    _m_add(GTK_MENU_SHELL(menu), mi);
+    mi = gtk_menu_item_new_with_mnemonic (_("_Close Window"));
+    gtk_widget_show(mi);
+    _m_add(GTK_MENU_SHELL(menu), mi);
+    g_signal_connect(G_OBJECT(mi), "activate", (GCallback)menu_close_window, tb);
+
+    return menu;
+}
+
+static GtkWidget *get_task_button_menu(TaskButton *tb, TaskDetails *task)
+{
+    GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(tb));
+    GtkWidget *menu = g_object_get_data(G_OBJECT(parent), "task-button-menu");
+    GtkWidget *workspace_menu0; /* item in task menu for workspace 0 */
+
+    if (menu == NULL)
+    {
+        /* this GtkMenu is built on demand on the parent widget */
+        menu = taskbar_make_menu(tb);
+        g_object_set_data_full(G_OBJECT(parent), "task-button-menu",
+                               g_object_ref_sink(menu), g_object_unref);
+    }
+    /* save current choice for our callbacks */
+    tb->menu_target = task->win;
+    /* notify menu makers about current choise */
+    g_signal_emit(tb, signals[MENU_TARGET_SET], 0, (gulong)task->win);
+    /* gray out workspace where window is on */
+    workspace_menu0 = g_object_get_data(G_OBJECT(menu), "task-menu-workspace0");
+    if (workspace_menu0)
+    {
+        GList *items = gtk_container_get_children(GTK_CONTAINER(gtk_widget_get_parent(workspace_menu0)));
+        GList *item = g_list_find(items, workspace_menu0);
+        int i;
+        if (item != NULL) /* else error */
+            for (i = 0; i < tb->n_desktops; i++, item = item->next)
+                gtk_widget_set_sensitive(item->data, i != task->desktop);
+        g_list_free(items);
+    }
+
+    return menu;
+}
+
+/* Do the proper steps to raise a window.
+ * This means removing it from iconified state and bringing it to the front.
+ * We also switch the active desktop and viewport if needed. */
+static void task_raise_window(TaskButton *tb, TaskDetails *tk, guint32 time)
+{
+    GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(tb));
+    Display *xdisplay = GDK_DISPLAY_XDISPLAY(display);
+
+    /* Change desktop if needed. */
+    if ((tk->desktop != ALL_WORKSPACES) && (tk->desktop != tb->desktop))
+        Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, tk->desktop, 0, 0, 0, 0);
+
+    /* Raise the window.  We can use NET_ACTIVE_WINDOW if the window manager supports it.
+     * Otherwise, do it the old way with XMapRaised and XSetInputFocus. */
+    if (tb->flags.use_net_active)
+        Xclimsg(tk->win, a_NET_ACTIVE_WINDOW, 2, time, 0, 0, 0);
+    else
+    {
+#if GTK_CHECK_VERSION(2, 24, 0)
+        GdkWindow * gdkwindow = gdk_x11_window_lookup_for_display(display, tk->win);
+#else
+        GdkWindow * gdkwindow = gdk_xid_table_lookup(tk->win);
+#endif
+        if (gdkwindow != NULL)
+            gdk_window_show(gdkwindow);
+        else
+            XMapRaised(xdisplay, tk->win);
+
+        /* There is a race condition between the X server actually executing the XMapRaised and this code executing XSetInputFocus.
+         * If the window is not viewable, the XSetInputFocus will fail with BadMatch. */
+        XWindowAttributes attr;
+        XGetWindowAttributes(xdisplay, tk->win, &attr);
+        if (attr.map_state == IsViewable)
+            XSetInputFocus(xdisplay, tk->win, RevertToNone, time);
+    }
+
+    /* Change viewport if needed. */
+    XWindowAttributes xwa;
+    XGetWindowAttributes(xdisplay, tk->win, &xwa);
+    Xclimsg(tk->win, a_NET_DESKTOP_VIEWPORT, xwa.x, xwa.y, 0, 0, 0);
+}
+
+/* called when list of windows menu emits signal "selection-done" */
+static void on_menu_list_selection_done(GtkMenuShell *menushell, TaskButton *tb)
+{
+    g_object_remove_weak_pointer(G_OBJECT(menushell), (void **)&tb->menu_list);
+    tb->menu_list = NULL;
+}
+
+static gboolean task_button_window_do_release_event(GtkWidget *tb, TaskDetails *task, GdkEventButton *event)
+{
+    if (event->button == 1)
+    {
+        Display *xdisplay = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(tb));
+        /* Left button.
+         * If the task is iconified, raise it.
+         * If the task is not iconified and has focus, iconify it.
+         * If the task is not iconified and does not have focus, raise it. */
+        if (task->iconified)
+            task_raise_window(PANEL_TASK_BUTTON(tb), task, event->time);
+        else if (task->focused)
+            XIconifyWindow(xdisplay, task->win, DefaultScreen(xdisplay));
+        else
+            task_raise_window(PANEL_TASK_BUTTON(tb), task, event->time);
+    }
+    else if (event->button == 2)
+    {
+        /* Middle button.  Toggle the shaded state of the window. */
+        Xclimsg(task->win, a_NET_WM_STATE,
+                2,      /* a_NET_WM_STATE_TOGGLE */
+                a_NET_WM_STATE_SHADED,
+                0, 0, 0);
+    }
+    return TRUE;
+}
+
+/* Handler for "button-press-event" event from grouped-task popup menu item. */
+static gboolean taskbar_popup_activate_event(GtkWidget *widget, GdkEventButton *event,
+                                             TaskButton *tk)
+{
+    GtkWidget *menu;
+    GList *l;
+
+    /* find details of this menu item and set tk->menu_target */
+    for (l = tk->details; l; l = l->next)
+        if (((TaskDetails *)l->data)->menu_item == widget)
+            break;
+    if (l == NULL) /* it's impossible really */
+        return FALSE;
+    /* if button 1 or 2 pressed then handle it the same as button-release
+       event on a single task button */
+    if (event->button == 1 || event->button == 2)
+        return task_button_window_do_release_event(GTK_WIDGET(tk), l->data, event);
+    else if (event->button != 3) /* don't process other buttons */
+        return FALSE;
+    /* process event the same way as for single task button */
+    menu = get_task_button_menu(tk, l->data);
+    /* attach and show menu */
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(widget), menu);
+    gtk_widget_show_all(menu);
+    /* let menu continue with submenu */
+    return FALSE;
+}
+
+static void menu_task_selected(GtkMenuItem *item, TaskButton *tb)
+{
+    GList *l;
+    TaskDetails *task;
+
+    for (l = tb->details; l; l = l->next)
+        if ((task = l->data)->menu_item == (GtkWidget *)item)
+            break;
+    if (l == NULL) /* it's impossible really */
+        return;
+    tb->menu_target = task->win;
+    // FIXME: auto attach menu?
+}
+
+static void menu_task_deselected(GtkMenuItem *item, TaskButton *tb)
+{
+    GList *l;
+    TaskDetails *task;
+
+    for (l = tb->details; l; l = l->next)
+        if ((task = l->data)->menu_item == (GtkWidget *)item)
+            break;
+    if (l == NULL) /* it's impossible really */
+        return;
+    /* remove submenu from item */
+    gtk_menu_item_set_submenu(item, NULL);
+}
+
+/* Handler for "activate" event from "close all windows" menu item */
+static void taskbar_close_all_windows(GtkWidget * widget, TaskButton *tb)
+{
+    GList *l;
+
+    for (l = tb->details; l; l = l->next)
+    {
+        TaskDetails *tk = l->data;
+
+        if (tk->visible)
+        {
+            Xclimsgwm(tk->win, a_WM_PROTOCOLS, a_WM_DELETE_WINDOW);
+        }
+    }
+}
+
+static void assemble_gui(TaskButton *self)
+{
+    /* Create a box to contain the application icon and window title. */
+    GtkWidget * container = gtk_hbox_new(FALSE, 1);
+    gtk_container_set_border_width(GTK_CONTAINER(container), 0);
+
+    /* Add the image to contain the application icon to the box. */
+    gtk_misc_set_padding(GTK_MISC(self->image), 0, 0);
+    gtk_box_pack_start(GTK_BOX(container), self->image, FALSE, FALSE, 0);
+
+    /* Add the label to contain the window title to the box. */
+    gtk_misc_set_alignment(GTK_MISC(self->label), 0.0, 0.5);
+    gtk_label_set_ellipsize(GTK_LABEL(self->label), PANGO_ELLIPSIZE_END);
+    gtk_box_pack_start(GTK_BOX(container), self->label, TRUE, TRUE, 0);
+
+    /* Add the box to the button. */
+    gtk_container_add(GTK_CONTAINER(self), container);
+    gtk_widget_show(container);
+    gtk_widget_show(self->image);
+    gtk_widget_set_visible(self->label, !self->flags.icons_only);
+}
+
+static void map_xwindow_animation(GtkWidget *widget, Window win, GtkAllocation *alloc)
+{
+    /* Tell WM to set iconifying animation the window into the task button */
+    if (gtk_widget_get_realized(widget))
+    {
+        int x, y;
+        gulong data[4];
+
+        /* Get the coordinates of the button. */
+        gdk_window_get_origin(gtk_button_get_event_window(GTK_BUTTON(widget)), &x, &y);
+
+        /* Send a NET_WM_ICON_GEOMETRY property change on the window. */
+        data[0] = x;
+        data[1] = y;
+        data[2] = alloc->width;
+        data[3] = alloc->height;
+        XChangeProperty(GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(widget)), win,
+                        gdk_x11_get_xatom_by_name("_NET_WM_ICON_GEOMETRY"),
+                        XA_CARDINAL, 32, PropModeReplace, (guchar *) &data, 4);
+    }
+}
+
+/* Get a pixbuf from a pixmap.
+ * Originally from libwnck, Copyright (C) 2001 Havoc Pennington. */
+#if !GTK_CHECK_VERSION(3, 0, 0)
+static GdkPixbuf * _wnck_gdk_pixbuf_get_from_pixmap(GdkScreen *screen, Pixmap xpixmap, int width, int height)
+{
+    /* Get the drawable. */
+#if GTK_CHECK_VERSION(2, 24, 0)
+    GdkDrawable * drawable = gdk_x11_window_lookup_for_display(gdk_display_get_default(), xpixmap);
+#else
+    GdkDrawable * drawable = gdk_xid_table_lookup(xpixmap);
+#endif
+    if (drawable != NULL)
+        g_object_ref(G_OBJECT(drawable));
+    else
+        drawable = gdk_pixmap_foreign_new(xpixmap);
+
+    GdkColormap * colormap = NULL;
+    GdkPixbuf * retval = NULL;
+    if (drawable != NULL)
+    {
+        /* Get the colormap.
+         * If the drawable has no colormap, use no colormap or the system colormap as recommended in the documentation of gdk_drawable_get_colormap. */
+        colormap = gdk_drawable_get_colormap(drawable);
+        gint depth = gdk_drawable_get_depth(drawable);
+        if (colormap != NULL)
+            g_object_ref(G_OBJECT(colormap));
+        else if (depth == 1)
+            colormap = NULL;
+        else
+        {
+            colormap = gdk_screen_get_system_colormap(screen);
+            g_object_ref(G_OBJECT(colormap));
+        }
+
+        /* Be sure we aren't going to fail due to visual mismatch. */
+        if ((colormap != NULL) && (gdk_visual_get_depth(gdk_colormap_get_visual(colormap)) != depth))
+        {
+            g_object_unref(G_OBJECT(colormap));
+            colormap = NULL;
+        }
+
+        /* Do the major work. */
+        retval = gdk_pixbuf_get_from_drawable(NULL, drawable, colormap, 0, 0, 0, 0, width, height);
+    }
+
+    /* Clean up and return. */
+    if (colormap != NULL)
+        g_object_unref(G_OBJECT(colormap));
+    if (drawable != NULL)
+        g_object_unref(G_OBJECT(drawable));
+    return retval;
+}
+#else
+static GdkPixbuf * _wnck_gdk_pixbuf_get_from_pixmap(GdkScreen *screen, Pixmap xpixmap, int width, int height)
+{
+  cairo_surface_t *surface;
+  GdkPixbuf *pixbuf;
+  Display *xdisplay;
+  Window root_return;
+  XWindowAttributes attrs;
+
+  surface = NULL;
+  xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+
+
+  gdk_error_trap_push();
+
+  if (!XGetWindowAttributes (xdisplay, root_return, &attrs))
+    goto TRAP_POP;
+
+  if (attrs.depth == 1)
+    {
+      surface = cairo_xlib_surface_create_for_bitmap (xdisplay,
+                                                      xpixmap,
+                                                      attrs.screen,
+                                                      width,
+                                                      height);
+    }
+  else
+    {
+      surface = cairo_xlib_surface_create (xdisplay,
+                                           xpixmap,
+                                           attrs.visual,
+                                           width, height);
+    }
+
+  pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, width, height);
+  cairo_surface_destroy (surface);
+
+TRAP_POP:
+  gdk_flush();
+  gdk_error_trap_pop();
+
+  return pixbuf;
+}
+#endif
+
+/* Apply a mask to a pixbuf.
+ * Originally from libwnck, Copyright (C) 2001 Havoc Pennington. */
+static GdkPixbuf * apply_mask(GdkPixbuf * pixbuf, GdkPixbuf * mask)
+{
+    /* Initialize. */
+    int w = MIN(gdk_pixbuf_get_width(mask), gdk_pixbuf_get_width(pixbuf));
+    int h = MIN(gdk_pixbuf_get_height(mask), gdk_pixbuf_get_height(pixbuf));
+    GdkPixbuf * with_alpha = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
+    guchar * dst = gdk_pixbuf_get_pixels(with_alpha);
+    guchar * src = gdk_pixbuf_get_pixels(mask);
+    int dst_stride = gdk_pixbuf_get_rowstride(with_alpha);
+    int src_stride = gdk_pixbuf_get_rowstride(mask);
+
+    /* Loop to do the work. */
+    int i;
+    for (i = 0; i < h; i += 1)
+    {
+        int j;
+        for (j = 0; j < w; j += 1)
+        {
+            guchar * s = src + i * src_stride + j * 3;
+            guchar * d = dst + i * dst_stride + j * 4;
+
+            /* s[0] == s[1] == s[2], they are 255 if the bit was set, 0 otherwise. */
+            d[3] = ((s[0] == 0) ? 0 : 255); /* 0 = transparent, 255 = opaque */
+        }
+    }
+
+    return with_alpha;
+}
+
+/* Get an icon from the window manager for a task, and scale it to a specified size. */
+static GdkPixbuf * get_wm_icon(Window task_win, guint required_width,
+                               guint required_height, Atom source,
+                               Atom * current_source, TaskButton * tb)
+{
+    /* The result. */
+    GdkPixbuf * pixmap = NULL;
+    Atom possible_source = None;
+    int result = -1;
+    Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+    GdkScreen *screen = gtk_widget_get_screen(GTK_WIDGET(tb));
+
+    if ((source == None) || (source == a_NET_WM_ICON))
+    {
+        /* Important Notes:
+         * According to freedesktop.org document:
+         * http://standards.freedesktop.org/wm-spec/wm-spec-1.4.html#id2552223
+         * _NET_WM_ICON contains an array of 32-bit packed CARDINAL ARGB.
+         * However, this is incorrect. Actually it's an array of long integers.
+         * Toolkits like gtk+ use unsigned long here to store icons.
+         * Besides, according to manpage of XGetWindowProperty, when returned format,
+         * is 32, the property data will be stored as an array of longs
+         * (which in a 64-bit application will be 64-bit values that are
+         * padded in the upper 4 bytes).
+         */
+
+        /* Get the window property _NET_WM_ICON, if possible. */
+        Atom type = None;
+        int format;
+        gulong nitems;
+        gulong bytes_after;
+        gulong * data = NULL;
+        result = XGetWindowProperty(
+            xdisplay,
+            task_win,
+            a_NET_WM_ICON,
+            0, G_MAXLONG,
+            False, XA_CARDINAL,
+            &type, &format, &nitems, &bytes_after, (void *) &data);
+
+        /* Inspect the result to see if it is usable.  If not, and we got data, free it. */
+        if ((type != XA_CARDINAL) || (nitems <= 0))
+        {
+            if (data != NULL)
+                XFree(data);
+            result = -1;
+        }
+
+        /* If the result is usable, extract the icon from it. */
+        if (result == Success)
+        {
+            /* Get the largest icon available, unless there is one that is the desired size. */
+            /* FIXME: should we try to find an icon whose size is closest to
+             * required_width and required_height to reduce unnecessary resizing? */
+            gulong * pdata = data;
+            gulong * pdata_end = data + nitems;
+            gulong * max_icon = NULL;
+            gulong max_w = 0;
+            gulong max_h = 0;
+            while ((pdata + 2) < pdata_end)
+            {
+                /* Extract the width and height. */
+                guint w = pdata[0];
+                guint h = pdata[1];
+                gulong size = w * h;
+                pdata += 2;
+
+                /* Bounds check the icon. Also check for invalid width and height,
+                   see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=801319 */
+                if (size == 0 || w > 1024 || h > 1024 || pdata + size > pdata_end)
+                    break;
+
+                /* Rare special case: the desired size is the same as icon size. */
+                if ((required_width == w) && (required_height == h))
+                {
+                    max_icon = pdata;
+                    max_w = w;
+                    max_h = h;
+                    break;
+                }
+
+                /* If the icon is the largest so far, capture it. */
+                if ((w > max_w) && (h > max_h))
+                {
+                    max_icon = pdata;
+                    max_w = w;
+                    max_h = h;
+                }
+                pdata += size;
+            }
+
+            /* If an icon was extracted, convert it to a pixbuf.
+             * Its size is max_w and max_h. */
+            if (max_icon != NULL)
+            {
+                /* Allocate enough space for the pixel data. */
+                gulong len = max_w * max_h;
+                guchar * pixdata = g_new(guchar, len * 4);
+
+                /* Loop to convert the pixel data. */
+                guchar * p = pixdata;
+                gulong i;
+                for (i = 0; i < len; p += 4, i += 1)
+                {
+                    guint argb = max_icon[i];
+                    guint rgba = (argb << 8) | (argb >> 24);
+                    p[0] = rgba >> 24;
+                    p[1] = (rgba >> 16) & 0xff;
+                    p[2] = (rgba >> 8) & 0xff;
+                    p[3] = rgba & 0xff;
+                }
+
+                /* Initialize a pixmap with the pixel data. */
+                pixmap = gdk_pixbuf_new_from_data(
+                    pixdata,
+                    GDK_COLORSPACE_RGB,
+                    TRUE, 8,    /* has_alpha, bits_per_sample */
+                    max_w, max_h, max_w * 4,
+                    (GdkPixbufDestroyNotify) g_free,
+                    NULL);
+                possible_source = a_NET_WM_ICON;
+            }
+        else
+            result = -1;
+
+            /* Free the X property data. */
+            XFree(data);
+        }
+    }
+
+    /* No icon available from _NET_WM_ICON.  Next try WM_HINTS, but do not overwrite _NET_WM_ICON. */
+    if ((result != Success) && (*current_source != a_NET_WM_ICON)
+    && ((source == None) || (source != a_NET_WM_ICON)))
+    {
+        XWMHints * hints = XGetWMHints(xdisplay, task_win);
+        result = (hints != NULL) ? Success : -1;
+        Pixmap xpixmap = None;
+        Pixmap xmask = None;
+
+        if (result == Success)
+        {
+            /* WM_HINTS is available.  Extract the X pixmap and mask. */
+            if ((hints->flags & IconPixmapHint))
+                xpixmap = hints->icon_pixmap;
+            if ((hints->flags & IconMaskHint))
+                xmask = hints->icon_mask;
+            XFree(hints);
+            if (xpixmap != None)
+            {
+                result = Success;
+                possible_source = XA_WM_HINTS;
+            }
+            else
+                result = -1;
+        }
+
+        if (result != Success)
+        {
+            /* No icon available from _NET_WM_ICON or WM_HINTS.  Next try KWM_WIN_ICON. */
+            Atom type = None;
+            int format;
+            gulong nitems;
+            gulong bytes_after;
+            Pixmap *icons = NULL;
+            Atom kwin_win_icon_atom = gdk_x11_get_xatom_by_name("KWM_WIN_ICON");
+            result = XGetWindowProperty(
+                xdisplay,
+                task_win,
+                kwin_win_icon_atom,
+                0, G_MAXLONG,
+                False, kwin_win_icon_atom,
+                &type, &format, &nitems, &bytes_after, (void *) &icons);
+
+            /* Inspect the result to see if it is usable.  If not, and we got data, free it. */
+            if (type != kwin_win_icon_atom)
+            {
+                if (icons != NULL)
+                    XFree(icons);
+                result = -1;
+            }
+
+            /* If the result is usable, extract the X pixmap and mask from it. */
+            if (result == Success)
+            {
+                xpixmap = icons[0];
+                xmask = icons[1];
+                if (xpixmap != None)
+                {
+                    result = Success;
+                    possible_source = kwin_win_icon_atom;
+                }
+                else
+                    result = -1;
+            }
+        }
+
+        /* If we have an X pixmap, get its geometry.*/
+        unsigned int w, h;
+        if (result == Success)
+        {
+            Window unused_win;
+            int unused;
+            unsigned int unused_2;
+            result = XGetGeometry(
+                xdisplay, xpixmap,
+                &unused_win, &unused, &unused, &w, &h, &unused_2, &unused_2) ? Success : -1;
+        }
+
+        /* If we have an X pixmap and its geometry, convert it to a GDK pixmap. */
+        if (result == Success)
+        {
+            pixmap = _wnck_gdk_pixbuf_get_from_pixmap(screen, xpixmap, w, h);
+            result = ((pixmap != NULL) ? Success : -1);
+        }
+
+        /* If we have success, see if the result needs to be masked.
+         * Failures here are implemented as nonfatal. */
+        if ((result == Success) && (xmask != None))
+        {
+            Window unused_win;
+            int unused;
+            unsigned int unused_2;
+            if (XGetGeometry(
+                xdisplay, xmask,
+                &unused_win, &unused, &unused, &w, &h, &unused_2, &unused_2))
+            {
+                /* Convert the X mask to a GDK pixmap. */
+                GdkPixbuf * mask = _wnck_gdk_pixbuf_get_from_pixmap(screen, xmask, w, h);
+                if (mask != NULL)
+                {
+                    /* Apply the mask. */
+                    GdkPixbuf * masked_pixmap = apply_mask(pixmap, mask);
+                    g_object_unref(G_OBJECT(pixmap));
+                    g_object_unref(G_OBJECT(mask));
+                    pixmap = masked_pixmap;
+                }
+            }
+        }
+    }
+
+    /* If we got a pixmap, scale it and return it. */
+    if (pixmap == NULL)
+        return NULL;
+    else
+    {
+        GdkPixbuf * ret;
+
+        *current_source = possible_source;
+        if (tb->flags.disable_taskbar_upscale)
+        {
+            guint w = gdk_pixbuf_get_width (pixmap);
+            guint h = gdk_pixbuf_get_height (pixmap);
+            if (w <= required_width || h <= required_height)
+                return pixmap;
+        }
+        ret = gdk_pixbuf_scale_simple(pixmap, required_width, required_height,
+                                      GDK_INTERP_BILINEAR);
+        g_object_unref(pixmap);
+        return ret;
+    }
+}
+
+/* Update the icon of a task. */
+static void _task_update_icon(TaskButton *task, TaskDetails *details, Atom source)
+{
+    GdkPixbuf *pixbuf = NULL;
+
+    if (source == a_NET_ACTIVE_WINDOW && details != NULL)
+        pixbuf = details->icon; /* use cached icon */
+
+    /* Get the icon from the window's hints. */
+    if (details != NULL && pixbuf == NULL)
+    {
+        pixbuf = get_wm_icon(details->win,
+                             MAX(0, (int)task->icon_size - ICON_BUTTON_TRIM),
+                             MAX(0, (int)task->icon_size - ICON_BUTTON_TRIM),
+                             source, &details->image_source, task);
+        if (pixbuf)
+        {
+            /* replace old cached image */
+            if (details->icon)
+                g_object_unref(details->icon);
+            details->icon = g_object_ref_sink(pixbuf);
+        }
+        else
+            /* use cached icon if available */
+            pixbuf = details->icon;
+    }
+
+    /* If that fails, and we have no other icon yet, return the fallback icon. */
+    if ((pixbuf == NULL)
+        && ((source == None) || (details->image_source == None)))
+    {
+        GObject *parent = G_OBJECT(gtk_widget_get_parent(GTK_WIDGET(task)));
+
+        /* Establish the fallback task icon.  This is used when no other icon is available. */
+        pixbuf = g_object_get_data(parent, "task-fallback-pixbuf");
+        if (pixbuf == NULL)
+        {
+            pixbuf = gdk_pixbuf_new_from_xpm_data((const char **) icon_xpm);
+            if (pixbuf != NULL)
+                g_object_set_data_full(parent, "task-fallback-pixbuf",
+                                       g_object_ref_sink(pixbuf), g_object_unref);
+        }
+    }
+
+    if (pixbuf != NULL)
+        gtk_image_set_from_pixbuf(GTK_IMAGE(task->image), pixbuf);
+}
+
+static gboolean task_update_icon_idle(gpointer user_data)
+{
+    TaskButton *task;
+    GList *l;
+    TaskDetails *details;
+
+    if (g_source_is_destroyed(g_main_current_source()))
+        return FALSE;
+    task = user_data;
+    for (l = task->details; l; l = l->next)
+    {
+        details = l->data;
+        if (details->icon == NULL)
+            _task_update_icon(task, details, None);
+    }
+    return FALSE;
+}
+
+static void task_update_icon(TaskButton *task, TaskDetails *details, Atom source)
+{
+    if (source != None || (details && details->icon))
+        _task_update_icon(task, details, source);
+    else if (task->idle_loader == 0)
+        task->idle_loader = gdk_threads_add_timeout_full(G_PRIORITY_LOW, 20,
+                                                         task_update_icon_idle,
+                                                         task, NULL);
+}
+
+/* Draw the label and tooltip on a taskbar button. */
+static void task_draw_label(TaskButton *tb, gboolean bold_style, gboolean force)
+{
+    GString *str;
+    gboolean old_bold = !!tb->set_bold;
+
+    if (!force && old_bold == bold_style) /* nothing to do */
+        return;
+    if (tb->flags.icons_only) /* no label to show */
+        return;
+
+    tb->set_bold = bold_style;
+    str = g_string_sized_new(32);
+    if (!tb->visible)
+        g_string_append_c(str, '[');
+    if (tb->n_visible > 1)
+        g_string_append_printf(str, "(%d) ", tb->n_visible);
+    if (!tb->same_name || !tb->last_focused || !tb->last_focused->name)
+        g_string_append(str, tb->res_class);
+    else
+        g_string_append(str, tb->last_focused->name);
+    if (!tb->visible)
+        g_string_append_c(str, ']');
+
+    if (force && tb->flags.tooltips)
+        gtk_widget_set_tooltip_text(GTK_WIDGET(tb), str->str);
+
+    lxpanel_draw_label_text(tb->panel, tb->label, str->str, bold_style, 1,
+                            tb->flags.flat_button);
+
+    g_string_free(str, TRUE);
+}
+
+
+/* update task->visible, task->n_visible, task->same_name
+   also update task->last_focused if it was NULL
+   returns TRUE if button's label would need update */
+static gboolean task_update_visibility(TaskButton *task)
+{
+    guint old_n_visible = task->n_visible;
+    gboolean old_visible = !!task->visible;
+    gboolean old_same_name = !!task->same_name;
+    gboolean old_last_focused = (task->last_focused != NULL && task->last_focused->visible);
+    GList *l;
+    TaskDetails *details, *first_visible = NULL;
+
+    task->same_name = TRUE;
+    task->visible = FALSE;
+    task->n_visible = 0;
+    for (l = task->details; l; l = l->next)
+    {
+        details = l->data;
+        if (!details->visible)
+            continue;
+        if (details->monitor == task->monitor && !details->iconified)
+            /* window is visible on the current desktop */
+            task->visible = TRUE;
+        /* Compute the visible name.  If all visible windows have the same title, use that.
+         * Otherwise, use the class name.  This follows WNCK. */
+g_print("'%s' ", details->name);
+        if (first_visible == NULL)
+            first_visible = details;
+        else if (task->same_name
+                 && g_strcmp0(first_visible->name, details->name) != 0)
+            task->same_name = FALSE;
+        task->n_visible++;
+        if (task->last_focused == NULL || !task->last_focused->visible)
+            task->last_focused = details;
+    }
+g_print(": %d\n", task->same_name);
+    if (!task->n_visible && old_n_visible)
+    {
+        /* task button became invisible */
+        gtk_widget_hide(GTK_WIDGET(task));
+        return FALSE;
+    }
+    else if (task->n_visible && !old_n_visible)
+        /* task button became visible */
+        gtk_widget_show(GTK_WIDGET(task));
+    return (task->n_visible != old_n_visible || /* n_visible changed */
+            !task->visible == old_visible || /* visible changed */
+            !task->same_name == old_same_name || /* visible name changed */
+            (task->same_name && !old_last_focused)); /* visible name unavailable */
+}
+
+
+/* -----------------------------------------------------------------------------
+ * Class implementation
+ */
+G_DEFINE_TYPE(TaskButton, task_button, GTK_TYPE_TOGGLE_BUTTON)
+
+static void task_button_finalize(GObject *object)
+{
+    TaskButton *self = (TaskButton *)object;
+
+    /* free all data */
+    g_free(self->res_class);
+    if (self->menu_list)
+        g_object_remove_weak_pointer(G_OBJECT(self->menu_list),
+                                     (void **)&self->menu_list);
+    if (self->idle_loader)
+        g_source_remove(self->idle_loader);
+    g_list_free_full(self->details, (GDestroyNotify)free_task_details);
+
+    G_OBJECT_CLASS(task_button_parent_class)->finalize(object);
+}
+
+static gboolean task_button_button_press_event(GtkWidget *widget, GdkEventButton *event)
+{
+    GtkWidget *menu, *mi;
+    TaskButton *tb = PANEL_TASK_BUTTON(widget);
+
+    if (event->button == 3) /* Right click */
+    {
+        if (tb->n_visible > 1)
+        {
+            /* This is grouped-task representative, meaning that there is a class
+             * with at least two windows. */
+            menu = gtk_menu_new();
+            mi = gtk_menu_item_new_with_mnemonic (_("_Close all windows"));
+            gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
+            g_signal_connect(mi, "activate", G_CALLBACK(taskbar_close_all_windows), tb);
+            gtk_widget_show_all(menu);
+        }
+        else
+        {
+            /* Not a grouped-task representative, or entered from the grouped-task popup menu. */
+            menu = get_task_button_menu(tb, tb->last_focused);
+        }
+        /* attach menu to the widget and show it */
+        gtk_menu_attach_to_widget(GTK_MENU(menu), widget, NULL);
+        gtk_menu_popup(GTK_MENU(menu), NULL, NULL, taskbar_popup_set_position,
+                       tb, event->button, event->time);
+    }
+    return TRUE;
+}
+
+static gboolean task_button_button_release_event(GtkWidget *widget, GdkEventButton *event)
+{
+    TaskButton *tb = PANEL_TASK_BUTTON(widget);
+    TaskDetails *task;
+    GList *l;
+    char *name;
+
+    if (!tb->entered_state)
+        /* SF bug#731: don't process button release with DND. Also if button was
+           released outside of widget but DND wasn't activated: this might happen
+           if drag started at edge of button so drag treshold wasn't reached. */
+        ;
+
+    else if (tb->n_visible > 1)
+    {
+        /* This is grouped-task representative, meaning that there is a class
+         * with at least two windows. */
+        if (event->button == 1) /* Left click */
+        {
+            if (tb->menu_list) // FIXME: is that possible?
+            {
+                g_object_remove_weak_pointer(G_OBJECT(tb->menu_list),
+                                             (void **)&tb->menu_list);
+                g_signal_handlers_disconnect_by_func(G_OBJECT(tb->menu_list),
+                                                     on_menu_list_selection_done, tb);
+                gtk_menu_detach(tb->menu_list);
+            }
+            tb->menu_list = GTK_MENU(gtk_menu_new());
+            g_object_add_weak_pointer(G_OBJECT(tb->menu_list), (void **)&tb->menu_list);
+            g_signal_connect(G_OBJECT(tb->menu_list), "selection-done",
+                             G_CALLBACK(on_menu_list_selection_done), tb);
+            /* Bring up a popup menu listing all the class members. */
+            for (l = tb->details; l; l = l->next)
+            {
+                task = l->data;
+                if (task->visible)
+                {
+                    /* The menu item has the name, or the iconified name, and
+                     * the icon of the application window. */
+                    name = task->iconified ? g_strdup_printf("[%s]", task->name) : NULL;
+                    task->menu_item = gtk_image_menu_item_new_with_label(name ? name : task->name);
+                    g_free(name);
+                    if (task->icon)
+                    {
+                        GtkWidget *im = gtk_image_new_from_pixbuf(task->icon);
+                        gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(task->menu_item), im);
+                    }
+                    g_signal_connect(task->menu_item, "button-press-event",
+                                     G_CALLBACK(taskbar_popup_activate_event), tb);
+                    g_signal_connect(task->menu_item, "select",
+                                     G_CALLBACK(menu_task_selected), tb);
+                    g_signal_connect(task->menu_item, "deselect",
+                                     G_CALLBACK(menu_task_deselected), tb);
+                    gtk_menu_shell_append(GTK_MENU_SHELL(tb->menu_list), task->menu_item);
+                }
+                else
+                    task->menu_item = NULL;
+            }
+            /* Show the menu.  Set context so we can find the menu later to dismiss it.
+             * Use a position-calculation callback to get the menu nicely
+             * positioned with respect to the button. */
+            gtk_widget_show_all(GTK_WIDGET(tb->menu_list));
+            gtk_menu_attach_to_widget(tb->menu_list, widget, NULL);
+            gtk_menu_popup(tb->menu_list, NULL, NULL, taskbar_popup_set_position,
+                           tb, event->button, event->time);
+        }
+    }
+    else
+    {
+        /* Not a grouped-task representative, or entered from the grouped-task popup menu. */
+        task_button_window_do_release_event(widget, tb->last_focused, event);
+    }
+
+    /* As a matter of policy, avoid showing selected or prelight states on flat buttons. */
+    if (tb->flags.flat_button)
+        gtk_widget_set_state(widget, GTK_STATE_NORMAL);
+    return TRUE;
+}
+
+static gboolean task_button_enter_notify_event(GtkWidget *widget, GdkEventCrossing *event)
+{
+    TaskButton *tb = PANEL_TASK_BUTTON(widget);
+
+    tb->entered_state = TRUE;
+    /* As a matter of policy, avoid showing selected or prelight states on flat buttons. */
+    if (tb->flags.flat_button)
+        gtk_widget_set_state(widget, GTK_STATE_NORMAL);
+    task_draw_label(tb, tb->flags.flat_button, FALSE);
+    return GTK_WIDGET_CLASS(task_button_parent_class)->enter_notify_event(widget, event);
+}
+
+static gboolean task_button_leave_notify_event(GtkWidget *widget, GdkEventCrossing *event)
+{
+    TaskButton *tb = PANEL_TASK_BUTTON(widget);
+
+    tb->entered_state = FALSE;
+    task_draw_label(tb, FALSE, FALSE);
+    return GTK_WIDGET_CLASS(task_button_parent_class)->leave_notify_event(widget, event);
+}
+
+static gboolean task_button_scroll_event(GtkWidget *widget, GdkEventScroll *event)
+{
+    TaskButton *tb = PANEL_TASK_BUTTON(widget);
+
+    if (tb->flags.use_mouse_wheel && tb->n_visible == 1)
+    {
+        if ((event->direction == GDK_SCROLL_UP) || (event->direction == GDK_SCROLL_LEFT))
+            task_raise_window(tb, tb->last_focused, event->time);
+        else
+        {
+            Display *xdisplay = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(widget));
+            XIconifyWindow(xdisplay, tb->last_focused->win, DefaultScreen(xdisplay));
+        }
+    }
+    return TRUE;
+}
+
+static void task_button_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
+{
+    TaskButton *tb = PANEL_TASK_BUTTON(widget);
+    GList *l;
+
+    /* Pass it to the GtkToggleButton handler first */
+    GTK_WIDGET_CLASS(task_button_parent_class)->size_allocate(widget, alloc);
+
+    /* Set iconifying animation for all related windows into this button */
+    if (gtk_widget_get_realized(widget))
+        for (l = tb->details; l; l = l->next)
+            map_xwindow_animation(widget, ((TaskDetails *)l->data)->win, alloc);
+}
+
+static void task_button_class_init(TaskButtonClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(klass);
+    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+
+    object_class->finalize = task_button_finalize;
+    widget_class->button_press_event = task_button_button_press_event;
+    widget_class->button_release_event = task_button_button_release_event;
+    widget_class->enter_notify_event = task_button_enter_notify_event;
+    widget_class->leave_notify_event = task_button_leave_notify_event;
+    widget_class->scroll_event = task_button_scroll_event;
+    widget_class->size_allocate = task_button_size_allocate;
+
+    /**
+     * Signal TaskButton::menu-built is emitted when GtkMenu is built
+     * by TaskButton on its parent widget. Connected callback therefore
+     * can add own menu items with handlers.
+     */
+    signals[MENU_BUILT] = g_signal_new ("menu-built",
+                                    G_TYPE_FROM_CLASS(klass),
+                                    G_SIGNAL_RUN_FIRST,
+                                    G_STRUCT_OFFSET(TaskButtonClass, menu_built),
+                                    NULL, NULL,
+                                    g_cclosure_marshal_VOID__OBJECT,
+                                    G_TYPE_NONE, 1, GTK_TYPE_MENU);
+
+    /**
+     * Signal TaskButton::menu-target-set is emitted when TaskButton
+     * activated menu popup against some task.
+     */
+    signals[MENU_TARGET_SET] = g_signal_new ("menu-target-set",
+                                    G_TYPE_FROM_CLASS(klass),
+                                    G_SIGNAL_RUN_FIRST,
+                                    G_STRUCT_OFFSET(TaskButtonClass, menu_target_set),
+                                    NULL, NULL,
+                                    g_cclosure_marshal_VOID__ULONG,
+                                    G_TYPE_NONE, 1, G_TYPE_ULONG);
+}
+
+static void task_button_init(TaskButton *self)
+{
+    gtk_container_set_border_width(GTK_CONTAINER(self), 0);
+    gtk_widget_set_can_focus(GTK_WIDGET(self), FALSE);
+    gtk_widget_set_can_default(GTK_WIDGET(self), FALSE);
+    gtk_widget_set_state(GTK_WIDGET(self), GTK_STATE_NORMAL);
+#if GTK_CHECK_VERSION(3, 0, 0)
+    gtk_widget_add_events(GTK_WIDGET(self), GDK_SCROLL_MASK);
+#endif
+}
+
+
+/* -----------------------------------------------------------------------------
+ * Interface functions
+ */
+
+/* creates new button and sets rendering options */
+TaskButton *task_button_new(Window win, gint desk, gint desks, LXPanel *panel,
+                            const char *res_class, TaskShowFlags flags)
+{
+    TaskButton *self = g_object_new(PANEL_TYPE_TASK_BUTTON,
+                                    "relief", flags.flat_button ? GTK_RELIEF_NONE : GTK_RELIEF_NORMAL,
+                                    NULL);
+
+    /* remember data */
+    self->desktop = desk;
+    self->n_desktops = desks;
+    self->panel = panel;
+    self->monitor = panel_get_monitor(panel);
+    self->icon_size = panel_get_icon_size(panel);
+    self->res_class = g_strdup(res_class);
+    self->flags = flags;
+    /* create empty image and label */
+    self->image = gtk_image_new();
+    self->label = gtk_label_new(NULL);
+    /* append the window and set icon/label by that */
+    task_button_add_window(self, win, self->res_class);
+    /* and now let assemble all widgets we got */
+    assemble_gui(self);
+    return self;
+}
+
+gboolean task_button_has_window(TaskButton *button, Window win)
+{
+    GList *l;
+
+    g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button), FALSE);
+
+    for (l = button->details; l; l = l->next)
+        if (((TaskDetails *)l->data)->win == win)
+            return TRUE;
+    return FALSE;
+}
+
+/* removes windows from button, that are missing in list */
+void task_button_update_windows_list(TaskButton *button, Window *list, gint n)
+{
+    GList *l, *next;
+    TaskDetails *details;
+    gint i;
+
+    g_return_if_fail(PANEL_IS_TASK_BUTTON(button));
+
+    for (l = button->details; l; )
+    {
+        next = l->next;
+        details = l->data;
+        for (i = 0; i < n; i++)
+            if (list[i] == details->win)
+                break;
+        if (i >= n) /* not found, remove details now */
+        {
+            button->details = g_list_delete_link(button->details, l);
+            free_task_details(details);
+        }
+        l = next; /* go next details */
+    }
+    if (button->details == NULL) /* all windows were deleted */
+        gtk_widget_destroy(GTK_WIDGET(button));
+    // FIXME: test if need to update label and menu
+}
+
+/* returns TRUE if found and updated */
+gboolean task_button_window_xprop_changed(TaskButton *button, Window win, Atom atom)
+{
+    TaskDetails *details;
+
+    g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button), FALSE);
+
+    details = task_details_lookup(button, win);
+    if (details == NULL)
+        return FALSE;
+
+    /* Dispatch on atom. */
+    if (atom == a_NET_WM_DESKTOP)
+    {
+        /* Window changed desktop. */
+        details->desktop = get_net_wm_desktop(win);
+        details->visible = task_is_visible(button, details);
+        if (task_update_visibility(button))
+            task_draw_label(button, (button->flags.flat_button && button->entered_state), TRUE);
+    }
+    else if ((atom == XA_WM_NAME) || (atom == a_NET_WM_NAME) || (atom == a_NET_WM_VISIBLE_NAME))
+    {
+        /* Window changed name. */
+        if (task_set_names(details, atom))
+            task_draw_label(button, (button->flags.flat_button && button->entered_state), TRUE);
+    }
+    else if (atom == XA_WM_CLASS)
+    {
+        /* Read the WM_CLASS property. */
+        XClassHint ch;
+        gchar *res_class;
+
+        ch.res_name = NULL;
+        ch.res_class = NULL;
+        XGetClassHint(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), win, &ch);
+        if (ch.res_name != NULL)
+            XFree(ch.res_name);
+        if (ch.res_class != NULL)
+        {
+            res_class = g_locale_to_utf8(ch.res_class, -1, NULL, NULL, NULL);
+            XFree(ch.res_class);
+            if (res_class != NULL)
+            {
+                g_free(button->res_class);
+                button->res_class = res_class;
+                if (!button->same_name)
+                    task_draw_label(button, (button->flags.flat_button && button->entered_state), TRUE);
+            }
+        }
+    }
+    else if (atom == a_WM_STATE)
+    {
+        /* Window changed state. */
+        details->iconified = (get_wm_state(win) == IconicState);
+        details->visible = task_is_visible(button, details);
+        if (task_update_visibility(button))
+            task_draw_label(button, (button->flags.flat_button && button->entered_state), TRUE);
+    }
+    else if (atom == XA_WM_HINTS)
+    {
+        gboolean has_urgency = details->urgency;
+
+        details->urgency = task_has_urgency(win);
+        if (!has_urgency && details->urgency && button->flags.use_urgency_hint)
+        {
+            /* gained urgency, update the button */
+            details->visible = task_is_visible(button, details);
+            task_update_visibility(button);
+            if (details->visible)
+                button->last_focused = details;
+            task_draw_label(button, (button->flags.flat_button && button->entered_state), TRUE);
+        }
+        /* Window changed "window manager hints".
+         * Some windows set their WM_HINTS icon after mapping. */
+        task_update_icon(button, details, atom);
+    }
+    else if (atom == a_NET_WM_ICON)
+    {
+        /* Window changed EWMH icon. */
+        task_update_icon(button, details, atom);
+    }
+    /* else
+    {
+        char *ev_name = XGetAtomName(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), atom);
+        g_debug("got event for me: %s", ev_name);
+        XFree(ev_name);
+    } */
+
+    return TRUE;
+}
+
+gboolean task_button_window_focus_changed(TaskButton *button, Window *win)
+{
+    GList *l;
+    TaskDetails *details;
+    gboolean res = FALSE;
+
+    g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button), FALSE);
+
+    for (l = button->details; l; l = l->next)
+    {
+        details = l->data;
+        if (details->win == *win)
+        {
+            res = TRUE;
+            details->focused = TRUE;
+            button->last_focused = details;
+        }
+        else
+            details->focused = FALSE;
+    }
+    if (res)
+    {
+        /* for no flat buttons we have to reflect focus by button state */
+        if (!button->flags.flat_button)
+            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+        /* if focus changed that means button widgets may need update */
+        task_update_icon(button, button->last_focused, a_NET_ACTIVE_WINDOW);
+        task_draw_label(button, (button->flags.flat_button && button->entered_state), TRUE);
+        // FIXME: test if need to update menu
+    }
+    else
+    {
+        /* if no focus on any button window then button may need style update */
+        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE);
+        // FIXME: test if need to update menu
+    }
+    return res;
+}
+
+/* update internal data */
+gboolean task_button_window_reconfigured(TaskButton *button, Window win)
+{
+    gint old_mon, new_mon;
+    TaskDetails *details;
+
+    g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button), FALSE);
+
+    details = task_details_lookup(button, win);
+    if (details == NULL)
+        return FALSE;
+
+    /* If the same_monitor_only option is set and the window is on a different
+       monitor than before, redraw the task button */
+    old_mon = details->monitor;
+    new_mon = get_window_monitor(details->win);
+
+    if (button->flags.same_monitor_only
+        && (old_mon == button->monitor || new_mon == button->monitor))
+    {
+        details->visible = task_is_visible(button, details);
+        task_update_visibility(button);
+        task_draw_label(button, (button->flags.flat_button && button->entered_state), TRUE);
+        // FIXME: test if need to update menu
+    }
+    details->monitor = new_mon;
+    return TRUE;
+}
+
+/* updates rendering options */
+void task_button_update(TaskButton *button, gint desk, gint desks,
+                        gint mon, guint icon_size, TaskShowFlags flags)
+{
+    gboolean changed = FALSE, changed_icon = FALSE, changed_label = FALSE;
+
+    g_return_if_fail(PANEL_IS_TASK_BUTTON(button));
+
+    if (button->desktop != desk
+        || button->monitor != mon
+        || button->flags.show_all_desks != flags.show_all_desks
+        || button->flags.same_monitor_only != flags.same_monitor_only)
+        changed = TRUE;
+    if (button->n_desktops != desks)
+        task_button_reset_menu(gtk_widget_get_parent(GTK_WIDGET(button)));
+    if (button->icon_size != icon_size
+        || button->flags.disable_taskbar_upscale != flags.disable_taskbar_upscale)
+        changed_icon = TRUE;
+    if (button->flags.flat_button != flags.flat_button)
+        changed_label = TRUE;
+    if (button->flags.icons_only != flags.icons_only)
+    {
+        changed_label = !flags.icons_only;
+        gtk_widget_set_visible(button->label, changed_label);
+    }
+    if (button->flags.flat_button != flags.flat_button)
+    {
+        if(flags.flat_button)
+        {
+            gtk_toggle_button_set_active((GtkToggleButton*)button, FALSE);
+            gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
+        }
+        else
+            gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NORMAL);
+    }
+    button->desktop = desk;
+    button->n_desktops = desks;
+    button->monitor = mon;
+    button->icon_size = icon_size;
+    button->flags = flags;
+
+    if (changed)
+    {
+        if (task_update_visibility(button))
+            changed_label = TRUE;
+        // FIXME: test if need to update menu
+    }
+    if (changed_label)
+        task_draw_label(button, (button->flags.flat_button && button->entered_state), TRUE);
+    if (changed_icon)
+        task_update_icon(button, button->last_focused, None);
+}
+
+/* updates state for flashing buttons, including menu list */
+void task_button_set_flash_state(TaskButton *button, gboolean state)
+{
+    gboolean has_flash = FALSE, m_state;
+    GList *l;
+    TaskDetails *details;
+
+    g_return_if_fail(PANEL_IS_TASK_BUTTON(button));
+
+    for (l = button->details; l; l = l->next)
+    {
+        details = l->data;
+        if (button->flags.use_urgency_hint && details->urgency)
+        {
+            has_flash = TRUE;
+            m_state = state;
+        }
+        else
+            m_state = FALSE;
+        if (button->menu_list && details->menu_item
+            /* don't ever touch selected menu item, it makes odd effects */
+            && button->menu_target != details->win)
+            /* if submenu exists and mapped then set state too */
+            gtk_widget_set_state(details->menu_item,
+                                 m_state ? GTK_STATE_SELECTED : GTK_STATE_NORMAL);
+    }
+    /* Set state on the button and redraw. */
+    if (!has_flash)
+        state = button->entered_state;
+    if (button->flags.flat_button)
+        task_draw_label(button, state, FALSE); /* we have to redraw bold text state */
+    else
+        gtk_widget_set_state(GTK_WIDGET(button),
+                             state ? GTK_STATE_SELECTED : GTK_STATE_NORMAL);
+}
+
+/* adds task only if it's the same class */
+gboolean task_button_add_window(TaskButton *button, Window win, const char *cl)
+{
+    TaskDetails *details;
+    GtkAllocation alloc;
+
+    g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button), FALSE);
+
+    if (g_strcmp0(button->res_class, cl) != 0)
+        return FALSE;
+    /* fetch task details */
+    details = task_details_for_window(button, win);
+    button->details = g_list_append(button->details, details);
+    /* redraw label on the button if need */
+    if (details->visible)
+    {
+        if (task_update_visibility(button))
+            task_draw_label(button, (button->flags.flat_button && button->entered_state), TRUE);
+        // FIXME: test if need to update menu
+    }
+    gtk_widget_get_allocation(GTK_WIDGET(button), &alloc);
+    map_xwindow_animation(GTK_WIDGET(button), win, &alloc);
+    return TRUE;
+}
+
+gboolean task_button_drop_window(TaskButton *button, Window win, gboolean leave_last)
+{
+    GList *l;
+    TaskDetails *details;
+    gboolean was_last_focused;
+
+    g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button), FALSE);
+
+    if (leave_last && g_list_length(button->details) <= 1)
+        return FALSE;
+    for (l = button->details; l; l = l->next)
+        if (((TaskDetails *)l->data)->win == win)
+            break;
+    if (l == NULL) /* not our window */
+        return FALSE;
+    if (g_list_length(button->details) == 1)
+    {
+        /* this was last window, destroy the button */
+        gtk_widget_destroy(GTK_WIDGET(button));
+        return TRUE;
+    }
+    details = l->data;
+    button->details = g_list_delete_link(button->details, l);
+    was_last_focused = (button->last_focused == details);
+    if (was_last_focused)
+        button->last_focused = NULL;
+    if (details->visible)
+    {
+        task_update_visibility(button);
+        if (was_last_focused)
+            task_update_icon(button, button->last_focused, None);
+        task_draw_label(button, (button->flags.flat_button && button->entered_state), TRUE);
+        // FIXME: test if need to update menu
+    }
+    free_task_details(details);
+    return TRUE;
+}
+
+/* leaves only last task in button and returns a copy containing rest */
+TaskButton *task_button_split(TaskButton *button)
+{
+    TaskButton *sibling;
+    GList *llast;
+
+    g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button), NULL);
+
+    if (g_list_length(button->details) < 2)
+        return NULL;
+    sibling = g_object_new(PANEL_TYPE_TASK_BUTTON,
+                           "relief", button->flags.flat_button ? GTK_RELIEF_NONE : GTK_RELIEF_NORMAL,
+                           NULL);
+    sibling->res_class = g_strdup(button->res_class);
+    sibling->panel = button->panel;
+    sibling->image = gtk_image_new();
+    sibling->label = gtk_label_new(NULL);
+    llast = g_list_last(button->details);
+    sibling->details = g_list_remove_link(button->details, llast);
+    button->details = llast;
+    if (button->last_focused != llast->data)
+    {
+        /* focused item migrated to sibling */
+        sibling->last_focused = button->last_focused;
+        button->last_focused = NULL;
+    }
+    sibling->desktop = button->desktop;
+    sibling->n_desktops = button->n_desktops;
+    sibling->monitor = button->monitor;
+    sibling->icon_size = button->icon_size;
+    sibling->flags = button->flags;
+    task_update_visibility(button);
+    task_update_visibility(sibling);
+    /* force redraw icons and labels on buttons */
+    if (button->n_visible > 0)
+    {
+        task_update_icon(button, button->last_focused, None);
+        task_draw_label(button, FALSE, TRUE);
+    }
+    if (sibling->n_visible > 0)
+    {
+        task_update_icon(sibling, button->last_focused, None);
+        task_draw_label(sibling, FALSE, TRUE);
+    }
+    assemble_gui(sibling);
+    // FIXME: test if need to update menu
+    return sibling;
+}
+
+/* merges buttons if they are the same class */
+gboolean task_button_merge(TaskButton *button, TaskButton *sibling)
+{
+    g_return_val_if_fail(PANEL_IS_TASK_BUTTON(button) && PANEL_IS_TASK_BUTTON(sibling), FALSE);
+
+    if (g_strcmp0(button->res_class, sibling->res_class) != 0)
+        return FALSE;
+    /* move data lists from sibling appending to button */
+    button->details = g_list_concat(button->details, sibling->details);
+    sibling->details = NULL;
+    /* update visibility */
+    button->n_visible += sibling->n_visible;
+    button->visible = (button->visible | sibling->visible);
+    /* eliminate sibling widget now */
+    gtk_widget_destroy(GTK_WIDGET(sibling));
+    /* redraw label on the button */
+    task_draw_label(button, (button->flags.flat_button && button->entered_state), TRUE);
+    // FIXME: test if need to update menu
+    return TRUE;
+}
+
+/* single-instance-menu management, should be called on button parent widget */
+void task_button_reset_menu(GtkWidget *parent)
+{
+    g_object_set_data(G_OBJECT(parent), "task-button-menu", NULL);
+}
diff --git a/plugins/task-button.h b/plugins/task-button.h
new file mode 100644 (file)
index 0000000..837ed8e
--- /dev/null
@@ -0,0 +1,90 @@
+/**
+ * Copyright (C) 2014-2016 Andriy Grytsenko <andrej@rep.kiev.ua>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __TASK_BUTTON_H__
+#define __TASK_BUTTON_H__ 1
+
+#include "plugin.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef struct /* bitwise rendering options for taskbar */
+{
+    gboolean show_all_desks : 1;        /* show windows from all desktops */
+    gboolean tooltips : 1;              /* show tooltips */
+    gboolean icons_only : 1;            /* show icons only, omit name */
+    gboolean use_mouse_wheel : 1;       /* scroll wheel does iconify and raise */
+    gboolean use_urgency_hint : 1;      /* windows with urgency will flash */
+    gboolean flat_button : 1;           /* taskbar buttons have visible background */
+    gboolean same_monitor_only : 1;     /* only show windows that are in the same monitor as the taskbar */
+    gboolean disable_taskbar_upscale : 1; /* don't upscale taskbar icons */
+    gboolean use_net_active : 1;        /* NET_WM_ACTIVE_WINDOW is supported by the window manager */
+} TaskShowFlags;
+
+#define PANEL_TYPE_TASK_BUTTON             (task_button_get_type())
+#define PANEL_TASK_BUTTON(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+                                            PANEL_TYPE_TASK_BUTTON, TaskButton))
+#define PANEL_TASK_BUTTON_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST((klass), \
+                                            PANEL_TYPE_TASK_BUTTON, TaskButtonClass))
+#define PANEL_IS_TASK_BUTTON(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+                                            PANEL_TYPE_TASK_BUTTON))
+#define PANEL_IS_TASK_BUTTON_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+                                            PANEL_TYPE_TASK_BUTTON))
+
+extern GType task_button_get_type          (void) G_GNUC_CONST;
+
+typedef struct _TaskButton                  TaskButton;
+typedef struct _TaskButtonClass             TaskButtonClass;
+
+struct _TaskButtonClass
+{
+    GtkToggleButtonClass parent_class;
+    void (*menu_built)(TaskButton *button, GtkMenu *menu); /* "menu-built" signal */
+    void (*menu_target_set)(TaskButton *button, gulong win); /* "menu-target-set" signal */
+};
+
+/* creates new button and sets rendering options */
+TaskButton *task_button_new(Window win, gint desk, gint desks, LXPanel *panel,
+                            const char *cl, TaskShowFlags flags);
+
+gboolean task_button_has_window(TaskButton *button, Window win);
+/* removes windows from button, that are missing in list */
+void task_button_update_windows_list(TaskButton *button, Window *list, gint n);
+/* returns TRUE if found and updated */
+gboolean task_button_window_xprop_changed(TaskButton *button, Window win, Atom atom);
+gboolean task_button_window_focus_changed(TaskButton *button, Window *win);
+gboolean task_button_window_reconfigured(TaskButton *button, Window win);
+/* updates rendering options */
+void task_button_update(TaskButton *button, gint desk, gint desks,
+                        gint mon, guint icon_size, TaskShowFlags flags);
+void task_button_set_flash_state(TaskButton *button, gboolean state);
+/* adds task only if it's the same class */
+gboolean task_button_add_window(TaskButton *button, Window win, const char *cl);
+gboolean task_button_drop_window(TaskButton *button, Window win, gboolean leave_last);
+/* leaves only last task in button and returns rest if not empty */
+TaskButton *task_button_split(TaskButton *button);
+/* merges buttons if they are the same class */
+gboolean task_button_merge(TaskButton *button, TaskButton *sibling);
+/* single-instance-menu management, should be called on button parent widget */
+void task_button_reset_menu(GtkWidget *parent);
+
+G_END_DECLS
+
+#endif /* __TASK_BUTTON_H__ */
index bf5ab2a..1d5dcc1 100644 (file)
@@ -5,6 +5,7 @@ src/plugin.c
 src/gtk-run.c
 src/main.c
 src/input-button.c
+src/space.c
 data/ui/launchtaskbar.glade
 data/ui/netstatus.glade
 data/ui/panel-pref.glade
@@ -13,11 +14,11 @@ data/ui/panel-pref.glade
 plugins/cpu/cpu.c
 plugins/deskno/deskno.c
 plugins/launchtaskbar.c
+plugins/task-button.c
 plugins/dclock.c
 plugins/menu.c
 plugins/separator.c
 plugins/pager.c
-plugins/space.c
 plugins/tray.c
 plugins/xkb/xkb-plugin.c
 plugins/wincmd.c