diff --git a/applets/wncklet/Makefile.am b/applets/wncklet/Makefile.am
index dfcca2417..42ff8d17f 100644
--- a/applets/wncklet/Makefile.am
+++ b/applets/wncklet/Makefile.am
@@ -15,6 +15,8 @@ AM_CFLAGS = $(WARN_CFLAGS)
WNCKLET_SOURCES = \
wncklet.c \
wncklet.h \
+ tasklist-backend.h \
+ tasklist-core.c \
window-menu.c \
window-menu.h \
window-list.c \
@@ -23,6 +25,10 @@ WNCKLET_SOURCES = \
showdesktop.h \
workspace-switcher.c \
workspace-switcher.h \
+ xfw-pager.c \
+ xfw-pager.h \
+ xfw-window-menu.c \
+ xfw-window-menu.h \
$(BUILT_SOURCES)
@@ -33,18 +39,22 @@ WNCKLET_LDADD = \
$(LIBMATE_PANEL_APPLET_LIBS)
if ENABLE_X11
+WNCKLET_SOURCES += \
+ x11-backend.c \
+ x11-backend.h
+
WNCKLET_LDADD += \
- $(X_LIBS)
+ $(X_LIBS) \
+ $(XFW_LIBS)
+
+AM_CPPFLAGS += \
+ $(XFW_CFLAGS)
endif
if ENABLE_WAYLAND
WNCKLET_SOURCES += \
wayland-backend.c \
- wayland-backend.h \
- wayland-workspace.c \
- wayland-workspace.h \
- wayland-window-menu.c \
- wayland-window-menu.h
+ wayland-backend.h
WNCKLET_LDADD += \
$(XFW_LIBS)
diff --git a/applets/wncklet/meson.build b/applets/wncklet/meson.build
index 55d19080e..bffac45c3 100644
--- a/applets/wncklet/meson.build
+++ b/applets/wncklet/meson.build
@@ -43,6 +43,8 @@ wncklet_built_sources = [
wncklet_sources = [
'wncklet.c',
'wncklet.h',
+ 'tasklist-backend.h',
+ 'tasklist-core.c',
'window-menu.c',
'window-menu.h',
'window-list.c',
@@ -51,23 +53,29 @@ wncklet_sources = [
'showdesktop.h',
'workspace-switcher.c',
'workspace-switcher.h',
+ 'xfw-pager.c',
+ 'xfw-pager.h',
+ 'xfw-window-menu.c',
+ 'xfw-window-menu.h',
wncklet_built_sources,
]
+if have_x11
+ wncklet_sources += [
+ 'x11-backend.c',
+ 'x11-backend.h',
+ ]
+endif
+
if have_wayland
wncklet_sources += [
'wayland-backend.c',
'wayland-backend.h',
- 'wayland-workspace.c',
- 'wayland-workspace.h',
- 'wayland-window-menu.c',
- 'wayland-window-menu.h',
]
endif
wncklet_deps = [
gtk_dep,
- wnck_dep,
mate_desktop_dep,
] + libmate_panel_applet_deps
@@ -75,7 +83,7 @@ if have_x11
wncklet_deps += x11_dep
endif
-if have_wayland
+if have_x11 or have_wayland
wncklet_deps += [xfw_dep]
endif
diff --git a/applets/wncklet/org.mate.panel.applet.workspace-switcher.gschema.xml.in b/applets/wncklet/org.mate.panel.applet.workspace-switcher.gschema.xml.in
index c49f472d8..6726617fd 100644
--- a/applets/wncklet/org.mate.panel.applet.workspace-switcher.gschema.xml.in
+++ b/applets/wncklet/org.mate.panel.applet.workspace-switcher.gschema.xml.in
@@ -3,7 +3,7 @@
false
Display workspace names
- If true, the workspaces in the workspace switcher will display the names of the workspaces. Otherwise they will display the windows on the workspace. This setting only works when the window manager is Marco.
+ If true, the workspaces in the workspace switcher will display the names of the workspaces. Otherwise they will display their numbers.
true
diff --git a/applets/wncklet/showdesktop.c b/applets/wncklet/showdesktop.c
index d393d6c3a..605eace0d 100644
--- a/applets/wncklet/showdesktop.c
+++ b/applets/wncklet/showdesktop.c
@@ -29,26 +29,13 @@
#include
-#ifdef HAVE_X11
-#include
-#define WNCK_I_KNOW_THIS_IS_UNSTABLE
-#include
-#endif
-
-#ifndef HAVE_X11
-#include
-#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
-#endif
+#include
#include "wncklet.h"
#include "showdesktop.h"
#include
-#ifdef HAVE_WAYLAND
-#include
-#include
-#endif
#define TIMEOUT_ACTIVATE_SECONDS 1
#define SHOW_DESKTOP_ICON "user-desktop"
@@ -61,13 +48,7 @@ typedef struct {
GtkOrientation orient;
int size;
-#ifdef HAVE_X11
- WnckHandle* wnck_handle;
-#endif
- WnckScreen* wnck_screen;
-#ifdef HAVE_WAYLAND
XfwScreen* xfw_screen;
-#endif
guint showing_desktop: 1;
guint button_activate;
@@ -86,12 +67,7 @@ static void theme_changed_callback(GtkIconTheme* icon_theme, ShowDesktopData* sd
static void button_toggled_callback(GtkWidget* button, ShowDesktopData* sdd);
static void show_desktop_state_changed (ShowDesktopData* sdd);
-#ifdef HAVE_X11
-static void show_desktop_changed_callback(WnckScreen* screen, ShowDesktopData* sdd);
-#endif
-#ifdef HAVE_WAYLAND
-static void wayland_show_desktop_changed(XfwScreen* screen, GParamSpec* pspec, ShowDesktopData* sdd);
-#endif
+static void show_desktop_changed_callback(XfwScreen* screen, GParamSpec* pspec, ShowDesktopData* sdd);
/* this is when the panel orientation changes */
@@ -317,22 +293,12 @@ static void applet_destroyed(GtkWidget* applet, ShowDesktopData* sdd)
sdd->button_activate = 0;
}
- #ifdef HAVE_X11
- if (sdd->wnck_screen != NULL)
- {
- g_signal_handlers_disconnect_by_func(sdd->wnck_screen, show_desktop_changed_callback, sdd);
- sdd->wnck_screen = NULL;
- }
-#endif
-
-#ifdef HAVE_WAYLAND
if (sdd->xfw_screen != NULL)
{
- g_signal_handlers_disconnect_by_func(sdd->xfw_screen, wayland_show_desktop_changed, sdd);
+ g_signal_handlers_disconnect_by_func(sdd->xfw_screen, show_desktop_changed_callback, sdd);
g_object_unref (sdd->xfw_screen);
sdd->xfw_screen = NULL;
}
-#endif
if (sdd->icon_theme != NULL)
{
@@ -340,10 +306,6 @@ static void applet_destroyed(GtkWidget* applet, ShowDesktopData* sdd)
sdd->icon_theme = NULL;
}
-#ifdef HAVE_X11
- g_clear_object(&sdd->wnck_handle);
-#endif
-
g_free (sdd);
}
@@ -399,40 +361,23 @@ static void show_desktop_applet_realized(MatePanelApplet* applet, gpointer data)
screen = gtk_widget_get_screen(sdd->applet);
- if (sdd->wnck_screen != NULL)
- g_signal_handlers_disconnect_by_func(sdd->wnck_screen, show_desktop_changed_callback, sdd);
-
- sdd->wnck_screen = NULL;
-
-#ifdef HAVE_X11
- if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ if (sdd->xfw_screen != NULL)
{
- sdd->wnck_screen = wncklet_get_screen (sdd->wnck_handle, sdd->applet);
- if (sdd->wnck_screen != NULL)
- wncklet_connect_while_alive (sdd->wnck_screen,
- "showing_desktop_changed",
- G_CALLBACK (show_desktop_changed_callback),
- sdd,
- sdd->applet);
- else
- g_warning ("Could not get WnckScreen!");
+ g_signal_handlers_disconnect_by_func(sdd->xfw_screen, show_desktop_changed_callback, sdd);
+ g_object_unref (sdd->xfw_screen);
+ sdd->xfw_screen = NULL;
}
-#endif /* HAVE_X11 */
-#ifdef HAVE_WAYLAND
- if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ()))
+
+ sdd->xfw_screen = xfw_screen_get_default ();
+ if (sdd->xfw_screen != NULL)
{
- XfwScreen* xfw_screen = xfw_screen_get_default ();
- if (xfw_screen != NULL)
- {
- g_signal_connect (G_OBJECT (xfw_screen), "notify::show-desktop",
- G_CALLBACK (wayland_show_desktop_changed),
- sdd);
- sdd->xfw_screen = xfw_screen;
- }
- else
- g_warning ("Could not get XfwScreen!");
+ g_signal_connect (G_OBJECT (sdd->xfw_screen), "notify::show-desktop",
+ G_CALLBACK (show_desktop_changed_callback),
+ sdd);
}
-#endif
+ else
+ g_warning ("Could not get XfwScreen!");
+
show_desktop_state_changed (sdd);
sdd->icon_theme = gtk_icon_theme_get_for_screen (screen);
@@ -476,13 +421,6 @@ gboolean show_desktop_applet_fill(MatePanelApplet* applet)
sdd->size = mate_panel_applet_get_size(MATE_PANEL_APPLET(sdd->applet));
-#ifdef HAVE_X11
- if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
- {
- sdd->wnck_handle = wnck_handle_new(WNCK_CLIENT_TYPE_PAGER);
- }
-#endif
-
g_signal_connect (sdd->applet, "realize",
G_CALLBACK (show_desktop_applet_realized),
sdd);
@@ -599,34 +537,10 @@ static void button_toggled_callback(GtkWidget* button, ShowDesktopData* sdd)
{
gboolean can_show_desktop;
-#ifdef HAVE_X11
- if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
- {
- can_show_desktop = gdk_x11_screen_supports_net_wm_hint(gtk_widget_get_screen(button),
- gdk_atom_intern("_NET_SHOWING_DESKTOP",
- FALSE));
- }
-#ifdef HAVE_WAYLAND
- else if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ()))
-#endif
-#endif
-#ifdef HAVE_WAYLAND
-#ifndef HAVE_X11
- if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ()))
-#endif
- {
- can_show_desktop = sdd->xfw_screen != NULL;
- if (can_show_desktop)
- xfw_screen_set_show_desktop (sdd->xfw_screen,
- gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)));
- }
-#endif
-
-else
-
- { /* not using X11 or wayland */
- can_show_desktop = FALSE;
- }
+ can_show_desktop = sdd->xfw_screen != NULL;
+ if (can_show_desktop)
+ xfw_screen_set_show_desktop (sdd->xfw_screen,
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)));
if (!can_show_desktop)
{
@@ -660,12 +574,6 @@ else
return;
}
-#ifdef HAVE_X11
- if (sdd->wnck_screen != NULL)
- wnck_screen_toggle_showing_desktop(sdd->wnck_screen, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)));
-#endif /* HAVE_X11 */
-
-
update_button_display (sdd);
}
@@ -674,29 +582,13 @@ show_desktop_state_changed (ShowDesktopData* sdd)
{
sdd->showing_desktop = FALSE;
-#ifdef HAVE_X11
- if (sdd->wnck_screen != NULL)
- sdd->showing_desktop = (wnck_screen_get_showing_desktop(sdd->wnck_screen) != FALSE);
-#endif /* HAVE_X11 */
-
-#ifdef HAVE_WAYLAND
if (sdd->xfw_screen != NULL)
sdd->showing_desktop = xfw_screen_get_show_desktop (sdd->xfw_screen);
-#endif /* HAVE_WAYLAND */
update_button_state (sdd);
}
-#ifdef HAVE_X11
-static void show_desktop_changed_callback(WnckScreen* screen, ShowDesktopData* sdd)
-{
- show_desktop_state_changed (sdd);
-}
-#endif /* HAVE_X11 */
-
-#ifdef HAVE_WAYLAND
-static void wayland_show_desktop_changed(XfwScreen* screen, GParamSpec* pspec, ShowDesktopData* sdd)
+static void show_desktop_changed_callback(XfwScreen* screen, GParamSpec* pspec, ShowDesktopData* sdd)
{
show_desktop_state_changed (sdd);
}
-#endif /* HAVE_WAYLAND */
diff --git a/applets/wncklet/tasklist-backend.h b/applets/wncklet/tasklist-backend.h
new file mode 100644
index 000000000..6ae2cc3c6
--- /dev/null
+++ b/applets/wncklet/tasklist-backend.h
@@ -0,0 +1,148 @@
+/* Wncklet applet tasklist backend interface */
+
+/*
+ * Copyright (C) 2019 William Wold
+ * Copyright (C) 2026 Aleksey Samoilov
+ *
+ * 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 St, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/* This header is deliberately free of libwnck, libxfce4windowing and of any
+ * HAVE_X11 / HAVE_WAYLAND guard: nothing windowing-system specific belongs
+ * here, so that every backend and the core can include it unconditionally.
+ */
+
+#ifndef _WNCKLET_APPLET_TASKLIST_BACKEND_H_
+#define _WNCKLET_APPLET_TASKLIST_BACKEND_H_
+
+#include
+
+G_BEGIN_DECLS
+
+typedef enum {
+ TASKLIST_NEVER_GROUP,
+ TASKLIST_AUTO_GROUP,
+ TASKLIST_ALWAYS_GROUP
+} TasklistGroupingType;
+
+/* Window state as reported by a backend, so that the core does not have to
+ * know about XfwWindowState or WnckWindowState. */
+typedef enum {
+ TASKLIST_STATE_ACTIVE = 1 << 0,
+ TASKLIST_STATE_MAXIMIZED = 1 << 1,
+ TASKLIST_STATE_MINIMIZED = 1 << 2,
+ TASKLIST_STATE_FULLSCREEN = 1 << 3,
+ TASKLIST_STATE_URGENT = 1 << 4
+} TasklistWindowState;
+
+/* A backend provides the windowing-system specific half of the tasklist.
+ * Screens, windows and applications cross this boundary as opaque handles, so
+ * that no windowing library type ever reaches the core.
+ *
+ * The core owns no signal handlers for backend objects. It asks the backend to
+ * start or stop following the backend's own signals, which keeps the number of
+ * handler ids the core has to track at zero and lets tasklist_rebuild() pause
+ * and resume tracking cheaply.
+ *
+ * Ownership of handles is entirely the backend's concern. A window or
+ * application handle stays valid for as long as the backend reports tracking
+ * for it, and the core never unrefs one.
+ */
+typedef struct _TasklistBackend TasklistBackend;
+
+struct _TasklistBackend {
+ /* screens */
+
+ /* The default screen, or NULL if there is none. */
+ gpointer (*get_screen) (void);
+ /* The windows of a screen, as a GList of window handles owned by the
+ * backend. Only windows that should get a task button are reported. */
+ GList *(*list_windows) (gpointer screen);
+ gpointer (*get_active_window) (gpointer screen);
+
+ /* windows */
+
+ const gchar *(*get_window_name) (gpointer window);
+ GIcon *(*get_window_gicon) (gpointer window);
+ GdkPixbuf *(*get_window_pixbuf) (gpointer window, gint size, gint scale);
+ TasklistWindowState (*get_window_state) (gpointer window);
+ gpointer (*get_window_application) (gpointer window);
+ /* The identifier used in the drag-and-drop payload. On X11 this is the
+ * window XID; on Wayland there is no XID and the handle is used. */
+ gulong (*get_window_id) (gpointer window);
+
+ /* applications, used as the grouping key */
+
+ const gchar *(*get_app_name) (gpointer app);
+ GIcon *(*get_app_gicon) (gpointer app);
+
+ /* Icon shown while no icon is known yet. */
+ const gchar *(*get_fallback_icon_name) (void);
+
+ /* workspaces, for the settings that decide which windows get a task
+ * button and whether unminimizing pulls the user to another workspace */
+
+ gboolean (*window_is_on_active_workspace) (gpointer window);
+ void (*window_move_to_workspace) (gpointer window);
+
+ /* actions */
+
+ void (*activate_window) (gpointer window, guint32 user_time);
+ void (*close_window) (gpointer window, guint32 timestamp);
+ void (*set_window_maximized) (gpointer window, gboolean maximized);
+ void (*set_window_minimized) (gpointer window, gboolean minimized);
+
+ /* signal tracking. Both tracking calls must use the core widget as the
+ * signal user data, so that stopping is a single
+ * g_signal_handlers_disconnect_by_data() call on the core widget. */
+ void (*set_screen_tracking) (GtkWidget *core, gpointer screen, gboolean track);
+ void (*set_window_tracking) (GtkWidget *core, gpointer window, gboolean track);
+ void (*set_app_tracking) (GtkWidget *core, gpointer app, gboolean track);
+};
+
+/* Building and configuring the tasklist. The tasklist is a plain widget, so
+ * that the applet shell does not need to know how it is implemented. */
+GtkWidget *tasklist_core_new (const TasklistBackend *backend);
+void tasklist_core_set_orientation (GtkWidget *core, GtkOrientation orient);
+void tasklist_core_set_middle_click_close (GtkWidget *core, gboolean enabled);
+void tasklist_core_set_scroll_enabled (GtkWidget *core, gboolean enabled);
+void tasklist_core_set_grouping (GtkWidget *core, TasklistGroupingType grouping);
+void tasklist_core_set_include_all_workspaces (GtkWidget *core, gboolean include);
+void tasklist_core_set_switch_workspace_on_unminimize (GtkWidget *core, gboolean switch_ws);
+/* The panel's cross-axis thickness (height on a horizontal panel, width on a
+ * vertical one), which is what the icon size is chosen from. size <= 0 means
+ * unknown; the call is then ignored. */
+void tasklist_core_set_panel_size (GtkWidget *core, gint size);
+
+/* The (max, min) width staircase the panel uses to decide how much room the
+ * tasklist may take. Derived from widget measurements, so it is not backend
+ * state. Borrowed, must not be freed. */
+const int *tasklist_core_get_size_hint_list (GtkWidget *core, int *n_elements);
+
+/* Callbacks a backend makes when its own signals fire. */
+void tasklist_core_window_added (GtkWidget *core, gpointer window);
+void tasklist_core_window_removed (GtkWidget *core, gpointer window);
+void tasklist_core_window_state_changed (GtkWidget *core, gpointer window);
+void tasklist_core_window_name_changed (GtkWidget *core, gpointer window);
+void tasklist_core_window_icon_changed (GtkWidget *core, gpointer window);
+void tasklist_core_window_application_changed (GtkWidget *core, gpointer window);
+void tasklist_core_app_icon_changed (GtkWidget *core, gpointer app);
+void tasklist_core_app_name_changed (GtkWidget *core, gpointer app);
+void tasklist_core_active_window_changed (GtkWidget *core);
+
+G_END_DECLS
+
+#endif /* _WNCKLET_APPLET_TASKLIST_BACKEND_H_ */
diff --git a/applets/wncklet/tasklist-core.c b/applets/wncklet/tasklist-core.c
new file mode 100644
index 000000000..3d927c004
--- /dev/null
+++ b/applets/wncklet/tasklist-core.c
@@ -0,0 +1,2458 @@
+/* Wncklet applet tasklist core, independent of the windowing backend */
+
+/*
+ * Copyright (C) 2019 William Wold
+ * Copyright (C) 2026 Aleksey Samoilov
+ *
+ * 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 St, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include
+
+#include
+
+#include "tasklist-backend.h"
+
+/*shorter than wnck-tasklist due to common use of larger fonts*/
+#define TASKLIST_TEXT_MAX_WIDTH 16
+
+/*In the future this could be changable from the panel-prefs dialog*/
+static const int max_button_width = 180;
+
+/* GTK hands most panel button themes back a zero button padding, which makes
+ * the icon size picker choose one size too large for the panel. Enforce a
+ * floor so the icons keep a little breathing room at the panel edges. */
+static const int min_button_padding = 2;
+
+/* Fallback icon size, used until the panel tells us how much room it has.
+ * After that TasklistManager.icon_size_enum/icon_px take over. */
+static const int icon_size = 16;
+
+/* The width a button shrinks to before it drops to just its icon. This
+ * matches libwnck's DEFAULT_GROUPING_LIMIT so that size hints computed by
+ * the core line up with the ones a WnckTasklist used to produce. */
+static const int default_grouping_limit = 80;
+
+typedef struct
+{
+ GtkWidget *menu;
+ GtkWidget *maximize;
+ GtkWidget *minimize;
+ GtkWidget *on_top;
+ GtkWidget *close;
+ GtkWidget *close_all;
+} ContextMenu;
+
+typedef struct _GroupTask GroupTask;
+
+typedef struct
+{
+ GtkWidget *list;
+ GtkWidget *outer_box;
+ ContextMenu *context_menu;
+ const TasklistBackend *backend;
+ gpointer screen;
+ GList *tasks;
+ gboolean scroll_enabled;
+ gboolean middle_click_close;
+ TasklistGroupingType grouping;
+ gboolean include_all_workspaces;
+ gboolean switch_workspace_on_unminimize;
+ gboolean auto_grouping;
+ gboolean auto_grouping_applied;
+ guint auto_grouping_idle;
+ gboolean rebuilding;
+ GHashTable *apps;
+ GHashTable *window_to_group;
+
+ /* Icon-size refresh scheduled on a theme change, so the fit can be
+ * recalculated off the new button padding. */
+ guint icon_refresh_idle;
+ gulong theme_changed_signal;
+
+ /* Button sizing, kept per tasklist rather than in file statics so that
+ * several tasklists cannot interfere with each other. */
+ guint buttons;
+ gint tasklist_width;
+ gint full_button_width;
+ gint grouping_limit;
+ gint last_button_space;
+
+ /* Standard icon size the buttons are rendering at, picked to fit the
+ * panel the applet was given. icon_px is its resolved size in logical
+ * pixels, cached because the show/hide thresholds need it on every
+ * pass. panel_thickness is the panel size itself, height on a
+ * horizontal panel and width on a vertical one, 0 until the applet
+ * reports it. */
+ GtkIconSize icon_size_enum;
+ gint icon_px;
+ gint panel_thickness;
+
+ /* Descending (max, min) width staircase handed to the panel, which walks
+ * it to decide how wide the tasklist may be as space runs short. */
+ gint *size_hints;
+ guint size_hints_len;
+} TasklistManager;
+
+typedef struct
+{
+ GtkWidget *button;
+ GtkWidget *icon;
+ GtkWidget *label;
+ gpointer window;
+ gboolean active;
+ gboolean maximized;
+ gboolean minimized;
+ gboolean fullscreen;
+ gboolean urgent;
+ TasklistManager *tasklist;
+ GroupTask *group;
+ gint natural_width;
+} ToplevelTask;
+
+struct _GroupTask
+{
+ TasklistManager *tasklist;
+ gpointer app;
+ GtkWidget *button;
+ GtkWidget *box;
+ GtkWidget *icon;
+ GtkWidget *label;
+ GtkWidget *counter_label;
+ GList *members;
+ guint n_windows;
+ gboolean active;
+ gboolean minimized;
+ gboolean urgent;
+};
+
+static const char *tasklist_manager_key = "tasklist_manager";
+static const char *toplevel_task_key = "toplevel_task";
+static const char *group_task_key = "group_task";
+
+/* The outer box carries the hover notifications, so it needs a type of its
+ * own to hang signals on. It adds no state of its own: the manager is still
+ * reached through the tasklist_manager_key object data. */
+typedef struct
+{
+ GtkBox parent_instance;
+} TasklistBox;
+
+typedef struct
+{
+ GtkBoxClass parent_class;
+} TasklistBoxClass;
+
+#define TASKLIST_TYPE_BOX (tasklist_box_get_type ())
+GType tasklist_box_get_type (void) G_GNUC_CONST;
+
+G_DEFINE_TYPE (TasklistBox, tasklist_box, GTK_TYPE_BOX)
+
+enum
+{
+ TASK_ENTER_NOTIFY,
+ TASK_LEAVE_NOTIFY,
+ TASKLIST_BOX_LAST_SIGNAL
+};
+
+static guint tasklist_box_signals[TASKLIST_BOX_LAST_SIGNAL];
+
+static void
+tasklist_box_class_init (TasklistBoxClass *klass)
+{
+ tasklist_box_signals[TASK_ENTER_NOTIFY] =
+ g_signal_new ("task-enter-notify", TASKLIST_TYPE_BOX,
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+ tasklist_box_signals[TASK_LEAVE_NOTIFY] =
+ g_signal_new ("task-leave-notify", TASKLIST_TYPE_BOX,
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+}
+
+static void
+tasklist_box_init (TasklistBox *box)
+{
+}
+
+static GtkTargetEntry source_targets[] =
+{
+ { "application/x-wnck-window-id", 0, 0 }
+};
+
+static ToplevelTask *toplevel_task_new (TasklistManager *tasklist, gpointer window);
+static gboolean tasklist_scroll_event (GtkWidget *widget, GdkEventScroll *event, TasklistManager *tasklist);
+static void group_task_update (GroupTask *group);
+static void group_task_update_name (GroupTask *group);
+static void group_task_update_name_for (GroupTask *group, ToplevelTask *task);
+static void group_task_add_window (GroupTask *group, ToplevelTask *task);
+static void group_task_remove_window (GroupTask *group, ToplevelTask *task);
+static void tasklist_assign_to_group (TasklistManager *tasklist, ToplevelTask *task);
+static void group_task_child_state_changed (GroupTask *group);
+static void tasklist_rebuild (TasklistManager *tasklist);
+static void adjust_buttons (TasklistManager *tasklist, int button_space, int buttons, ToplevelTask *task);
+static void tasklist_update_size_hints (TasklistManager *tasklist, gint natural_width);
+static void tasklist_refresh_icon_size (TasklistManager *tasklist);
+static void tasklist_theme_changed (GtkSettings *settings, GParamSpec *pspec, TasklistManager *tasklist);
+static gint tasklist_count_visible_buttons (TasklistManager *tasklist);
+static void tasklist_refresh_visibility (TasklistManager *tasklist);
+static void tasklist_unminimize (TasklistManager *tasklist, gpointer window);
+static gboolean on_toplevel_button_enter_notify (GtkWidget *button, GdkEvent *event, TasklistManager *tasklist);
+static gboolean on_toplevel_button_leave_notify (GtkWidget *button, GdkEvent *event, TasklistManager *tasklist);
+
+static gboolean
+window_is_active (TasklistManager *tasklist, gpointer window)
+{
+ if (window == NULL)
+ return FALSE;
+
+ return (tasklist->backend->get_window_state (window) & TASKLIST_STATE_ACTIVE) != 0;
+}
+
+/* A window only gets a visible task button when the tasklist shows every
+ * workspace, or when the window is on the one that is active. */
+static gboolean
+task_should_be_shown (TasklistManager *tasklist, gpointer window)
+{
+ const TasklistBackend *backend = tasklist->backend;
+
+ if (tasklist->include_all_workspaces)
+ return TRUE;
+
+ return backend->window_is_on_active_workspace (window);
+}
+
+static void
+update_task_visibility (ToplevelTask *task)
+{
+ if (task == NULL || task->button == NULL)
+ return;
+
+ gtk_widget_set_visible (task->button,
+ task_should_be_shown (task->tasklist, task->window));
+}
+
+static void
+update_task_state (ToplevelTask *task)
+{
+ TasklistWindowState state;
+
+ if (!task || !task->window)
+ return;
+
+ state = task->tasklist->backend->get_window_state (task->window);
+ task->active = (state & TASKLIST_STATE_ACTIVE) != 0;
+ task->maximized = (state & TASKLIST_STATE_MAXIMIZED) != 0;
+ task->minimized = (state & TASKLIST_STATE_MINIMIZED) != 0;
+ task->fullscreen = (state & TASKLIST_STATE_FULLSCREEN) != 0;
+ task->urgent = (state & TASKLIST_STATE_URGENT) != 0;
+
+ if (task->button)
+ {
+ if (task->urgent)
+ gtk_style_context_add_class (gtk_widget_get_style_context (task->button), "urgent");
+ else
+ gtk_style_context_remove_class (gtk_widget_get_style_context (task->button), "urgent");
+
+ gtk_button_set_relief (GTK_BUTTON (task->button),
+ task->active ? GTK_RELIEF_NORMAL : GTK_RELIEF_NONE);
+ }
+
+ update_task_visibility (task);
+
+ if (task->group)
+ group_task_child_state_changed (task->group);
+}
+
+static gint
+tasklist_button_space (TasklistManager *tasklist, gint buttons)
+{
+ if (buttons <= 0)
+ return 0;
+
+ return MIN ((tasklist->tasklist_width / buttons) * 0.75,
+ tasklist->full_button_width);
+}
+
+static void
+adjust_buttons (TasklistManager *tasklist, int button_space, int buttons, ToplevelTask *task)
+{
+ GtkWidget *widget, *button, *box;
+ GList *children, *contents;
+ GroupTask *group;
+ gboolean crowded;
+ gint width;
+
+ /*catch the case of an added button that can be missed
+ *Note that button space can come up zero on a first button
+ */
+ if (buttons < 2)
+ {
+ if (task)
+ gtk_widget_set_size_request (task->button, tasklist->full_button_width, -1);
+ }
+
+ crowded = (buttons >= 2) &&
+ (buttons * tasklist->full_button_width >= tasklist->tasklist_width * 0.75);
+
+ /* the button that just appeared gets the same treatment as the rest */
+ if (task)
+ {
+ if (crowded && button_space < tasklist->icon_px * 3)
+ gtk_widget_hide (task->icon);
+ else
+ gtk_widget_show (task->icon);
+
+ if (crowded && button_space < tasklist->icon_px)
+ gtk_widget_hide (task->label);
+ else
+ gtk_widget_show (task->label);
+ }
+
+ children = gtk_container_get_children (GTK_CONTAINER (tasklist->list));
+
+ while (children != NULL)
+ {
+ button = GTK_WIDGET (children->data);
+ box = gtk_bin_get_child (GTK_BIN (button));
+
+ /* keep buttons that are hidden as group members hidden,
+ * even when there is enough space to show them at full width */
+ {
+ ToplevelTask *child_task =
+ g_object_get_data (G_OBJECT (button), toplevel_task_key);
+ if (child_task && child_task->group)
+ {
+ children = children->next;
+ continue;
+ }
+ }
+
+ group = g_object_get_data (G_OBJECT (button), group_task_key);
+
+ if (group != NULL && !crowded)
+ {
+ children = children->next;
+ continue;
+ }
+
+ if (!crowded)
+ {
+ gtk_widget_set_size_request (button, tasklist->full_button_width, -1);
+ gtk_widget_show_all (button);
+ children = children->next;
+ continue;
+ }
+
+ width = MIN (button_space, tasklist->full_button_width);
+ gtk_widget_set_size_request (button, width, -1);
+
+ if (group != NULL)
+ {
+ if (width < tasklist->icon_px * 3)
+ gtk_widget_hide (group->icon);
+ else
+ gtk_widget_show (group->icon);
+
+ if (width < tasklist->icon_px)
+ gtk_widget_hide (group->label);
+ else
+ gtk_widget_show (group->label);
+ }
+ else
+ {
+ contents = gtk_container_get_children (GTK_CONTAINER (box));
+ while (contents != NULL)
+ {
+ widget = GTK_WIDGET (contents->data);
+
+ if (GTK_IS_IMAGE (widget))
+ {
+ if (width < tasklist->icon_px * 3)
+ gtk_widget_hide (widget);
+ else
+ gtk_widget_show (widget);
+ }
+ else if (GTK_IS_LABEL (widget))
+ {
+ if (width < tasklist->icon_px)
+ gtk_widget_hide (widget);
+ else
+ gtk_widget_show (widget);
+ }
+
+ contents = contents->next;
+ }
+ g_list_free (contents);
+ }
+
+ children = children->next;
+ }
+
+ g_list_free (children);
+}
+
+static void
+update_task_icon (ToplevelTask *task)
+{
+ const TasklistBackend *backend;
+ GIcon *icon = NULL;
+ gpointer app;
+ gint width = 0;
+
+ if (!task || !task->window || !task->icon)
+ return;
+
+ backend = task->tasklist->backend;
+
+ app = backend->get_window_application (task->window);
+ if (app != NULL)
+ icon = backend->get_app_gicon (app);
+ if (icon == NULL)
+ icon = backend->get_window_gicon (task->window);
+
+ if (icon != NULL)
+ gtk_image_set_from_gicon (GTK_IMAGE (task->icon), icon,
+ task->tasklist->icon_size_enum);
+ else
+ gtk_image_set_from_icon_name (GTK_IMAGE (task->icon),
+ backend->get_fallback_icon_name (),
+ task->tasklist->icon_size_enum);
+
+ /* Force the rendered size, not just the size asked for above. The icon
+ * theme is free to hand back whatever pixel size it likes, so an
+ * application that only ships a 128x128 icon would otherwise drag the
+ * button's minimum width up with it and push the tasklist off the
+ * panel. With a pixel size set, GTK asks the theme for exactly this
+ * size and scales there, which also keeps the result sharp. */
+ gtk_icon_size_lookup (task->tasklist->icon_size_enum, &width, NULL);
+ gtk_image_set_pixel_size (GTK_IMAGE (task->icon), width);
+}
+
+static GIcon *
+window_gicon (TasklistManager *tasklist, gpointer window)
+{
+ const TasklistBackend *backend;
+ GIcon *icon = NULL;
+ gpointer app;
+
+ if (!window)
+ return NULL;
+
+ backend = tasklist->backend;
+
+ app = backend->get_window_application (window);
+ if (app != NULL)
+ icon = backend->get_app_gicon (app);
+ if (icon == NULL)
+ icon = backend->get_window_gicon (window);
+
+ return icon;
+}
+
+static void
+group_menu_window_activate (GtkMenuItem *item, gpointer user_data)
+{
+ ToplevelTask *task = user_data;
+
+ if (task && task->window)
+ task->tasklist->backend->activate_window (task->window, 0);
+}
+
+static void
+group_task_update_name (GroupTask *group)
+{
+ const TasklistBackend *backend = group->tasklist->backend;
+ gpointer active;
+ GList *l;
+ const gchar *name = NULL;
+
+ if (!group || !group->label)
+ return;
+
+ active = NULL;
+ if (group->tasklist && group->tasklist->screen)
+ active = backend->get_active_window (group->tasklist->screen);
+
+ for (l = group->members; l != NULL; l = l->next)
+ {
+ ToplevelTask *task = l->data;
+
+ if (!task || !task->window)
+ continue;
+
+ if (name == NULL)
+ name = backend->get_window_name (task->window);
+ if (active != NULL && task->window == active)
+ {
+ name = backend->get_window_name (task->window);
+ break;
+ }
+ }
+
+ if (name == NULL)
+ name = backend->get_app_name (group->app);
+
+ gtk_label_set_label (GTK_LABEL (group->label), name ? name : "");
+}
+
+static void
+group_task_update_name_for (GroupTask *group, ToplevelTask *task)
+{
+ const gchar *name;
+
+ if (!group || !group->label || !task || !task->window)
+ return;
+
+ name = group->tasklist->backend->get_window_name (task->window);
+ gtk_label_set_label (GTK_LABEL (group->label), name ? name : "");
+}
+
+static void
+group_task_update_icon (GroupTask *group)
+{
+ GIcon *icon;
+ gint width = 0;
+
+ if (!group || !group->icon)
+ return;
+
+ icon = group->tasklist->backend->get_app_gicon (group->app);
+
+ if (icon != NULL)
+ gtk_image_set_from_gicon (GTK_IMAGE (group->icon), icon,
+ group->tasklist->icon_size_enum);
+ else
+ gtk_image_set_from_icon_name (GTK_IMAGE (group->icon),
+ group->tasklist->backend->get_fallback_icon_name (),
+ group->tasklist->icon_size_enum);
+
+ /* Same reason as update_task_icon(): the group button must not inherit
+ * the minimum width of whatever the icon theme decides to hand back. */
+ gtk_icon_size_lookup (group->tasklist->icon_size_enum, &width, NULL);
+ gtk_image_set_pixel_size (GTK_IMAGE (group->icon), width);
+}
+
+static gboolean
+tasklist_should_group (TasklistManager *tasklist, guint n_windows)
+{
+ if (tasklist->grouping == TASKLIST_NEVER_GROUP)
+ return FALSE;
+ if (tasklist->grouping == TASKLIST_ALWAYS_GROUP)
+ return n_windows >= 2;
+ return tasklist->auto_grouping_applied && n_windows >= 2;
+}
+
+static gint
+tasklist_count_visible_buttons (TasklistManager *tasklist)
+{
+ GList *children, *l;
+ gint visible = 0;
+
+ children = gtk_container_get_children (GTK_CONTAINER (tasklist->list));
+
+ for (l = children; l != NULL; l = l->next)
+ {
+ if (gtk_widget_get_visible (GTK_WIDGET (l->data)))
+ visible++;
+ }
+
+ g_list_free (children);
+
+ return visible;
+}
+
+static gboolean
+tasklist_buttons_crowded (TasklistManager *tasklist, gint available_width)
+{
+ GtkRequisition req;
+ GList *l;
+ gint needed = 0;
+
+ for (l = tasklist->tasks; l != NULL; l = l->next)
+ {
+ ToplevelTask *task = l->data;
+
+ if (task == NULL || task->button == NULL)
+ continue;
+
+ if (!gtk_widget_get_visible (task->button))
+ continue;
+
+ gtk_widget_get_preferred_width (task->button, NULL, &req.width);
+ task->natural_width = req.width;
+
+ if (task->natural_width <= 0)
+ task->natural_width = tasklist->icon_px + 32;
+
+ needed += task->natural_width;
+ }
+
+ tasklist_update_size_hints (tasklist, needed);
+
+ return needed > available_width;
+}
+
+/* The standard icon sizes the tasklist may render at, largest first. Only
+ * these are offered because GtkImage wants a GtkIconSize to look the icon up
+ * with and a pixel size to pin the result to; both have to be set, or the
+ * icon theme is free to hand back whatever size it likes. */
+static const GtkIconSize tasklist_icon_sizes[] = {
+ GTK_ICON_SIZE_DIALOG,
+ GTK_ICON_SIZE_DND,
+ GTK_ICON_SIZE_LARGE_TOOLBAR,
+ GTK_ICON_SIZE_MENU,
+};
+
+/* What the panel has left over for an icon once the button's own style padding
+ * is taken off, matching what showdesktop.c does. The panel size the applet
+ * reports and the sizes gtk_icon_size_lookup() returns are both logical
+ * pixels, so unlike showdesktop there is no scale factor to apply here: it
+ * loads the surface itself, we only tell GTK what to load. */
+static gint
+tasklist_icon_available_space (TasklistManager *tasklist)
+{
+ GtkWidget *probe = NULL;
+ GtkStyleContext *context;
+ GtkStateFlags state;
+ GtkBorder padding;
+ const gboolean horizontal =
+ gtk_orientable_get_orientation (GTK_ORIENTABLE (tasklist->outer_box)) ==
+ GTK_ORIENTATION_HORIZONTAL;
+
+ if (tasklist->panel_thickness <= 0)
+ return 0;
+
+ /* Probing a real button is the accurate case: set_panel_size() runs
+ * after tasklist_manager_new() has already added the existing
+ * windows. Fall back to the box so a tasklist with no windows still
+ * gets a sane answer. */
+ if (tasklist->tasks != NULL && tasklist->tasks->data != NULL)
+ probe = ((ToplevelTask *) tasklist->tasks->data)->button;
+
+ if (probe == NULL)
+ probe = tasklist->list;
+
+ context = gtk_widget_get_style_context (probe);
+ state = gtk_widget_get_state_flags (probe);
+ gtk_style_context_get_padding (context, state, &padding);
+
+ padding.top = MAX (padding.top, min_button_padding);
+ padding.bottom = MAX (padding.bottom, min_button_padding);
+ padding.left = MAX (padding.left, min_button_padding);
+ padding.right = MAX (padding.right, min_button_padding);
+
+ if (horizontal)
+ return tasklist->panel_thickness - padding.top - padding.bottom;
+
+ return tasklist->panel_thickness - padding.left - padding.right;
+}
+
+/* Largest standard icon size that still fits the space available. */
+static GtkIconSize
+tasklist_choose_icon_size (gint available_px)
+{
+ guint i;
+ gint width = 0, height = 0;
+
+ for (i = 0; i < G_N_ELEMENTS (tasklist_icon_sizes); i++)
+ {
+ if (!gtk_icon_size_lookup (tasklist_icon_sizes[i], &width, &height))
+ continue;
+
+ if (MAX (width, height) <= available_px)
+ return tasklist_icon_sizes[i];
+ }
+
+ /* Nothing fits, so keep the smallest, which is what the tasklist has
+ * always used. It is the one size that is never worse than before. */
+ return GTK_ICON_SIZE_MENU;
+}
+
+/* Push the current icon size at every button. The buttons' minimum widths
+ * follow the icon, so the hints the panel is holding go stale with it: clear
+ * the button_space cache, ask for a relayout, and re-measure now rather than
+ * waiting for the next size-allocate, which may not come if only the panel's
+ * thickness changed. */
+static void
+tasklist_apply_icon_size (TasklistManager *tasklist)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+ gint width = 0, height = 0;
+ GList *l;
+
+ for (l = tasklist->tasks; l != NULL; l = l->next)
+ update_task_icon (l->data);
+
+ if (tasklist->apps)
+ {
+ g_hash_table_iter_init (&iter, tasklist->apps);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ GroupTask *group = value;
+
+ if (group == NULL)
+ continue;
+
+ group_task_update_icon (group);
+ }
+ }
+
+ /* Keep a usable floor even if the theme refuses to answer for this
+ * size: icon_px drives the show/hide thresholds, and 0 would make
+ * every button drop its icon. */
+ if (gtk_icon_size_lookup (tasklist->icon_size_enum, &width, &height))
+ tasklist->icon_px = MAX (width, height);
+ else
+ tasklist->icon_px = icon_size;
+
+ tasklist->last_button_space = -1;
+ gtk_widget_queue_resize (tasklist->outer_box);
+
+ if (tasklist->tasklist_width > 0)
+ tasklist_buttons_crowded (tasklist, tasklist->tasklist_width);
+}
+
+/* Build the width staircase the panel walks when it has to squeeze the
+ * tasklist. The array is a descending series of (max, min) pixel pairs: give
+ * me this much room and the buttons fit at their natural width, and at least
+ * this much room or they collapse. The last entry is always 0 so the tasklist
+ * can be squeezed down to nothing rather than pushed off the panel.
+ *
+ * We only describe the ungrouped layout. Collapsing windows into groups frees
+ * space too, but that happens on its own via the auto-grouping check above, so
+ * omitting those steps only makes the hint list slightly less precise.
+ */
+static void
+tasklist_update_size_hints (TasklistManager *tasklist, gint natural_width)
+{
+ const gboolean horizontal =
+ gtk_orientable_get_orientation (GTK_ORIENTABLE (tasklist->outer_box)) ==
+ GTK_ORIENTATION_HORIZONTAL;
+ const guint n_buttons = tasklist->buttons;
+ gint floor;
+ GArray *hints;
+
+ if (n_buttons == 0)
+ {
+ g_clear_pointer (&tasklist->size_hints, g_free);
+ tasklist->size_hints_len = 0;
+ return;
+ }
+
+ /* A vertical panel is sized by height, and the core does not compress
+ * vertical buttons at all, so there is nothing to describe. */
+ if (!horizontal)
+ return;
+
+ floor = MIN (tasklist->icon_px + 2 * min_button_padding, natural_width);
+
+ hints = g_array_new (FALSE, FALSE, sizeof (gint));
+ g_array_append_val (hints, natural_width);
+ g_array_append_val (hints, floor);
+
+ g_free (tasklist->size_hints);
+ tasklist->size_hints_len = hints->len;
+ tasklist->size_hints = (gint *)g_array_free (hints, FALSE);
+}
+
+static gboolean
+tasklist_apply_auto_grouping (gpointer data)
+{
+ TasklistManager *tasklist = data;
+
+ tasklist->auto_grouping_idle = 0;
+
+ if (tasklist->auto_grouping == tasklist->auto_grouping_applied)
+ return G_SOURCE_REMOVE;
+
+ tasklist->auto_grouping_applied = tasklist->auto_grouping;
+ tasklist_rebuild (tasklist);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+tasklist_list_size_allocate (GtkWidget *widget, GdkRectangle *allocation, TasklistManager *tasklist)
+{
+ gboolean crowded;
+ gboolean auto_group = (tasklist->grouping == TASKLIST_AUTO_GROUP);
+ gint visible, button_space;
+
+ if (!auto_group)
+ tasklist->auto_grouping = FALSE;
+
+ /* don't evaluate crowd state while we are rebuilding, the buttons are
+ * half-hidden and the measurement would suggest the wrong thing */
+ if (tasklist->rebuilding)
+ return;
+
+ if (gtk_orientable_get_orientation (GTK_ORIENTABLE (tasklist->outer_box)) == GTK_ORIENTATION_HORIZONTAL
+ && allocation->width > 1)
+ {
+ visible = tasklist_count_visible_buttons (tasklist);
+
+ if (visible > 0)
+ {
+ tasklist->tasklist_width = allocation->width;
+ tasklist->full_button_width = MIN (max_button_width, allocation->width / 3);
+ button_space = tasklist_button_space (tasklist, visible);
+
+ if (button_space != tasklist->last_button_space)
+ {
+ tasklist->last_button_space = button_space;
+ adjust_buttons (tasklist, button_space, visible, NULL);
+ }
+ }
+ }
+
+ crowded = tasklist_buttons_crowded (tasklist, allocation->width);
+
+ if (!auto_group)
+ return;
+
+ tasklist->auto_grouping = crowded;
+
+ if (crowded != tasklist->auto_grouping_applied && tasklist->auto_grouping_idle == 0)
+ tasklist->auto_grouping_idle = g_idle_add (tasklist_apply_auto_grouping, tasklist);
+}
+
+static void
+group_task_update_member_buttons (GroupTask *group)
+{
+ GList *l;
+ gboolean grouped;
+
+ if (!group)
+ return;
+
+ grouped = tasklist_should_group (group->tasklist, group->n_windows);
+
+ for (l = group->members; l != NULL; l = l->next)
+ {
+ ToplevelTask *task = l->data;
+
+ if (task && task->button)
+ {
+ if (grouped)
+ gtk_widget_hide (task->button);
+ else if (!gtk_widget_get_visible (task->button))
+ gtk_widget_show (task->button);
+ }
+ }
+}
+
+static void
+group_task_update (GroupTask *group)
+{
+ gboolean grouped;
+
+ if (!group)
+ return;
+
+ grouped = tasklist_should_group (group->tasklist, group->n_windows);
+
+ group_task_update_name (group);
+ group_task_update_icon (group);
+
+ if (group->counter_label)
+ {
+ if (grouped)
+ {
+ gchar *text = g_strdup_printf ("%u", group->n_windows);
+ gtk_label_set_label (GTK_LABEL (group->counter_label), text);
+ g_free (text);
+ gtk_widget_show (group->counter_label);
+ }
+ else
+ {
+ gtk_widget_hide (group->counter_label);
+ }
+ }
+
+ if (grouped)
+ gtk_widget_show (group->button);
+ else
+ gtk_widget_hide (group->button);
+}
+
+static void
+group_task_child_state_changed (GroupTask *group)
+{
+ GList *l;
+ gboolean active = FALSE;
+ gboolean urgent = FALSE;
+ gboolean minimized = TRUE;
+
+ if (!group || !group->button)
+ return;
+
+ for (l = group->members; l != NULL; l = l->next)
+ {
+ ToplevelTask *task = l->data;
+
+ if (!task)
+ continue;
+ if (task->active)
+ active = TRUE;
+ if (task->urgent)
+ urgent = TRUE;
+ if (!task->minimized)
+ minimized = FALSE;
+ }
+
+ group->active = active;
+ group->urgent = urgent;
+ group->minimized = minimized;
+
+ if (urgent)
+ gtk_style_context_add_class (gtk_widget_get_style_context (group->button), "urgent");
+ else
+ gtk_style_context_remove_class (gtk_widget_get_style_context (group->button), "urgent");
+
+ gtk_button_set_relief (GTK_BUTTON (group->button),
+ active ? GTK_RELIEF_NORMAL : GTK_RELIEF_NONE);
+
+ /* the group button shows the active window's title */
+ group_task_update_name (group);
+}
+
+static void
+group_menu_close_all (GtkMenuItem *item, gpointer user_data)
+{
+ GroupTask *group = user_data;
+ GList *l;
+
+ for (l = group->members; l != NULL; l = l->next)
+ {
+ ToplevelTask *task = l->data;
+
+ if (task && task->window)
+ task->tasklist->backend->close_window (task->window, 0);
+ }
+}
+
+static void
+group_task_menu_show (GroupTask *group, GdkEventButton *event, gboolean right_click)
+{
+ GtkWidget *menu;
+ GList *l;
+
+ if (!group || !group->button)
+ return;
+
+ (void) right_click;
+
+ menu = gtk_menu_new ();
+
+ for (l = group->members; l != NULL; l = l->next)
+ {
+ ToplevelTask *task = l->data;
+ const TasklistBackend *backend = group->tasklist->backend;
+ GtkWidget *item, *box, *icon, *label;
+ GIcon *gicon;
+ gint width = 0;
+
+ if (!task || !task->window)
+ continue;
+
+ item = gtk_menu_item_new ();
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ icon = gtk_image_new ();
+ gicon = window_gicon (group->tasklist, task->window);
+
+ if (gicon != NULL)
+ gtk_image_set_from_gicon (GTK_IMAGE (icon), gicon,
+ group->tasklist->icon_size_enum);
+ else
+ gtk_image_set_from_icon_name (GTK_IMAGE (icon),
+ backend->get_fallback_icon_name (),
+ group->tasklist->icon_size_enum);
+
+ /* A menu row would otherwise be stretched to the icon's own
+ * pixel size, which is not fixed at GTK_ICON_SIZE_MENU. */
+ gtk_icon_size_lookup (group->tasklist->icon_size_enum, &width, NULL);
+ gtk_image_set_pixel_size (GTK_IMAGE (icon), width);
+
+ label = gtk_label_new (backend->get_window_name (task->window));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
+ gtk_label_set_max_width_chars (GTK_LABEL (label), 24);
+
+ gtk_box_pack_start (GTK_BOX (box), icon, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 0);
+ gtk_container_add (GTK_CONTAINER (item), box);
+
+ g_signal_connect (item, "activate",
+ G_CALLBACK (group_menu_window_activate), task);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show_all (item);
+ }
+
+ /* a visible group button always represents two or more windows, so
+ * "Close All Windows" is offered whether the menu was opened by left
+ * click (picker) or right click (context menu) */
+ {
+ GtkWidget *item;
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), gtk_separator_menu_item_new ());
+ item = gtk_menu_item_new_with_label ("Close All Windows");
+ g_signal_connect (item, "activate", G_CALLBACK (group_menu_close_all), group);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show_all (item);
+ }
+
+ gtk_widget_show (menu);
+ gtk_menu_popup_at_widget (GTK_MENU (menu), group->button,
+ GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_SOUTH_WEST, (GdkEvent *) event);
+}
+
+static void
+group_task_clicked (GtkButton *button, GroupTask *group)
+{
+ group_task_menu_show (group, NULL, FALSE);
+}
+
+static gboolean
+on_group_button_press (GtkWidget *button, GdkEvent *event, GroupTask *group)
+{
+ if (((GdkEventButton*)event)->button == GDK_BUTTON_MIDDLE &&
+ group->tasklist->middle_click_close)
+ {
+ GList *l;
+
+ for (l = group->members; l != NULL; l = l->next)
+ {
+ ToplevelTask *task = l->data;
+
+ if (task && task->window)
+ task->tasklist->backend->close_window (task->window,
+ ((GdkEventButton*)event)->time);
+ }
+ return TRUE;
+ }
+
+ if (((GdkEventButton*)event)->button == GDK_BUTTON_SECONDARY)
+ {
+ group_task_menu_show (group, (GdkEventButton*)event, TRUE);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static GroupTask *
+group_task_new (TasklistManager *tasklist, gpointer app)
+{
+ GroupTask *group;
+
+ group = g_new0 (GroupTask, 1);
+ group->tasklist = tasklist;
+ group->app = app;
+
+ group->button = gtk_button_new ();
+ group->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ group->icon = gtk_image_new_from_icon_name (tasklist->backend->get_fallback_icon_name (),
+ GTK_ICON_SIZE_MENU);
+ group->label = gtk_label_new ("");
+ group->counter_label = gtk_label_new ("");
+
+ gtk_label_set_max_width_chars (GTK_LABEL (group->label), TASKLIST_TEXT_MAX_WIDTH);
+ gtk_label_set_ellipsize (GTK_LABEL (group->label), PANGO_ELLIPSIZE_END);
+ gtk_label_set_xalign (GTK_LABEL (group->label), 0.0);
+
+ gtk_box_pack_start (GTK_BOX (group->box), group->icon, FALSE, FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (group->box), group->label, TRUE, TRUE, 2);
+ gtk_box_pack_start (GTK_BOX (group->box), group->counter_label, FALSE, FALSE, 4);
+
+ gtk_container_add (GTK_CONTAINER (group->button), group->box);
+ gtk_widget_set_name (group->button, "tasklist-button");
+ gtk_widget_show_all (group->button);
+
+ g_object_set_data (G_OBJECT (group->button), group_task_key, group);
+
+ g_signal_connect (group->button, "clicked",
+ G_CALLBACK (group_task_clicked), group);
+ g_signal_connect (group->button, "button-press-event",
+ G_CALLBACK (on_group_button_press), group);
+ g_signal_connect (group->button, "scroll-event",
+ G_CALLBACK (tasklist_scroll_event), tasklist);
+ g_signal_connect (group->button, "enter-notify-event",
+ G_CALLBACK (on_toplevel_button_enter_notify), tasklist);
+ g_signal_connect (group->button, "leave-notify-event",
+ G_CALLBACK (on_toplevel_button_leave_notify), tasklist);
+
+ tasklist->backend->set_app_tracking (GTK_WIDGET (tasklist->outer_box),
+ app, TRUE);
+
+ group_task_update_icon (group);
+ group_task_update_name (group);
+
+ gtk_box_pack_start (GTK_BOX (tasklist->list), group->button, TRUE, TRUE, 0);
+ /* hidden until it has more than one window */
+ gtk_widget_hide (group->button);
+
+ return group;
+}
+
+static void
+group_task_free (gpointer data)
+{
+ GroupTask *group = data;
+
+ if (!group)
+ return;
+
+ /* A group can be freed while the application is still being tracked,
+ * either when its last window goes away or when the whole tasklist is
+ * torn down, so stop tracking here rather than only in tasklist_rebuild. */
+ if (group->app && group->tasklist)
+ group->tasklist->backend->set_app_tracking (GTK_WIDGET (group->tasklist->outer_box),
+ group->app, FALSE);
+
+ group->button = NULL;
+
+ g_list_free (group->members);
+ g_free (group);
+}
+
+static void
+group_task_add_window (GroupTask *group, ToplevelTask *task)
+{
+ if (!group || !task || !task->window)
+ return;
+
+ if (g_list_find (group->members, task) != NULL)
+ return;
+
+ task->group = group;
+ group->members = g_list_append (group->members, task);
+ group->n_windows++;
+
+ g_hash_table_insert (group->tasklist->window_to_group, task->window, group);
+
+ group_task_update (group);
+ group_task_update_member_buttons (group);
+ group_task_child_state_changed (group);
+ group_task_update_name_for (group, task);
+}
+
+static void
+group_task_remove_window (GroupTask *group, ToplevelTask *task)
+{
+ if (!group || !task)
+ return;
+
+ group->members = g_list_remove (group->members, task);
+ if (task->window &&
+ g_hash_table_lookup (group->tasklist->window_to_group, task->window) == group)
+ g_hash_table_remove (group->tasklist->window_to_group, task->window);
+
+ task->group = NULL;
+ group->n_windows--;
+
+ if (group->n_windows == 0)
+ {
+ if (group->button)
+ {
+ GtkWidget *parent = gtk_widget_get_parent (group->button);
+ if (parent)
+ gtk_container_remove (GTK_CONTAINER (parent), group->button);
+ group->button = NULL;
+ }
+
+ g_hash_table_remove (group->tasklist->apps, group->app);
+ return;
+ }
+
+ group_task_update (group);
+ group_task_update_member_buttons (group);
+ group_task_child_state_changed (group);
+}
+
+static void
+tasklist_assign_to_group (TasklistManager *tasklist, ToplevelTask *task)
+{
+ const TasklistBackend *backend = tasklist->backend;
+ gpointer app;
+ GroupTask *group;
+
+ if (tasklist->grouping == TASKLIST_NEVER_GROUP)
+ return;
+
+ app = backend->get_window_application (task->window);
+ if (app == NULL)
+ return;
+
+ group = g_hash_table_lookup (tasklist->apps, app);
+ if (group == NULL)
+ {
+ group = group_task_new (tasklist, app);
+ g_hash_table_insert (tasklist->apps, app, group);
+ }
+
+ group_task_add_window (group, task);
+}
+
+static void
+menu_on_maximize (GtkMenuItem *item, gpointer user_data)
+{
+ ToplevelTask *task = g_object_get_data (G_OBJECT (item), toplevel_task_key);
+ if (task && task->window)
+ task->tasklist->backend->set_window_maximized (task->window, !task->maximized);
+}
+
+static void
+menu_on_minimize (GtkMenuItem *item, gpointer user_data)
+{
+ ToplevelTask *task = g_object_get_data (G_OBJECT (item), toplevel_task_key);
+ if (task && task->window)
+ {
+ if (task->minimized)
+ tasklist_unminimize (task->tasklist, task->window);
+ else
+ task->tasklist->backend->set_window_minimized (task->window, TRUE);
+ }
+}
+
+static void
+menu_on_close (GtkMenuItem *item, gpointer user_data)
+{
+ ToplevelTask *task = g_object_get_data (G_OBJECT (item), toplevel_task_key);
+ if (task && task->window)
+ task->tasklist->backend->close_window (task->window, 0);
+}
+
+static void
+menu_on_close_all (GtkMenuItem *item, gpointer user_data)
+{
+ ToplevelTask *task = g_object_get_data (G_OBJECT (item), toplevel_task_key);
+ GroupTask *group;
+ GList *l;
+
+ if (!task)
+ return;
+
+ group = task->group;
+ if (!group)
+ {
+ if (task->window)
+ task->tasklist->backend->close_window (task->window, 0);
+ return;
+ }
+
+ for (l = group->members; l != NULL; l = l->next)
+ {
+ ToplevelTask *member = l->data;
+
+ if (member && member->window)
+ member->tasklist->backend->close_window (member->window, 0);
+ }
+}
+
+static ContextMenu *
+context_menu_new ()
+{
+ ContextMenu *menu = g_new0 (ContextMenu, 1);
+ menu->menu = gtk_menu_new ();
+ menu->maximize = gtk_menu_item_new ();
+ menu->minimize = gtk_menu_item_new ();
+ menu->on_top = gtk_check_menu_item_new_with_label ("Always On Top");
+ menu->close = gtk_menu_item_new_with_label ("Close");
+ menu->close_all = gtk_menu_item_new_with_label ("Close All Windows");
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), menu->maximize);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), menu->minimize);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), gtk_separator_menu_item_new ());
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), menu->on_top);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), gtk_separator_menu_item_new ());
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), menu->close);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), menu->close_all);
+
+ gtk_widget_show_all (menu->menu);
+
+ g_signal_connect (menu->maximize, "activate", G_CALLBACK (menu_on_maximize), NULL);
+ g_signal_connect (menu->minimize, "activate", G_CALLBACK (menu_on_minimize), NULL);
+ g_signal_connect (menu->close, "activate", G_CALLBACK (menu_on_close), NULL);
+ g_signal_connect (menu->close_all, "activate", G_CALLBACK (menu_on_close_all), NULL);
+ gtk_widget_set_sensitive (menu->on_top, FALSE);
+ return menu;
+}
+
+static gboolean
+tasklist_has_window (TasklistManager *tasklist, gpointer window)
+{
+ GList *l;
+
+ for (l = tasklist->tasks; l != NULL; l = l->next)
+ {
+ ToplevelTask *task = l->data;
+ if (task->window == window)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+tasklist_remove_window (TasklistManager *tasklist, gpointer window)
+{
+ GList *l;
+
+ for (l = tasklist->tasks; l != NULL; l = l->next)
+ {
+ ToplevelTask *task = l->data;
+ if (task->window == window)
+ {
+ GtkOrientation orient;
+ GtkWidget *outer_box, *parent_box;
+ int button_space;
+
+ tasklist->buttons = tasklist->buttons - 1;
+
+ /* remove the window from its group (if any) before destroying the button */
+ if (task->group)
+ group_task_remove_window (task->group, task);
+
+ outer_box = tasklist->outer_box;
+ /* removes the task from tasklist->tasks and destroys the button */
+ tasklist->tasks = g_list_remove (tasklist->tasks, task);
+ gtk_widget_destroy (task->button);
+
+ if (tasklist->buttons == 0)
+ return;
+
+ /* We don't need to modify button size on a vertical panel*/
+ orient = gtk_orientable_get_orientation (GTK_ORIENTABLE (outer_box));
+ if (orient == GTK_ORIENTATION_VERTICAL)
+ return;
+
+ /*Get the box the tasklist outer box sits in
+ *and leave a little space so the buttons don't push other applets off the panel
+ */
+ parent_box = gtk_widget_get_ancestor ((outer_box), GTK_TYPE_BOX);
+ tasklist->tasklist_width = MAX (gtk_widget_get_allocated_width (parent_box),
+ tasklist->tasklist_width);
+ button_space = tasklist_button_space (tasklist, tasklist->buttons);
+ adjust_buttons (tasklist, button_space, tasklist->buttons, NULL);
+
+ return;
+ }
+ }
+}
+
+static void
+tasklist_manager_disconnected_from_widget (TasklistManager *tasklist)
+{
+ if (tasklist->auto_grouping_idle != 0)
+ {
+ g_source_remove (tasklist->auto_grouping_idle);
+ tasklist->auto_grouping_idle = 0;
+ }
+
+ if (tasklist->icon_refresh_idle != 0)
+ {
+ g_source_remove (tasklist->icon_refresh_idle);
+ tasklist->icon_refresh_idle = 0;
+ }
+
+ if (tasklist->theme_changed_signal != 0)
+ {
+ g_signal_handler_disconnect (gtk_settings_get_default (),
+ tasklist->theme_changed_signal);
+ tasklist->theme_changed_signal = 0;
+ }
+
+ g_clear_pointer (&tasklist->size_hints, g_free);
+ tasklist->size_hints_len = 0;
+
+ if (tasklist->list)
+ {
+ GList *children = gtk_container_get_children (GTK_CONTAINER (tasklist->list));
+ for (GList *iter = children; iter != NULL; iter = g_list_next (iter))
+ gtk_widget_destroy (GTK_WIDGET (iter->data));
+ g_list_free (children);
+ tasklist->list = NULL;
+ }
+
+ /* free any remaining tasks not removed by window-closed;
+ * toplevel_task_disconnected_from_widget (GDestroyNotify) handles freeing
+ * as buttons finalize, so just stop tracking the windows here. */
+ {
+ GList *t;
+ for (t = tasklist->tasks; t != NULL; t = t->next)
+ {
+ ToplevelTask *task = t->data;
+ if (task->window)
+ tasklist->backend->set_window_tracking (GTK_WIDGET (tasklist->outer_box),
+ task->window, FALSE);
+ }
+ }
+
+ if (tasklist->apps)
+ {
+ g_hash_table_destroy (tasklist->apps);
+ tasklist->apps = NULL;
+ }
+
+ if (tasklist->window_to_group)
+ {
+ g_hash_table_destroy (tasklist->window_to_group);
+ tasklist->window_to_group = NULL;
+ }
+
+ if (tasklist->outer_box)
+ tasklist->outer_box = NULL;
+
+ if (tasklist->screen)
+ {
+ tasklist->backend->set_screen_tracking (GTK_WIDGET (tasklist->outer_box),
+ tasklist->screen, FALSE);
+ tasklist->screen = NULL;
+ }
+
+ if (tasklist->context_menu)
+ {
+ gtk_widget_destroy (tasklist->context_menu->menu);
+ g_free (tasklist->context_menu);
+ tasklist->context_menu = NULL;
+ }
+
+ g_free (tasklist);
+}
+
+static TasklistManager *
+tasklist_manager_new (const TasklistBackend *backend)
+{
+ TasklistManager *tasklist;
+ GList *windows, *l;
+ gpointer screen = backend->get_screen ();
+
+ if (screen == NULL)
+ return NULL;
+
+ tasklist = g_new0 (TasklistManager, 1);
+ tasklist->backend = backend;
+ tasklist->screen = screen;
+ tasklist->grouping_limit = default_grouping_limit;
+ tasklist->last_button_space = -1;
+ tasklist->icon_size_enum = GTK_ICON_SIZE_MENU;
+ tasklist->icon_px = icon_size;
+ tasklist->list = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_set_homogeneous (GTK_BOX (tasklist->list), TRUE);
+ tasklist->outer_box = g_object_new (TASKLIST_TYPE_BOX, NULL);
+ gtk_box_pack_start (GTK_BOX (tasklist->outer_box), tasklist->list, FALSE, FALSE, 0);
+ gtk_widget_show (tasklist->list);
+ g_object_set_data_full (G_OBJECT (tasklist->outer_box),
+ tasklist_manager_key,
+ tasklist,
+ (GDestroyNotify)tasklist_manager_disconnected_from_widget);
+ tasklist->context_menu = context_menu_new ();
+
+ tasklist->apps = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
+ (GDestroyNotify) group_task_free);
+ tasklist->window_to_group = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ /* mouse scroll to switch windows */
+ gtk_widget_add_events (tasklist->list, GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK);
+ g_signal_connect (tasklist->list, "scroll-event",
+ G_CALLBACK (tasklist_scroll_event), tasklist);
+ /* space-based auto-grouping */
+ g_signal_connect (tasklist->list, "size-allocate",
+ G_CALLBACK (tasklist_list_size_allocate), tasklist);
+
+ tasklist->theme_changed_signal = g_signal_connect (gtk_settings_get_default (),
+ "notify::gtk-theme-name",
+ G_CALLBACK (tasklist_theme_changed), tasklist);
+
+ /* add all existing windows on this screen */
+ windows = backend->list_windows (screen);
+ for (l = windows; l != NULL; l = l->next)
+ {
+ gpointer window = l->data;
+ ToplevelTask *task = toplevel_task_new (tasklist, window);
+ if (task != NULL)
+ gtk_box_pack_start (GTK_BOX (tasklist->list), task->button, TRUE, TRUE, 0);
+ }
+
+ /* monitor window changes */
+ backend->set_screen_tracking (GTK_WIDGET (tasklist->outer_box), screen, TRUE);
+
+ return tasklist;
+}
+
+static void
+toplevel_task_disconnected_from_widget (ToplevelTask *task)
+{
+ if (task->window)
+ {
+ task->tasklist->backend->set_window_tracking (GTK_WIDGET (task->tasklist->outer_box),
+ task->window, FALSE);
+ }
+
+ if (task->tasklist)
+ task->tasklist->tasks = g_list_remove (task->tasklist->tasks, task);
+
+ task->button = NULL;
+ task->icon = NULL;
+ task->label = NULL;
+ task->window = NULL;
+
+ g_free (task);
+}
+
+static gboolean
+tasklist_horizontal (TasklistManager *tasklist)
+{
+ return gtk_orientable_get_orientation (GTK_ORIENTABLE (tasklist->list)) == GTK_ORIENTATION_HORIZONTAL;
+}
+
+static void
+tasklist_button_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time,
+ ToplevelTask *task)
+{
+ gulong wid = task->tasklist->backend->get_window_id (task->window);
+ gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data),
+ 8, (guchar *) &wid, sizeof (wid));
+}
+
+static void
+tasklist_button_drag_begin (GtkWidget *widget,
+ GdkDragContext *context,
+ ToplevelTask *task)
+{
+ GdkPixbuf *icon;
+ gint scale;
+
+ if (task->window)
+ {
+ scale = gtk_widget_get_scale_factor (widget);
+ icon = task->tasklist->backend->get_window_pixbuf (task->window,
+ task->tasklist->icon_px,
+ scale);
+ if (icon != NULL)
+ gtk_drag_set_icon_pixbuf (context, icon, 0, 0);
+ }
+}
+
+static gboolean
+tasklist_button_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x, gint y, guint time,
+ TasklistManager *tasklist)
+{
+ GtkWidget *source_widget;
+ ToplevelTask *source, *target;
+ GList *source_node, *target_node;
+ GtkAllocation allocation;
+ gboolean second_half;
+
+ source_widget = gtk_drag_get_source_widget (context);
+ if (source_widget == NULL)
+ return FALSE;
+
+ /* only reorder among our own tasklist buttons */
+ if (gtk_widget_get_parent (source_widget) != GTK_WIDGET (tasklist->list))
+ return FALSE;
+
+ source = g_object_get_data (G_OBJECT (source_widget), toplevel_task_key);
+ target = g_object_get_data (G_OBJECT (widget), toplevel_task_key);
+ if (source == NULL || target == NULL || source == target)
+ return FALSE;
+
+ /* drop on the right/bottom half -> insert after the target button */
+ gtk_widget_get_allocation (widget, &allocation);
+ second_half = tasklist_horizontal (tasklist)
+ ? x >= allocation.width / 2
+ : y >= allocation.height / 2;
+
+ source_node = g_list_find (tasklist->tasks, source);
+ target_node = g_list_find (tasklist->tasks, target);
+ if (source_node == NULL || target_node == NULL)
+ return FALSE;
+ if (second_half)
+ target_node = g_list_next (target_node);
+
+ /* no change needed if already at the insertion point */
+ if (source_node == target_node || g_list_next (source_node) == target_node)
+ {
+ gdk_drag_status (context, GDK_ACTION_MOVE, time);
+ return TRUE;
+ }
+
+ /* live-reorder the list and the widgets */
+ tasklist->tasks = g_list_remove (tasklist->tasks, source);
+ tasklist->tasks = g_list_insert_before (tasklist->tasks, target_node, source);
+ gtk_box_reorder_child (GTK_BOX (tasklist->list), source->button,
+ g_list_index (tasklist->tasks, source));
+ gtk_widget_queue_resize (tasklist->list);
+
+ gdk_drag_status (context, GDK_ACTION_MOVE, time);
+ return TRUE;
+}
+
+static void
+tasklist_button_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x, gint y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time,
+ TasklistManager *tasklist)
+{
+ /* the list has already been reordered live by drag-motion, so
+ * nothing left to do here */
+}
+
+static void
+toplevel_task_handle_clicked (GtkButton *button, ToplevelTask *task)
+{
+ if (task->window)
+ {
+ if (task->active)
+ {
+ task->tasklist->backend->set_window_minimized (task->window, TRUE);
+ }
+ else
+ {
+ /* A minimized window is normally activated in place, but
+ * that is invisible to the user if it lives on another
+ * workspace, so follow it there first. */
+ if (task->minimized)
+ tasklist_unminimize (task->tasklist, task->window);
+ else
+ task->tasklist->backend->activate_window (task->window, 0);
+ }
+ }
+}
+
+/* Hover notifications. The windows are gathered the way libwnck's
+ * wnck_task_extract_windows() did: a plain task reports its own window, a
+ * group reports every window it holds. The list is only valid for the
+ * duration of the emission. */
+static void
+tasklist_emit_hover_notify (GtkWidget *button, TasklistManager *tasklist, guint signal_id)
+{
+ GroupTask *group;
+ ToplevelTask *task;
+ GList *windows = NULL;
+
+ group = g_object_get_data (G_OBJECT (button), group_task_key);
+ task = g_object_get_data (G_OBJECT (button), toplevel_task_key);
+
+ if (group != NULL)
+ {
+ GList *l;
+
+ for (l = group->members; l != NULL; l = l->next)
+ {
+ ToplevelTask *member = l->data;
+
+ if (member != NULL && member->window != NULL)
+ windows = g_list_prepend (windows, member->window);
+ }
+ }
+ else if (task != NULL && task->window != NULL)
+ {
+ windows = g_list_prepend (windows, task->window);
+ }
+
+ windows = g_list_reverse (windows);
+
+ /* The signals belong to the outer box, which is the widget the applet
+ * shell holds on to. */
+ g_signal_emit (tasklist->outer_box, signal_id, 0, windows);
+
+ g_list_free (windows);
+}
+
+static gboolean
+on_toplevel_button_enter_notify (GtkWidget *button, GdkEvent *event, TasklistManager *tasklist)
+{
+ tasklist_emit_hover_notify (button, tasklist, tasklist_box_signals[TASK_ENTER_NOTIFY]);
+
+ return FALSE;
+}
+
+static gboolean
+on_toplevel_button_leave_notify (GtkWidget *button, GdkEvent *event, TasklistManager *tasklist)
+{
+ tasklist_emit_hover_notify (button, tasklist, tasklist_box_signals[TASK_LEAVE_NOTIFY]);
+
+ return FALSE;
+}
+
+static gboolean on_toplevel_button_press (GtkWidget *button, GdkEvent *event, TasklistManager *tasklist)
+{
+ /* Assume event is a button press */
+
+ if (((GdkEventButton*)event)->button == GDK_BUTTON_MIDDLE &&
+ tasklist->middle_click_close)
+ {
+ ToplevelTask *task = g_object_get_data (G_OBJECT (button), toplevel_task_key);
+ if (task && task->window)
+ task->tasklist->backend->close_window (task->window, ((GdkEventButton*)event)->time);
+ return TRUE;
+ }
+
+ if (((GdkEventButton*)event)->button == GDK_BUTTON_SECONDARY)
+ {
+ ContextMenu *menu = tasklist->context_menu;
+ ToplevelTask *task = g_object_get_data (G_OBJECT (button), toplevel_task_key);
+
+ g_object_set_data (G_OBJECT (menu->maximize), toplevel_task_key, task);
+ g_object_set_data (G_OBJECT (menu->minimize), toplevel_task_key, task);
+ g_object_set_data (G_OBJECT (menu->close), toplevel_task_key, task);
+ g_object_set_data (G_OBJECT (menu->close_all), toplevel_task_key, task);
+
+ /* offer "Close All Windows" whenever grouping is enabled, so the
+ * option exists in Auto mode even while the windows are shown as
+ * individual buttons (and not just on the group button) */
+ gtk_widget_set_visible (menu->close_all,
+ tasklist->grouping != TASKLIST_NEVER_GROUP);
+
+ gtk_menu_item_set_label (GTK_MENU_ITEM (menu->minimize),
+ task->minimized ? "Unminimize" : "Minimize");
+ gtk_menu_item_set_label (GTK_MENU_ITEM (menu->maximize),
+ task->maximized ? "Unmaximize" : "Maximize");
+
+ gtk_menu_popup_at_widget (GTK_MENU (menu->menu), button,
+ GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_SOUTH_WEST, event);
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+static gboolean
+tasklist_scroll_event (GtkWidget *widget, GdkEventScroll *event, TasklistManager *tasklist)
+{
+ ToplevelTask *child;
+ GList *li, *lnew = NULL;
+ GdkScrollDirection direction;
+ gboolean wrap_windows = TRUE;
+
+ if (!tasklist->scroll_enabled)
+ return TRUE;
+
+ /* get the current active window button */
+ for (li = tasklist->tasks; li != NULL; li = li->next)
+ {
+ child = li->data;
+ if (child->window && !child->group && window_is_active (tasklist, child->window))
+ break;
+ }
+
+ if (li == NULL)
+ return TRUE;
+
+ if (event->direction != GDK_SCROLL_SMOOTH)
+ direction = event->direction;
+ else if (event->delta_y < 0)
+ direction = GDK_SCROLL_UP;
+ else if (event->delta_y > 0)
+ direction = GDK_SCROLL_DOWN;
+ else if (event->delta_x < 0)
+ direction = GDK_SCROLL_LEFT;
+ else if (event->delta_x > 0)
+ direction = GDK_SCROLL_RIGHT;
+ else
+ return TRUE;
+
+ switch (direction)
+ {
+ case GDK_SCROLL_UP:
+ /* find the previous button on the tasklist */
+ for (lnew = g_list_previous (li);; lnew = lnew->prev)
+ {
+ if (lnew == NULL)
+ {
+ if (wrap_windows)
+ {
+ lnew = g_list_last (li);
+ wrap_windows = FALSE;
+ if (lnew == NULL)
+ break;
+ }
+ else
+ break;
+ }
+
+ child = lnew->data;
+ if (child->window != NULL && !child->group)
+ break;
+ }
+ break;
+
+ case GDK_SCROLL_DOWN:
+ /* find the next button on the tasklist */
+ for (lnew = g_list_next (li);; lnew = lnew->next)
+ {
+ if (lnew == NULL)
+ {
+ if (wrap_windows)
+ {
+ lnew = g_list_first (li);
+ wrap_windows = FALSE;
+ if (lnew == NULL)
+ break;
+ }
+ else
+ break;
+ }
+
+ child = lnew->data;
+ if (child->window != NULL && !child->group)
+ break;
+ }
+ break;
+
+ case GDK_SCROLL_LEFT:
+ case GDK_SCROLL_RIGHT:
+ default:
+ return TRUE;
+ }
+
+ if (lnew != NULL)
+ {
+ child = lnew->data;
+ tasklist->backend->activate_window (child->window, event->time);
+ }
+
+ return TRUE;
+}
+
+static ToplevelTask *
+toplevel_task_new (TasklistManager *tasklist, gpointer window)
+{
+ const TasklistBackend *backend = tasklist->backend;
+ ToplevelTask *task = g_new0 (ToplevelTask, 1);
+ GtkOrientation orient;
+ GtkWidget *whole_panel_box, *parent_box;
+ int button_space, panel_width;
+
+ if (window == NULL)
+ {
+ g_free (task);
+ return NULL;
+ }
+
+ tasklist->buttons = tasklist->buttons + 1;
+ orient = gtk_orientable_get_orientation (GTK_ORIENTABLE (tasklist->outer_box));
+ task->window = window;
+ task->tasklist = tasklist;
+ task->button = gtk_button_new ();
+ g_signal_connect (task->button, "clicked", G_CALLBACK (toplevel_task_handle_clicked), task);
+
+ task->icon = gtk_image_new_from_icon_name (backend->get_fallback_icon_name (),
+ GTK_ICON_SIZE_MENU);
+
+ task->label = gtk_label_new ("");
+ gtk_label_set_max_width_chars (GTK_LABEL (task->label), TASKLIST_TEXT_MAX_WIDTH);
+ gtk_label_set_ellipsize (GTK_LABEL (task->label), PANGO_ELLIPSIZE_END);
+ gtk_label_set_xalign (GTK_LABEL (task->label), 0.0);
+
+ GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start (GTK_BOX (box), task->icon, FALSE, FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (box), task->label, TRUE, TRUE, 2);
+
+ gtk_container_add (GTK_CONTAINER (task->button), box);
+ gtk_widget_set_name (task->button , "tasklist-button");
+ gtk_widget_show_all (task->button);
+
+ g_object_set_data_full (G_OBJECT (task->button),
+ toplevel_task_key,
+ task,
+ (GDestroyNotify)toplevel_task_disconnected_from_widget);
+
+ g_signal_connect (task->button, "button-press-event",
+ G_CALLBACK (on_toplevel_button_press),
+ tasklist);
+
+ g_signal_connect (task->button, "enter-notify-event",
+ G_CALLBACK (on_toplevel_button_enter_notify),
+ tasklist);
+ g_signal_connect (task->button, "leave-notify-event",
+ G_CALLBACK (on_toplevel_button_leave_notify),
+ tasklist);
+
+ /* mouse scroll to switch windows */
+ gtk_widget_add_events (task->button, GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK);
+ g_signal_connect (task->button, "scroll-event",
+ G_CALLBACK (tasklist_scroll_event), tasklist);
+
+ /* drag-and-drop to reorder task buttons */
+ gtk_drag_source_set (task->button, GDK_BUTTON1_MASK,
+ source_targets, G_N_ELEMENTS (source_targets),
+ GDK_ACTION_MOVE);
+ gtk_drag_dest_set (task->button, GTK_DEST_DEFAULT_DROP,
+ source_targets, G_N_ELEMENTS (source_targets),
+ GDK_ACTION_MOVE);
+ g_signal_connect (task->button, "drag-data-get",
+ G_CALLBACK (tasklist_button_drag_data_get), task);
+ g_signal_connect (task->button, "drag-data-received",
+ G_CALLBACK (tasklist_button_drag_data_received), tasklist);
+ g_signal_connect (task->button, "drag-motion",
+ G_CALLBACK (tasklist_button_drag_motion), tasklist);
+ g_signal_connect (task->button, "drag-begin",
+ G_CALLBACK (tasklist_button_drag_begin), task);
+
+ /* monitor window changes */
+ backend->set_window_tracking (GTK_WIDGET (tasklist->outer_box), window, TRUE);
+
+ /* set initial contents */
+ gtk_label_set_label (GTK_LABEL (task->label), backend->get_window_name (window));
+ update_task_icon (task);
+ update_task_state (task);
+
+ tasklist->tasks = g_list_append (tasklist->tasks, task);
+
+ /* assign to an application group if grouping is enabled */
+ tasklist_assign_to_group (tasklist, task);
+
+ /* Buttons on a vertical panel are not affected by how many are needed
+ * GTK handles compressing contents as needed as the window width tells
+ * GTK how much space to allocate the label and icon. Buttons will use
+ * the full width of a vertical panel without any special attention
+ * so break out here instead of breaking the vertical panel case
+ */
+
+ if (orient == GTK_ORIENTATION_VERTICAL)
+ return task;
+
+ /* On horizontal panels, GTK does not by default limit the width of the tasklist
+ * as it does not run out of space in the window until the entire panel is used,
+ * leaving buttons at full width until then and overflowing all other applets
+ *
+ * Thus we must get the tasklist's allocated width when extra space remains,
+ * which will be most of the distance between the handle and the next applet
+ * From there, we can expand buttons and/or hide elements as needed
+ * For some reason this function always gets called twice, so use half the value of buttons
+ * but do not attempt to adjust the global value as it would get adjusted twice
+ * Since we are adding a button here the true value cannot be zero
+ */
+ whole_panel_box = gtk_widget_get_toplevel(GTK_WIDGET (tasklist->outer_box));
+ parent_box = gtk_widget_get_ancestor(GTK_WIDGET (tasklist->outer_box), GTK_TYPE_BOX);
+ if (gtk_widget_get_allocated_width (parent_box) > 1)
+ {
+ tasklist->tasklist_width = gtk_widget_get_allocated_width (parent_box);
+ }
+ else
+ {
+ tasklist->tasklist_width = MAX (gtk_widget_get_allocated_width (parent_box),
+ tasklist->tasklist_width);
+ }
+
+ panel_width = gtk_widget_get_allocated_width (whole_panel_box);
+
+ /*on startup we get an allocated with of zero, so start with 1/3 the panel width
+ *as a sane default
+ *This may overflow on very crowded panels where the tasklist is less than 1/3ed the
+ *panel witth but will self-correct on opening or closing a few windows
+ */
+
+ if (tasklist->tasklist_width <= 2)
+ tasklist->tasklist_width = panel_width / 3;
+
+ /*Do not allow buttons to equal zero or the division below is a crasher*/
+ tasklist->buttons = MAX (tasklist->buttons, 1);
+
+ /*always allow at least three buttons to fit without adjustment
+ *so short window lists don't overflow
+ */
+ if (tasklist->tasklist_width > 0)
+ {
+ tasklist->full_button_width = MIN (max_button_width, tasklist->tasklist_width / 3);
+ }
+
+ /*Leave a little space so the buttons don't push other applets off the panel*/
+ button_space = tasklist_button_space (tasklist, tasklist->buttons);
+
+ /* iterate over all the buttons*/
+ adjust_buttons (tasklist, button_space, tasklist->buttons, task);
+
+ /*Reset the tasklist width after button adjustments*/
+ if (gtk_widget_get_allocated_width (parent_box) > 1)
+ {
+ tasklist->tasklist_width = gtk_widget_get_allocated_width (parent_box);
+ }
+ else
+ {
+ tasklist->tasklist_width = MAX (gtk_widget_get_allocated_width (parent_box),
+ tasklist->tasklist_width);
+ }
+ return task;
+}
+
+GtkWidget *
+tasklist_core_new (const TasklistBackend *backend)
+{
+ TasklistManager *tasklist;
+
+ g_return_val_if_fail (backend != NULL, NULL);
+
+ tasklist = tasklist_manager_new (backend);
+
+ if (!tasklist)
+ return NULL;
+
+ return tasklist->outer_box;
+}
+
+static TasklistManager *
+tasklist_widget_get_tasklist (GtkWidget *tasklist_widget)
+{
+ return g_object_get_data (G_OBJECT (tasklist_widget), tasklist_manager_key);
+}
+
+void
+tasklist_core_set_orientation (GtkWidget *tasklist_widget, GtkOrientation orient)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+ g_return_if_fail (tasklist);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (tasklist->list), orient);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (tasklist->outer_box), orient);
+}
+
+void
+tasklist_core_set_middle_click_close (GtkWidget *tasklist_widget, gboolean enabled)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+ g_return_if_fail (tasklist);
+ tasklist->middle_click_close = enabled;
+}
+
+void
+tasklist_core_set_scroll_enabled (GtkWidget *tasklist_widget, gboolean enabled)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+ g_return_if_fail (tasklist);
+ tasklist->scroll_enabled = enabled;
+}
+
+void
+tasklist_core_set_include_all_workspaces (GtkWidget *tasklist_widget, gboolean include)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+ g_return_if_fail (tasklist);
+
+ if (tasklist->include_all_workspaces == include)
+ return;
+
+ tasklist->include_all_workspaces = include;
+ tasklist_refresh_visibility (tasklist);
+}
+
+void
+tasklist_core_set_switch_workspace_on_unminimize (GtkWidget *tasklist_widget, gboolean switch_ws)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+ g_return_if_fail (tasklist);
+ tasklist->switch_workspace_on_unminimize = switch_ws;
+}
+
+static void
+tasklist_refresh_visibility (TasklistManager *tasklist)
+{
+ GList *l;
+
+ for (l = tasklist->tasks; l != NULL; l = l->next)
+ update_task_visibility (l->data);
+}
+
+/* Bring the user to the window's workspace before unminimizing it, so that
+ * the window does not appear to vanish off-screen. */
+static void
+tasklist_unminimize (TasklistManager *tasklist, gpointer window)
+{
+ if (tasklist->switch_workspace_on_unminimize &&
+ !tasklist->backend->window_is_on_active_workspace (window))
+ tasklist->backend->window_move_to_workspace (window);
+
+ tasklist->backend->set_window_minimized (window, FALSE);
+}
+
+/* Returns a borrowed (max, min) staircase of widths, terminated by a 0 so
+ * that the tasklist may always be squeezed down to nothing. The caller must
+ * not modify or free it. *n_elements receives the number of integers, which
+ * is always even while there is anything to show. */
+const int *
+tasklist_core_get_size_hint_list (GtkWidget *tasklist_widget, int *n_elements)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+
+ g_return_val_if_fail (tasklist, NULL);
+ g_return_val_if_fail (n_elements != NULL, NULL);
+
+ *n_elements = tasklist->size_hints_len;
+
+ return tasklist->size_hints;
+}
+
+static void
+tasklist_rebuild (TasklistManager *tasklist)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+ GList *t, *l;
+ GList *to_destroy = NULL;
+
+ /* stop tracking window changes while rebuilding */
+ if (tasklist->screen)
+ tasklist->backend->set_screen_tracking (GTK_WIDGET (tasklist->outer_box),
+ tasklist->screen, FALSE);
+
+ /* guard against re-entrant rebuilds: the show/hide of buttons during
+ * the rebuild triggers size-allocate, which would otherwise schedule
+ * another rebuild based on a half-updated grouping state */
+ tasklist->rebuilding = TRUE;
+
+ /* 1. Collect every group button so we can destroy them in one pass
+ * below. The individual task buttons are kept alive: they are the
+ * owners of the window handles the core still has to be able to
+ * ask the backend about after the regroup. */
+ if (tasklist->apps)
+ {
+ g_hash_table_iter_init (&iter, tasklist->apps);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ GroupTask *group = value;
+
+ if (group == NULL)
+ continue;
+
+ if (group->app)
+ tasklist->backend->set_app_tracking (GTK_WIDGET (tasklist->outer_box),
+ group->app, FALSE);
+
+ if (group->button)
+ to_destroy = g_list_prepend (to_destroy, group->button);
+
+ group->members = NULL;
+ }
+
+ for (l = to_destroy; l != NULL; l = l->next)
+ {
+ GtkWidget *btn = GTK_WIDGET (l->data);
+ if (gtk_widget_get_parent (btn))
+ gtk_widget_destroy (btn);
+ }
+ g_list_free (to_destroy);
+
+ g_hash_table_remove_all (tasklist->apps);
+ }
+
+ if (tasklist->window_to_group)
+ g_hash_table_remove_all (tasklist->window_to_group);
+
+ /* 2. Clear each task's grouping state. */
+ for (t = tasklist->tasks; t != NULL; t = t->next)
+ {
+ ToplevelTask *task = t->data;
+
+ if (task)
+ task->group = NULL;
+ }
+
+ /* 3. Show every individual task button again and assign it according to
+ * the current grouping mode. */
+ for (t = tasklist->tasks; t != NULL; t = t->next)
+ {
+ ToplevelTask *task = t->data;
+
+ if (task == NULL || task->button == NULL)
+ continue;
+
+ gtk_widget_show (task->button);
+
+ if (gtk_widget_get_parent (task->button) == NULL)
+ gtk_box_pack_start (GTK_BOX (tasklist->list), task->button, TRUE, TRUE, 0);
+
+ tasklist_assign_to_group (tasklist, task);
+ }
+
+ /* 4. Re-enable screen event tracking */
+ tasklist->backend->set_screen_tracking (GTK_WIDGET (tasklist->outer_box),
+ tasklist->screen, TRUE);
+
+ tasklist->rebuilding = FALSE;
+
+ if (gtk_orientable_get_orientation (GTK_ORIENTABLE (tasklist->outer_box)) != GTK_ORIENTATION_VERTICAL)
+ {
+ GList *children = gtk_container_get_children (GTK_CONTAINER (tasklist->list));
+ guint visible = g_list_length (children);
+ g_list_free (children);
+
+ if (visible > 0)
+ {
+ GtkWidget *parent_box;
+ int button_space;
+
+ parent_box = gtk_widget_get_ancestor (tasklist->outer_box, GTK_TYPE_BOX);
+ tasklist->tasklist_width = MAX (gtk_widget_get_allocated_width (parent_box),
+ tasklist->tasklist_width);
+ button_space = tasklist_button_space (tasklist, visible);
+ adjust_buttons (tasklist, button_space, visible, NULL);
+ }
+ }
+
+ gtk_widget_queue_resize (tasklist->list);
+}
+
+static void
+tasklist_refresh_icon_size (TasklistManager *tasklist)
+{
+ GtkIconSize chosen;
+ gint available;
+
+ if (!tasklist)
+ return;
+
+ /* Deliberately no early return on a non-positive available: the
+ * chooser treats that as "nothing fits" and drops to the smallest
+ * size, which is the right answer for a very thin panel. */
+ available = tasklist_icon_available_space (tasklist);
+ chosen = tasklist_choose_icon_size (available);
+
+ if (chosen == tasklist->icon_size_enum)
+ return;
+
+ tasklist->icon_size_enum = chosen;
+ tasklist_apply_icon_size (tasklist);
+}
+
+static gboolean
+tasklist_theme_change_idle (gpointer data)
+{
+ TasklistManager *tasklist = data;
+
+ tasklist->icon_refresh_idle = 0;
+ tasklist_refresh_icon_size (tasklist);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+tasklist_theme_changed (GtkSettings *settings, GParamSpec *pspec, TasklistManager *tasklist)
+{
+ if (!tasklist || tasklist->icon_refresh_idle != 0)
+ return;
+
+ tasklist->icon_refresh_idle = g_idle_add (tasklist_theme_change_idle, tasklist);
+}
+
+void
+tasklist_core_set_panel_size (GtkWidget *tasklist_widget, gint size)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+
+ g_return_if_fail (tasklist);
+
+ if (size <= 0 || size == tasklist->panel_thickness)
+ return;
+
+ tasklist->panel_thickness = size;
+
+ tasklist_refresh_icon_size (tasklist);
+}
+
+void
+tasklist_core_set_grouping (GtkWidget *tasklist_widget, TasklistGroupingType grouping)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+ g_return_if_fail (tasklist);
+
+ if (tasklist->grouping == grouping)
+ return;
+
+ tasklist->grouping = grouping;
+
+ /* when switching away from auto-grouping, stop the space-based
+ * regrouping so it doesn't fight with the new mode */
+ if (grouping != TASKLIST_AUTO_GROUP)
+ {
+ if (tasklist->auto_grouping_idle != 0)
+ {
+ g_source_remove (tasklist->auto_grouping_idle);
+ tasklist->auto_grouping_idle = 0;
+ }
+ tasklist->auto_grouping = FALSE;
+ tasklist->auto_grouping_applied = FALSE;
+ }
+
+ tasklist_rebuild (tasklist);
+}
+
+/* Callbacks a backend makes when its own signals fire. */
+
+void
+tasklist_core_window_added (GtkWidget *tasklist_widget, gpointer window)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+ ToplevelTask *task;
+
+ g_return_if_fail (tasklist);
+
+ if (!window || tasklist_has_window (tasklist, window))
+ return;
+
+ task = toplevel_task_new (tasklist, window);
+ if (task != NULL)
+ gtk_box_pack_start (GTK_BOX (tasklist->list), task->button, TRUE, TRUE, 0);
+}
+
+void
+tasklist_core_window_removed (GtkWidget *tasklist_widget, gpointer window)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+
+ g_return_if_fail (tasklist);
+
+ if (window)
+ tasklist_remove_window (tasklist, window);
+}
+
+void
+tasklist_core_window_state_changed (GtkWidget *tasklist_widget, gpointer window)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+ GList *l;
+
+ g_return_if_fail (tasklist);
+
+ for (l = tasklist->tasks; l != NULL; l = l->next)
+ {
+ ToplevelTask *task = l->data;
+
+ if (task != NULL && task->window == window)
+ {
+ update_task_state (task);
+ return;
+ }
+ }
+}
+
+void
+tasklist_core_window_name_changed (GtkWidget *tasklist_widget, gpointer window)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+ GList *l;
+
+ g_return_if_fail (tasklist);
+
+ for (l = tasklist->tasks; l != NULL; l = l->next)
+ {
+ ToplevelTask *task = l->data;
+
+ if (task == NULL || task->window != window)
+ continue;
+
+ if (task->label)
+ gtk_label_set_label (GTK_LABEL (task->label),
+ tasklist->backend->get_window_name (task->window));
+
+ /* keep the group button (showing the active window's title) in sync */
+ if (task->group)
+ group_task_update_name (task->group);
+
+ return;
+ }
+}
+
+void
+tasklist_core_window_icon_changed (GtkWidget *tasklist_widget, gpointer window)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+ GList *l;
+
+ g_return_if_fail (tasklist);
+
+ for (l = tasklist->tasks; l != NULL; l = l->next)
+ {
+ ToplevelTask *task = l->data;
+
+ if (task != NULL && task->window == window)
+ {
+ update_task_icon (task);
+ return;
+ }
+ }
+}
+
+void
+tasklist_core_window_application_changed (GtkWidget *tasklist_widget, gpointer window)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+ GList *l;
+
+ g_return_if_fail (tasklist);
+
+ for (l = tasklist->tasks; l != NULL; l = l->next)
+ {
+ ToplevelTask *task = l->data;
+
+ if (task == NULL || task->window != window)
+ continue;
+
+ if (task->group)
+ group_task_remove_window (task->group, task);
+
+ if (tasklist->grouping != TASKLIST_NEVER_GROUP)
+ tasklist_assign_to_group (tasklist, task);
+
+ return;
+ }
+}
+
+void
+tasklist_core_app_icon_changed (GtkWidget *tasklist_widget, gpointer app)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+
+ g_return_if_fail (tasklist);
+
+ group_task_update_icon (tasklist->apps ? g_hash_table_lookup (tasklist->apps, app) : NULL);
+}
+
+void
+tasklist_core_app_name_changed (GtkWidget *tasklist_widget, gpointer app)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+
+ g_return_if_fail (tasklist);
+
+ group_task_update_name (tasklist->apps ? g_hash_table_lookup (tasklist->apps, app) : NULL);
+}
+
+void
+tasklist_core_active_window_changed (GtkWidget *tasklist_widget)
+{
+ TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
+ GList *l;
+
+ g_return_if_fail (tasklist);
+
+ for (l = tasklist->tasks; l != NULL; l = l->next)
+ update_task_state (l->data);
+}
diff --git a/applets/wncklet/wayland-backend.c b/applets/wncklet/wayland-backend.c
index c07434752..da485e2f5 100644
--- a/applets/wncklet/wayland-backend.c
+++ b/applets/wncklet/wayland-backend.c
@@ -20,6 +20,11 @@
* 02110-1301, USA.
*/
+/* This file only implements the windowing-system half of the tasklist. All of
+ * the widget logic lives in tasklist-core.c and is shared with the other
+ * backends.
+ */
+
#include
#ifndef HAVE_WAYLAND
@@ -30,1798 +35,315 @@
#include
#include
+#include "tasklist-backend.h"
#include "wayland-backend.h"
-/*shorter than wnck-tasklist due to common use of larger fonts*/
-#define TASKLIST_TEXT_MAX_WIDTH 16
-
-/* On Wayland there is no window XID, the XfwWindow pointer is used to
- * identify a window in the drag-and-drop payload */
-#define wayland_task_window_get_wid(window) ((gulong) (window))
-
-/*In the future this could be changable from the panel-prefs dialog*/
-static const int max_button_width = 180;
-static const int icon_size = 16;
-int full_button_width;
-static guint buttons, tasklist_width;
-
-typedef struct
-{
- GtkWidget *menu;
- GtkWidget *maximize;
- GtkWidget *minimize;
- GtkWidget *on_top;
- GtkWidget *close;
- GtkWidget *close_all;
-} ContextMenu;
-
-typedef struct _GroupTask GroupTask;
-
-typedef struct
-{
- GtkWidget *list;
- GtkWidget *outer_box;
- ContextMenu *context_menu;
- XfwScreen *screen;
- GList *tasks;
- gboolean scroll_enabled;
- gboolean middle_click_close;
- WaylandTasklistGroupingType grouping;
- gboolean auto_grouping;
- gboolean auto_grouping_applied;
- guint auto_grouping_idle;
- gboolean rebuilding;
- GHashTable *apps;
- GHashTable *window_to_group;
-} TasklistManager;
-
-typedef struct
-{
- GtkWidget *button;
- GtkWidget *icon;
- GtkWidget *label;
- XfwWindow *window;
- gulong state_changed_id;
- gulong name_changed_id;
- gulong icon_changed_id;
- gboolean active;
- gboolean maximized;
- gboolean minimized;
- gboolean fullscreen;
- gboolean urgent;
- TasklistManager *tasklist;
- GroupTask *group;
- gulong application_changed_id;
- gint natural_width;
-} ToplevelTask;
-
-struct _GroupTask
-{
- TasklistManager *tasklist;
- XfwApplication *app;
- GtkWidget *button;
- GtkWidget *box;
- GtkWidget *icon;
- GtkWidget *label;
- GtkWidget *counter_label;
- GList *members;
- guint n_windows;
- gboolean active;
- gboolean minimized;
- gboolean urgent;
-};
-
-static const char *tasklist_manager_key = "tasklist_manager";
-static const char *toplevel_task_key = "toplevel_task";
-static const char *group_task_key = "group_task";
-
-static GtkTargetEntry source_targets[] =
-{
- { "application/x-wnck-window-id", 0, 0 }
-};
-
-static ToplevelTask *toplevel_task_new (TasklistManager *tasklist, XfwWindow *window);
-static gboolean tasklist_scroll_event (GtkWidget *widget, GdkEventScroll *event, TasklistManager *tasklist);
-static void group_task_update (GroupTask *group);
-static void group_task_update_name (GroupTask *group);
-static void group_task_update_name_for (GroupTask *group, ToplevelTask *task);
-static void group_task_add_window (GroupTask *group, ToplevelTask *task);
-static void group_task_remove_window (GroupTask *group, ToplevelTask *task);
-static void tasklist_assign_to_group (TasklistManager *tasklist, ToplevelTask *task);
-static void group_task_child_state_changed (GroupTask *group);
-static void tasklist_rebuild (TasklistManager *tasklist);
-
-static void
-update_task_state (ToplevelTask *task)
-{
- XfwWindowState state;
-
- if (!task || !task->window)
- return;
-
- state = xfw_window_get_state (task->window);
- task->active = (state & XFW_WINDOW_STATE_ACTIVE) != 0;
- task->maximized = (state & XFW_WINDOW_STATE_MAXIMIZED) != 0;
- task->minimized = (state & XFW_WINDOW_STATE_MINIMIZED) != 0;
- task->fullscreen = (state & XFW_WINDOW_STATE_FULLSCREEN) != 0;
- task->urgent = (state & XFW_WINDOW_STATE_URGENT) != 0;
-
- if (task->button)
- {
- if (task->urgent)
- gtk_style_context_add_class (gtk_widget_get_style_context (task->button), "urgent");
- else
- gtk_style_context_remove_class (gtk_widget_get_style_context (task->button), "urgent");
-
- gtk_button_set_relief (GTK_BUTTON (task->button),
- task->active ? GTK_RELIEF_NORMAL : GTK_RELIEF_NONE);
- }
-
- if (task->group)
- group_task_child_state_changed (task->group);
-}
-
-static void
-adjust_buttons (GtkContainer *tasklist_list, int button_space, int buttons, ToplevelTask *task)
-{
- GtkWidget *widget, *button, *box;
-
- /*catch the case of an added button that can be missed
- *Note that button space can come up zero on a first button
- */
- if (buttons < 2)
- {
- if(task)
- {
- gtk_widget_set_size_request (task->button, full_button_width, -1);
- }
- }
-
- if ((task) && (button_space > 0) && (button_space < icon_size * 3))
- {
- gtk_widget_hide (task->icon);
- }
- else if (task)
- {
- gtk_widget_show (task->icon);
- }
-
- if ((task) && (button_space > 0) && (button_space < icon_size))
- {
- gtk_widget_hide (task->label);
- }
- else if (task)
- {
- gtk_widget_show (task->label);
- }
-
- GList* children = gtk_container_get_children (GTK_CONTAINER (tasklist_list));
-
- while (children != NULL)
- {
- button = GTK_WIDGET (children->data);
- box = gtk_bin_get_child (GTK_BIN (button));
-
- /* keep buttons that are hidden as group members hidden,
- * even when there is enough space to show them at full width */
- {
- ToplevelTask *child_task =
- g_object_get_data (G_OBJECT (button), toplevel_task_key);
- if (child_task && child_task->group)
- {
- children = children->next;
- continue;
- }
- }
-
- /* group buttons are shown/hidden solely by the grouping code,
- * never by adjust_buttons (otherwise a hidden group button for a
- * single-window application would be force-shown again) */
- if (g_object_get_data (G_OBJECT (button), group_task_key) != NULL)
- {
- children = children->next;
- continue;
- }
-
- if ((buttons < 2) || (buttons * full_button_width < tasklist_width * 0.75))
- {
- gtk_widget_set_size_request (button, full_button_width, -1);
- gtk_widget_show_all (button);
- return;
- }
- else
- {
- gtk_widget_set_size_request (button, MIN(button_space, full_button_width), -1);
- }
-
- /* if the number of buttons forces width to less than 3x the icon size, hide the icons
- * if the number of buttons forces width to less than the icon size, hide the labels too.
- * This is roughy the same behavior as on x11
- * To find the icon and label we must iterate through the children of the box we packed
- * into the button, there are two of them
- */
-
- GList* contents = gtk_container_get_children (GTK_CONTAINER (box));
- while (contents != NULL)
- {
- widget = GTK_WIDGET (contents->data);
- /*Show or hide the icon*/
- if (GTK_IS_IMAGE (widget))
- {
- if ((button_space < icon_size * 3) && (button_space > 1))
- gtk_widget_hide (widget);
-
- else
- gtk_widget_show (widget);
-
- }
-
- /*Show or hide the label*/
- if (GTK_IS_LABEL (widget))
- {
- if ((button_space < icon_size) && (button_space > 1))
- {
- gtk_widget_hide (widget);
- /*We can go a little wider for empty buttons*/
- gtk_widget_set_size_request (button, tasklist_width / buttons * 0.9, -1);
- if (task)
- gtk_widget_hide (task->label);
-
- }
- else
- {
- gtk_widget_show (widget);
- }
- }
- contents = contents->next;
- }
- children = children->next;
- }
- return;
-}
-
-static void
-window_state_changed (XfwWindow *window, XfwWindowState changed_mask, XfwWindowState new_state, gpointer user_data)
+static gpointer
+wayland_get_screen (void)
{
- ToplevelTask *task = user_data;
- update_task_state (task);
+ return xfw_screen_get_default ();
}
-static void
-window_name_changed (XfwWindow *window, ToplevelTask *task)
+static GList *
+wayland_list_windows (gpointer screen)
{
- if (task->label)
- gtk_label_set_label (GTK_LABEL (task->label), xfw_window_get_name (window));
-
- /* keep the group button (showing the active window's title) in sync */
- if (task->group)
- group_task_update_name (task->group);
+ return xfw_screen_get_windows (screen);
}
-static void
-update_task_icon (ToplevelTask *task)
+static gpointer
+wayland_get_active_window (gpointer screen)
{
- GIcon *icon = NULL;
- XfwApplication *app;
-
- if (!task || !task->window || !task->icon)
- return;
-
- app = xfw_window_get_application (task->window);
- if (app != NULL)
- icon = xfw_application_get_gicon (app);
- if (icon == NULL)
- icon = xfw_window_get_gicon (task->window);
-
- if (icon != NULL)
- gtk_image_set_from_gicon (GTK_IMAGE (task->icon), icon, GTK_ICON_SIZE_MENU);
- else
- gtk_image_set_from_icon_name (GTK_IMAGE (task->icon), "unknown", GTK_ICON_SIZE_MENU);
+ return xfw_screen_get_active_window (screen);
}
-static void
-window_icon_changed (XfwWindow *window, ToplevelTask *task)
+static const gchar *
+wayland_get_window_name (gpointer window)
{
- update_task_icon (task);
+ return xfw_window_get_name (window);
}
static GIcon *
-window_gicon (XfwWindow *window)
-{
- GIcon *icon = NULL;
- XfwApplication *app;
-
- if (!window)
- return NULL;
-
- app = xfw_window_get_application (window);
- if (app != NULL)
- icon = xfw_application_get_gicon (app);
- if (icon == NULL)
- icon = xfw_window_get_gicon (window);
-
- return icon;
-}
-
-static void
-group_menu_window_activate (GtkMenuItem *item, gpointer user_data)
-{
- ToplevelTask *task = user_data;
-
- if (task && task->window)
- xfw_window_activate (task->window, NULL, 0, NULL);
-}
-
-static void
-group_task_update_name (GroupTask *group)
-{
- XfwWindow *active;
- GList *l;
- const gchar *name = NULL;
-
- if (!group || !group->label)
- return;
-
- active = NULL;
- if (group->tasklist && group->tasklist->screen)
- active = xfw_screen_get_active_window (group->tasklist->screen);
-
- for (l = group->members; l != NULL; l = l->next)
- {
- ToplevelTask *task = l->data;
-
- if (!task || !task->window)
- continue;
-
- if (name == NULL)
- name = xfw_window_get_name (task->window);
- if (active != NULL && task->window == active)
- {
- name = xfw_window_get_name (task->window);
- break;
- }
- }
-
- if (name == NULL)
- name = xfw_application_get_name (group->app);
-
- gtk_label_set_label (GTK_LABEL (group->label), name ? name : "");
-}
-
-static void
-group_task_update_name_for (GroupTask *group, ToplevelTask *task)
-{
- const gchar *name;
-
- if (!group || !group->label || !task || !task->window)
- return;
-
- name = xfw_window_get_name (task->window);
- gtk_label_set_label (GTK_LABEL (group->label), name ? name : "");
-}
-
-static void
-group_task_update_icon (GroupTask *group)
-{
- GIcon *icon;
-
- if (!group || !group->icon)
- return;
-
- icon = xfw_application_get_gicon (group->app);
- if (icon != NULL)
- gtk_image_set_from_gicon (GTK_IMAGE (group->icon), icon, GTK_ICON_SIZE_MENU);
- else
- gtk_image_set_from_icon_name (GTK_IMAGE (group->icon), "unknown", GTK_ICON_SIZE_MENU);
-}
-
-static gboolean
-tasklist_should_group (TasklistManager *tasklist, guint n_windows)
-{
- if (tasklist->grouping == WAYLAND_TASKLIST_NEVER_GROUP)
- return FALSE;
- if (tasklist->grouping == WAYLAND_TASKLIST_ALWAYS_GROUP)
- return n_windows >= 2;
- return tasklist->auto_grouping_applied && n_windows >= 2;
-}
-
-static gboolean
-tasklist_buttons_crowded (TasklistManager *tasklist, gint available_width)
-{
- GtkRequisition req;
- GList *l;
- gint needed = 0;
-
- for (l = tasklist->tasks; l != NULL; l = l->next)
- {
- ToplevelTask *task = l->data;
-
- if (task == NULL || task->button == NULL)
- continue;
-
- if (gtk_widget_get_visible (task->button))
- {
- gtk_widget_get_preferred_width (task->button, NULL, &req.width);
- task->natural_width = req.width;
- }
-
- if (task->natural_width <= 0)
- task->natural_width = icon_size + 32;
-
- needed += task->natural_width;
- }
-
- return needed > available_width;
-}
-
-static gboolean
-tasklist_apply_auto_grouping (gpointer data)
-{
- TasklistManager *tasklist = data;
-
- tasklist->auto_grouping_idle = 0;
-
- if (tasklist->auto_grouping == tasklist->auto_grouping_applied)
- return G_SOURCE_REMOVE;
-
- tasklist->auto_grouping_applied = tasklist->auto_grouping;
- tasklist_rebuild (tasklist);
-
- return G_SOURCE_REMOVE;
-}
-
-static void
-tasklist_list_size_allocate (GtkWidget *widget, GdkRectangle *allocation, TasklistManager *tasklist)
+wayland_get_window_gicon (gpointer window)
{
- gboolean crowded;
-
- if (tasklist->grouping != WAYLAND_TASKLIST_AUTO_GROUP)
- {
- tasklist->auto_grouping = FALSE;
- return;
- }
-
- /* don't evaluate crowd state while we are rebuilding, the buttons are
- * half-hidden and the measurement would suggest the wrong thing */
- if (tasklist->rebuilding)
- return;
-
- crowded = tasklist_buttons_crowded (tasklist, allocation->width);
- tasklist->auto_grouping = crowded;
-
- if (crowded != tasklist->auto_grouping_applied && tasklist->auto_grouping_idle == 0)
- tasklist->auto_grouping_idle = g_idle_add (tasklist_apply_auto_grouping, tasklist);
+ return xfw_window_get_gicon (window);
}
-static void
-group_task_update_member_buttons (GroupTask *group)
+static GdkPixbuf *
+wayland_get_window_pixbuf (gpointer window, gint size, gint scale)
{
- GList *l;
- gboolean grouped;
-
- if (!group)
- return;
-
- grouped = tasklist_should_group (group->tasklist, group->n_windows);
-
- for (l = group->members; l != NULL; l = l->next)
- {
- ToplevelTask *task = l->data;
-
- if (task && task->button)
- {
- if (grouped)
- gtk_widget_hide (task->button);
- else if (!gtk_widget_get_visible (task->button))
- gtk_widget_show (task->button);
- }
- }
+ return xfw_window_get_icon (window, size, scale);
}
-static void
-group_task_update (GroupTask *group)
+static TasklistWindowState
+wayland_get_window_state (gpointer window)
{
- gboolean grouped;
-
- if (!group)
- return;
-
- grouped = tasklist_should_group (group->tasklist, group->n_windows);
-
- group_task_update_name (group);
- group_task_update_icon (group);
-
- if (group->counter_label)
- {
- if (grouped)
- {
- gchar *text = g_strdup_printf ("%u", group->n_windows);
- gtk_label_set_label (GTK_LABEL (group->counter_label), text);
- g_free (text);
- gtk_widget_show (group->counter_label);
- }
- else
- {
- gtk_widget_hide (group->counter_label);
- }
- }
-
- if (grouped)
- gtk_widget_show (group->button);
- else
- gtk_widget_hide (group->button);
-}
-
-static void
-group_task_child_state_changed (GroupTask *group)
-{
- GList *l;
- gboolean active = FALSE;
- gboolean urgent = FALSE;
- gboolean minimized = TRUE;
-
- if (!group || !group->button)
- return;
-
- for (l = group->members; l != NULL; l = l->next)
- {
- ToplevelTask *task = l->data;
-
- if (!task)
- continue;
- if (task->active)
- active = TRUE;
- if (task->urgent)
- urgent = TRUE;
- if (!task->minimized)
- minimized = FALSE;
- }
-
- group->active = active;
- group->urgent = urgent;
- group->minimized = minimized;
+ XfwWindowState state;
+ TasklistWindowState result = 0;
- if (urgent)
- gtk_style_context_add_class (gtk_widget_get_style_context (group->button), "urgent");
- else
- gtk_style_context_remove_class (gtk_widget_get_style_context (group->button), "urgent");
+ state = xfw_window_get_state (window);
- gtk_button_set_relief (GTK_BUTTON (group->button),
- active ? GTK_RELIEF_NORMAL : GTK_RELIEF_NONE);
+ if (state & XFW_WINDOW_STATE_ACTIVE)
+ result |= TASKLIST_STATE_ACTIVE;
+ if (state & XFW_WINDOW_STATE_MAXIMIZED)
+ result |= TASKLIST_STATE_MAXIMIZED;
+ if (state & XFW_WINDOW_STATE_MINIMIZED)
+ result |= TASKLIST_STATE_MINIMIZED;
+ if (state & XFW_WINDOW_STATE_FULLSCREEN)
+ result |= TASKLIST_STATE_FULLSCREEN;
+ if (state & XFW_WINDOW_STATE_URGENT)
+ result |= TASKLIST_STATE_URGENT;
- /* the group button shows the active window's title */
- group_task_update_name (group);
+ return result;
}
-static void
-group_task_icon_changed (XfwApplication *app, GroupTask *group)
+static gpointer
+wayland_get_window_application (gpointer window)
{
- group_task_update_icon (group);
+ return xfw_window_get_application (window);
}
-static void
-group_task_name_changed (XfwApplication *app, GParamSpec *pspec, GroupTask *group)
+static gulong
+wayland_get_window_id (gpointer window)
{
- group_task_update_name (group);
+ /* On Wayland there is no window XID, so the handle is used to identify
+ * a window in the drag-and-drop payload */
+ return (gulong) window;
}
-static void
-group_menu_close_all (GtkMenuItem *item, gpointer user_data)
+static const gchar *
+wayland_get_app_name (gpointer app)
{
- GroupTask *group = user_data;
- GList *l;
-
- for (l = group->members; l != NULL; l = l->next)
- {
- ToplevelTask *task = l->data;
-
- if (task && task->window)
- xfw_window_close (task->window, 0, NULL);
- }
+ return xfw_application_get_name (app);
}
-static void
-group_task_menu_show (GroupTask *group, GdkEventButton *event, gboolean right_click)
+static GIcon *
+wayland_get_app_gicon (gpointer app)
{
- GtkWidget *menu;
- GList *l;
-
- if (!group || !group->button)
- return;
-
- (void) right_click;
-
- menu = gtk_menu_new ();
-
- for (l = group->members; l != NULL; l = l->next)
- {
- ToplevelTask *task = l->data;
- GtkWidget *item, *box, *icon, *label;
- GIcon *gicon;
-
- if (!task || !task->window)
- continue;
-
- item = gtk_menu_item_new ();
- box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
- icon = gtk_image_new ();
- gicon = window_gicon (task->window);
- if (gicon != NULL)
- gtk_image_set_from_gicon (GTK_IMAGE (icon), gicon, GTK_ICON_SIZE_MENU);
- else
- gtk_image_set_from_icon_name (GTK_IMAGE (icon), "unknown", GTK_ICON_SIZE_MENU);
-
- label = gtk_label_new (xfw_window_get_name (task->window));
- gtk_label_set_xalign (GTK_LABEL (label), 0.0);
- gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
- gtk_label_set_max_width_chars (GTK_LABEL (label), 24);
-
- gtk_box_pack_start (GTK_BOX (box), icon, FALSE, FALSE, 0);
- gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 0);
- gtk_container_add (GTK_CONTAINER (item), box);
-
- g_signal_connect (item, "activate",
- G_CALLBACK (group_menu_window_activate), task);
-
- gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
- gtk_widget_show_all (item);
- }
-
- /* a visible group button always represents two or more windows, so
- * "Close All Windows" is offered whether the menu was opened by left
- * click (picker) or right click (context menu) */
- {
- GtkWidget *item;
-
- gtk_menu_shell_append (GTK_MENU_SHELL (menu), gtk_separator_menu_item_new ());
- item = gtk_menu_item_new_with_label ("Close All Windows");
- g_signal_connect (item, "activate", G_CALLBACK (group_menu_close_all), group);
- gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
- gtk_widget_show_all (item);
- }
-
- gtk_widget_show (menu);
- gtk_menu_popup_at_widget (GTK_MENU (menu), group->button,
- GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_SOUTH_WEST, (GdkEvent *) event);
+ return xfw_application_get_gicon (app);
}
-static void
-group_task_clicked (GtkButton *button, GroupTask *group)
+static const gchar *
+wayland_get_fallback_icon_name (void)
{
- group_task_menu_show (group, NULL, FALSE);
+ return "unknown";
}
+/* xfw reports a single workspace per window, so the active one is the active
+ * workspace of the group that window's workspace belongs to. */
static gboolean
-on_group_button_press (GtkWidget *button, GdkEvent *event, GroupTask *group)
+wayland_window_is_on_active_workspace (gpointer window)
{
- if (((GdkEventButton*)event)->button == GDK_BUTTON_MIDDLE &&
- group->tasklist->middle_click_close)
- {
- GList *l;
+ XfwWorkspace *workspace, *active;
+ XfwWorkspaceGroup *group;
- for (l = group->members; l != NULL; l = l->next)
- {
- ToplevelTask *task = l->data;
-
- if (task && task->window)
- xfw_window_close (task->window, ((GdkEventButton*)event)->time, NULL);
- }
+ workspace = xfw_window_get_workspace (window);
+ if (workspace == NULL)
return TRUE;
- }
- if (((GdkEventButton*)event)->button == GDK_BUTTON_SECONDARY)
- {
- group_task_menu_show (group, (GdkEventButton*)event, TRUE);
+ group = xfw_workspace_get_workspace_group (workspace);
+ if (group == NULL)
return TRUE;
- }
-
- return FALSE;
-}
-
-static GroupTask *
-group_task_new (TasklistManager *tasklist, XfwApplication *app)
-{
- GroupTask *group;
-
- group = g_new0 (GroupTask, 1);
- group->tasklist = tasklist;
- group->app = g_object_ref (app);
-
- group->button = gtk_button_new ();
- group->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
- group->icon = gtk_image_new_from_icon_name ("unknown", icon_size);
- group->label = gtk_label_new ("");
- group->counter_label = gtk_label_new ("");
-
- gtk_label_set_max_width_chars (GTK_LABEL (group->label), TASKLIST_TEXT_MAX_WIDTH);
- gtk_label_set_ellipsize (GTK_LABEL (group->label), PANGO_ELLIPSIZE_END);
- gtk_label_set_xalign (GTK_LABEL (group->label), 0.0);
-
- gtk_box_pack_start (GTK_BOX (group->box), group->icon, FALSE, FALSE, 6);
- gtk_box_pack_start (GTK_BOX (group->box), group->label, TRUE, TRUE, 2);
- gtk_box_pack_start (GTK_BOX (group->box), group->counter_label, FALSE, FALSE, 4);
-
- gtk_container_add (GTK_CONTAINER (group->button), group->box);
- gtk_widget_set_name (group->button, "tasklist-button");
- gtk_widget_show_all (group->button);
- g_object_set_data (G_OBJECT (group->button), group_task_key, group);
-
- g_signal_connect (group->button, "clicked",
- G_CALLBACK (group_task_clicked), group);
- g_signal_connect (group->button, "button-press-event",
- G_CALLBACK (on_group_button_press), group);
- g_signal_connect (group->button, "scroll-event",
- G_CALLBACK (tasklist_scroll_event), tasklist);
-
- g_signal_connect (app, "icon-changed",
- G_CALLBACK (group_task_icon_changed), group);
- g_signal_connect (app, "notify::name",
- G_CALLBACK (group_task_name_changed), group);
-
- group_task_update_icon (group);
- group_task_update_name (group);
-
- gtk_box_pack_start (GTK_BOX (tasklist->list), group->button, TRUE, TRUE, 0);
- /* hidden until it has more than one window */
- gtk_widget_hide (group->button);
+ active = xfw_workspace_group_get_active_workspace (group);
+ if (active == NULL)
+ return TRUE;
- return group;
+ return xfw_window_is_on_workspace (window, active);
}
static void
-group_task_free (gpointer data)
+wayland_window_move_to_workspace (gpointer window)
{
- GroupTask *group = data;
+ XfwWorkspace *workspace;
+ GError *error = NULL;
- if (!group)
+ workspace = xfw_window_get_workspace (window);
+ if (workspace == NULL)
return;
- if (group->app)
+ if (!xfw_workspace_activate (workspace, &error))
{
- g_signal_handlers_disconnect_by_data (group->app, group);
- g_object_unref (group->app);
+ g_clear_error (&error);
}
-
- group->button = NULL;
-
- g_list_free (group->members);
- g_free (group);
}
static void
-group_task_add_window (GroupTask *group, ToplevelTask *task)
+wayland_activate_window (gpointer window, guint32 user_time)
{
- if (!group || !task || !task->window)
- return;
-
- if (g_list_find (group->members, task) != NULL)
- return;
-
- task->group = group;
- group->members = g_list_append (group->members, task);
- group->n_windows++;
-
- g_hash_table_insert (group->tasklist->window_to_group, task->window, group);
-
- group_task_update (group);
- group_task_update_member_buttons (group);
- group_task_child_state_changed (group);
- group_task_update_name_for (group, task);
+ xfw_window_activate (window, NULL, user_time, NULL);
}
static void
-group_task_remove_window (GroupTask *group, ToplevelTask *task)
+wayland_close_window (gpointer window, guint32 timestamp)
{
- if (!group || !task)
- return;
-
- group->members = g_list_remove (group->members, task);
- if (task->window &&
- g_hash_table_lookup (group->tasklist->window_to_group, task->window) == group)
- g_hash_table_remove (group->tasklist->window_to_group, task->window);
-
- task->group = NULL;
- group->n_windows--;
-
- if (group->n_windows == 0)
- {
- if (group->button)
- {
- GtkWidget *parent = gtk_widget_get_parent (group->button);
- if (parent)
- gtk_container_remove (GTK_CONTAINER (parent), group->button);
- group->button = NULL;
- }
-
- g_hash_table_remove (group->tasklist->apps, group->app);
- return;
- }
-
- group_task_update (group);
- group_task_update_member_buttons (group);
- group_task_child_state_changed (group);
+ xfw_window_close (window, timestamp, NULL);
}
static void
-tasklist_assign_to_group (TasklistManager *tasklist, ToplevelTask *task)
+wayland_set_window_maximized (gpointer window, gboolean maximized)
{
- XfwApplication *app;
- GroupTask *group;
-
- if (tasklist->grouping == WAYLAND_TASKLIST_NEVER_GROUP)
- return;
-
- app = xfw_window_get_application (task->window);
- if (app == NULL)
- return;
-
- group = g_hash_table_lookup (tasklist->apps, app);
- if (group == NULL)
- {
- group = group_task_new (tasklist, app);
- g_hash_table_insert (tasklist->apps, app, group);
- }
-
- group_task_add_window (group, task);
+ xfw_window_set_maximized (window, maximized, NULL);
}
static void
-window_application_changed (XfwWindow *window, GParamSpec *pspec, ToplevelTask *task)
+wayland_set_window_minimized (gpointer window, gboolean minimized)
{
- TasklistManager *tasklist;
-
- if (!task)
- return;
-
- tasklist = task->tasklist;
-
- if (task->group)
- group_task_remove_window (task->group, task);
-
- if (tasklist && tasklist->grouping != WAYLAND_TASKLIST_NEVER_GROUP)
- tasklist_assign_to_group (tasklist, task);
+ xfw_window_set_minimized (window, minimized, NULL);
}
-static void
-menu_on_maximize (GtkMenuItem *item, gpointer user_data)
-{
- ToplevelTask *task = g_object_get_data (G_OBJECT (item), toplevel_task_key);
- if (task && task->window)
- xfw_window_set_maximized (task->window, !task->maximized, NULL);
-}
+/* The core widget is passed in as the signal user data, so that the core can
+ * stop every handler it owns with a single
+ * g_signal_handlers_disconnect_by_data() on itself. */
static void
-menu_on_minimize (GtkMenuItem *item, gpointer user_data)
+wayland_screen_window_opened (XfwScreen *screen, XfwWindow *window, gpointer core)
{
- ToplevelTask *task = g_object_get_data (G_OBJECT (item), toplevel_task_key);
- if (task && task->window)
- xfw_window_set_minimized (task->window, !task->minimized, NULL);
+ tasklist_core_window_added (GTK_WIDGET (core), window);
}
static void
-menu_on_close (GtkMenuItem *item, gpointer user_data)
+wayland_screen_window_closed (XfwScreen *screen, XfwWindow *window, gpointer core)
{
- ToplevelTask *task = g_object_get_data (G_OBJECT (item), toplevel_task_key);
- if (task && task->window)
- xfw_window_close (task->window, 0, NULL);
+ tasklist_core_window_removed (GTK_WIDGET (core), window);
}
static void
-menu_on_close_all (GtkMenuItem *item, gpointer user_data)
-{
- ToplevelTask *task = g_object_get_data (G_OBJECT (item), toplevel_task_key);
- GroupTask *group;
- GList *l;
-
- if (!task)
- return;
-
- group = task->group;
- if (!group)
- {
- if (task->window)
- xfw_window_close (task->window, 0, NULL);
- return;
- }
-
- for (l = group->members; l != NULL; l = l->next)
- {
- ToplevelTask *member = l->data;
-
- if (member && member->window)
- xfw_window_close (member->window, 0, NULL);
- }
-}
-
-static ContextMenu *
-context_menu_new ()
-{
- ContextMenu *menu = g_new0 (ContextMenu, 1);
- menu->menu = gtk_menu_new ();
- menu->maximize = gtk_menu_item_new ();
- menu->minimize = gtk_menu_item_new ();
- menu->on_top = gtk_check_menu_item_new_with_label ("Always On Top");
- menu->close = gtk_menu_item_new_with_label ("Close");
- menu->close_all = gtk_menu_item_new_with_label ("Close All Windows");
-
- gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), menu->maximize);
- gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), menu->minimize);
- gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), gtk_separator_menu_item_new ());
- gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), menu->on_top);
- gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), gtk_separator_menu_item_new ());
- gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), menu->close);
- gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), menu->close_all);
-
- gtk_widget_show_all (menu->menu);
-
- g_signal_connect (menu->maximize, "activate", G_CALLBACK (menu_on_maximize), NULL);
- g_signal_connect (menu->minimize, "activate", G_CALLBACK (menu_on_minimize), NULL);
- g_signal_connect (menu->close, "activate", G_CALLBACK (menu_on_close), NULL);
- g_signal_connect (menu->close_all, "activate", G_CALLBACK (menu_on_close_all), NULL);
- gtk_widget_set_sensitive (menu->on_top, FALSE);
- return menu;
-}
-
-static gboolean
-tasklist_has_window (TasklistManager *tasklist, XfwWindow *window)
+wayland_screen_active_window_changed (XfwScreen *screen,
+ XfwWindow *previous_window,
+ gpointer core)
{
- GList *l;
-
- for (l = tasklist->tasks; l != NULL; l = l->next)
- {
- ToplevelTask *task = l->data;
- if (task->window == window)
- return TRUE;
- }
- return FALSE;
+ tasklist_core_active_window_changed (GTK_WIDGET (core));
}
static void
-screen_window_opened (XfwScreen *screen, XfwWindow *window, TasklistManager *tasklist)
+wayland_window_state_changed (XfwWindow *window,
+ XfwWindowState changed_mask,
+ XfwWindowState new_state,
+ gpointer core)
{
- ToplevelTask *task;
-
- if (!window || tasklist_has_window (tasklist, window))
- return;
-
- task = toplevel_task_new (tasklist, window);
- gtk_box_pack_start (GTK_BOX (tasklist->list), task->button, TRUE, TRUE, 0);
+ tasklist_core_window_state_changed (GTK_WIDGET (core), window);
}
static void
-screen_window_closed (XfwScreen *screen, XfwWindow *window, TasklistManager *tasklist)
+wayland_window_name_changed (XfwWindow *window, gpointer core)
{
- GList *l;
-
- for (l = tasklist->tasks; l != NULL; l = l->next)
- {
- ToplevelTask *task = l->data;
- if (task->window == window)
- {
- GtkOrientation orient;
- GtkWidget *outer_box, *parent_box;
- int button_space;
-
- buttons = buttons -1;
-
- /* remove the window from its group (if any) before destroying the button */
- if (task->group)
- group_task_remove_window (task->group, task);
-
- outer_box = tasklist->outer_box;
- /* removes the task from tasklist->tasks and destroys the button */
- tasklist->tasks = g_list_remove (tasklist->tasks, task);
- gtk_widget_destroy (task->button);
-
- if (buttons == 0)
- return;
-
- /* We don't need to modify button size on a vertical panel*/
- orient = gtk_orientable_get_orientation (GTK_ORIENTABLE (outer_box));
- if (orient == GTK_ORIENTATION_VERTICAL)
- return;
-
- /*Get the box the tasklist outer box sits in
- *and leave a little space so the buttons don't push other applets off the panel
- */
- parent_box = gtk_widget_get_ancestor ((outer_box), GTK_TYPE_BOX);
- tasklist_width = MAX(gtk_widget_get_allocated_width (parent_box), tasklist_width) ;
- button_space = (tasklist_width / buttons) * 0.75;
- button_space = MIN(button_space, full_button_width);
- adjust_buttons (GTK_CONTAINER (tasklist->list), button_space, buttons, NULL);
-
- return;
- }
- }
+ tasklist_core_window_name_changed (GTK_WIDGET (core), window);
}
static void
-screen_active_window_changed (XfwScreen *screen, XfwWindow *previous_window, gpointer user_data)
+wayland_window_icon_changed (XfwWindow *window, gpointer core)
{
- TasklistManager *tasklist = user_data;
- GList *l;
-
- for (l = tasklist->tasks; l != NULL; l = l->next)
- update_task_state (l->data);
+ tasklist_core_window_icon_changed (GTK_WIDGET (core), window);
}
static void
-tasklist_manager_disconnected_from_widget (TasklistManager *tasklist)
-{
- if (tasklist->auto_grouping_idle != 0)
- {
- g_source_remove (tasklist->auto_grouping_idle);
- tasklist->auto_grouping_idle = 0;
- }
-
- if (tasklist->list)
- {
- GList *children = gtk_container_get_children (GTK_CONTAINER (tasklist->list));
- for (GList *iter = children; iter != NULL; iter = g_list_next (iter))
- gtk_widget_destroy (GTK_WIDGET (iter->data));
- g_list_free (children);
- tasklist->list = NULL;
- }
-
- /* free any remaining tasks not removed by window-closed;
- * toplevel_task_disconnected_from_widget (GDestroyNotify) handles freeing
- * as buttons finalize, so just disconnect signal handlers here for safety */
- {
- GList *t;
- for (t = tasklist->tasks; t != NULL; t = t->next)
- {
- ToplevelTask *task = t->data;
- if (task->window)
- {
- g_signal_handlers_disconnect_by_data (task->window, task);
- g_object_unref (task->window);
- }
- }
- }
-
- if (tasklist->apps)
- {
- GHashTableIter iter;
- gpointer key, value;
-
- g_hash_table_iter_init (&iter, tasklist->apps);
- while (g_hash_table_iter_next (&iter, &key, &value))
- {
- GroupTask *g = value;
- if (g && g->app)
- g_signal_handlers_disconnect_by_data (g->app, g);
- }
-
- g_hash_table_destroy (tasklist->apps);
- tasklist->apps = NULL;
- }
-
- if (tasklist->window_to_group)
- {
- g_hash_table_destroy (tasklist->window_to_group);
- tasklist->window_to_group = NULL;
- }
-
- if (tasklist->outer_box)
- tasklist->outer_box = NULL;
-
- if (tasklist->screen)
- {
- g_signal_handlers_disconnect_by_data (tasklist->screen, tasklist);
- g_object_unref (tasklist->screen);
- tasklist->screen = NULL;
- }
-
- if (tasklist->context_menu)
- {
- gtk_widget_destroy (tasklist->context_menu->menu);
- g_free (tasklist->context_menu);
- tasklist->context_menu = NULL;
- }
-
- g_free (tasklist);
-}
-
-static TasklistManager *
-tasklist_manager_new (void)
+wayland_window_application_changed (XfwWindow *window,
+ GParamSpec *pspec,
+ gpointer core)
{
- TasklistManager *tasklist;
- GList *windows, *l;
- XfwScreen *screen = xfw_screen_get_default ();
-
- if (screen == NULL)
- return NULL;
-
- tasklist = g_new0 (TasklistManager, 1);
- tasklist->screen = screen;
- tasklist->list = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
- gtk_box_set_homogeneous (GTK_BOX (tasklist->list), TRUE);
- tasklist->outer_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
- gtk_box_pack_start (GTK_BOX (tasklist->outer_box), tasklist->list, FALSE, FALSE, 0);
- gtk_widget_show (tasklist->list);
- g_object_set_data_full (G_OBJECT (tasklist->outer_box),
- tasklist_manager_key,
- tasklist,
- (GDestroyNotify)tasklist_manager_disconnected_from_widget);
- tasklist->context_menu = context_menu_new ();
-
- tasklist->apps = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
- (GDestroyNotify) group_task_free);
- tasklist->window_to_group = g_hash_table_new (g_direct_hash, g_direct_equal);
-
- /* mouse scroll to switch windows */
- gtk_widget_add_events (tasklist->list, GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK);
- g_signal_connect (tasklist->list, "scroll-event",
- G_CALLBACK (tasklist_scroll_event), tasklist);
- /* space-based auto-grouping */
- g_signal_connect (tasklist->list, "size-allocate",
- G_CALLBACK (tasklist_list_size_allocate), tasklist);
-
- /* add all existing windows on this screen */
- windows = xfw_screen_get_windows (screen);
- for (l = windows; l != NULL; l = l->next)
- {
- XfwWindow *window = l->data;
- ToplevelTask *task = toplevel_task_new (tasklist, window);
- if (task != NULL)
- gtk_box_pack_start (GTK_BOX (tasklist->list), task->button, TRUE, TRUE, 0);
- }
-
- /* monitor window changes */
- g_signal_connect (screen, "window-opened",
- G_CALLBACK (screen_window_opened), tasklist);
- g_signal_connect (screen, "window-closed",
- G_CALLBACK (screen_window_closed), tasklist);
- g_signal_connect (screen, "active-window-changed",
- G_CALLBACK (screen_active_window_changed), tasklist);
-
- return tasklist;
+ tasklist_core_window_application_changed (GTK_WIDGET (core), window);
}
static void
-toplevel_task_disconnected_from_widget (ToplevelTask *task)
-{
- if (task->window)
- {
- if (task->state_changed_id)
- g_signal_handler_disconnect (task->window, task->state_changed_id);
- if (task->name_changed_id)
- g_signal_handler_disconnect (task->window, task->name_changed_id);
- if (task->icon_changed_id)
- g_signal_handler_disconnect (task->window, task->icon_changed_id);
- if (task->application_changed_id)
- g_signal_handler_disconnect (task->window, task->application_changed_id);
- g_object_unref (task->window);
- }
-
- if (task->tasklist)
- task->tasklist->tasks = g_list_remove (task->tasklist->tasks, task);
-
- task->button = NULL;
- task->icon = NULL;
- task->label = NULL;
- task->window = NULL;
-
- g_free (task);
-}
-
-static gboolean
-tasklist_horizontal (TasklistManager *tasklist)
+wayland_app_icon_changed (XfwApplication *app, gpointer core)
{
- return gtk_orientable_get_orientation (GTK_ORIENTABLE (tasklist->list)) == GTK_ORIENTATION_HORIZONTAL;
+ tasklist_core_app_icon_changed (GTK_WIDGET (core), app);
}
static void
-tasklist_button_drag_data_get (GtkWidget *widget,
- GdkDragContext *context,
- GtkSelectionData *selection_data,
- guint info,
- guint time,
- ToplevelTask *task)
+wayland_app_name_changed (XfwApplication *app, GParamSpec *pspec, gpointer core)
{
- gulong wid = wayland_task_window_get_wid (task->window);
- gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data),
- 8, (guchar *) &wid, sizeof (wid));
+ tasklist_core_app_name_changed (GTK_WIDGET (core), app);
}
static void
-tasklist_button_drag_begin (GtkWidget *widget,
- GdkDragContext *context,
- ToplevelTask *task)
+wayland_set_screen_tracking (GtkWidget *core, gpointer screen, gboolean track)
{
- GdkPixbuf *icon;
- gint scale;
-
- if (task->window)
+ if (track)
{
- scale = gtk_widget_get_scale_factor (widget);
- icon = xfw_window_get_icon (task->window, icon_size, scale);
- if (icon != NULL)
- gtk_drag_set_icon_pixbuf (context, icon, 0, 0);
+ g_signal_connect (screen, "window-opened",
+ G_CALLBACK (wayland_screen_window_opened), core);
+ g_signal_connect (screen, "window-closed",
+ G_CALLBACK (wayland_screen_window_closed), core);
+ g_signal_connect (screen, "active-window-changed",
+ G_CALLBACK (wayland_screen_active_window_changed), core);
}
-}
-
-static gboolean
-tasklist_button_drag_motion (GtkWidget *widget,
- GdkDragContext *context,
- gint x, gint y, guint time,
- TasklistManager *tasklist)
-{
- GtkWidget *source_widget;
- ToplevelTask *source, *target;
- GList *source_node, *target_node;
- GtkAllocation allocation;
- gboolean second_half;
-
- source_widget = gtk_drag_get_source_widget (context);
- if (source_widget == NULL)
- return FALSE;
-
- /* only reorder among our own tasklist buttons */
- if (gtk_widget_get_parent (source_widget) != GTK_WIDGET (tasklist->list))
- return FALSE;
-
- source = g_object_get_data (G_OBJECT (source_widget), toplevel_task_key);
- target = g_object_get_data (G_OBJECT (widget), toplevel_task_key);
- if (source == NULL || target == NULL || source == target)
- return FALSE;
-
- /* drop on the right/bottom half -> insert after the target button */
- gtk_widget_get_allocation (widget, &allocation);
- second_half = tasklist_horizontal (tasklist)
- ? x >= allocation.width / 2
- : y >= allocation.height / 2;
-
- source_node = g_list_find (tasklist->tasks, source);
- target_node = g_list_find (tasklist->tasks, target);
- if (source_node == NULL || target_node == NULL)
- return FALSE;
- if (second_half)
- target_node = g_list_next (target_node);
-
- /* no change needed if already at the insertion point */
- if (source_node == target_node || g_list_next (source_node) == target_node)
+ else
{
- gdk_drag_status (context, GDK_ACTION_MOVE, time);
- return TRUE;
+ g_signal_handlers_disconnect_by_data (screen, core);
}
-
- /* live-reorder the list and the widgets */
- tasklist->tasks = g_list_remove (tasklist->tasks, source);
- tasklist->tasks = g_list_insert_before (tasklist->tasks, target_node, source);
- gtk_box_reorder_child (GTK_BOX (tasklist->list), source->button,
- g_list_index (tasklist->tasks, source));
- gtk_widget_queue_resize (tasklist->list);
-
- gdk_drag_status (context, GDK_ACTION_MOVE, time);
- return TRUE;
-}
-
-static void
-tasklist_button_drag_data_received (GtkWidget *widget,
- GdkDragContext *context,
- gint x, gint y,
- GtkSelectionData *selection_data,
- guint info,
- guint time,
- TasklistManager *tasklist)
-{
- /* the list has already been reordered live by drag-motion, so
- * nothing left to do here */
}
static void
-toplevel_task_handle_clicked (GtkButton *button, ToplevelTask *task)
+wayland_set_window_tracking (GtkWidget *core, gpointer window, gboolean track)
{
- if (task->window)
+ if (track)
{
- if (task->active)
- {
- xfw_window_set_minimized (task->window, TRUE, NULL);
- }
- else
- {
- xfw_window_activate (task->window, NULL, 0, NULL);
- }
- }
-}
-
-static gboolean on_toplevel_button_press (GtkWidget *button, GdkEvent *event, TasklistManager *tasklist)
-{
- /* Assume event is a button press */
-
- if (((GdkEventButton*)event)->button == GDK_BUTTON_MIDDLE &&
- tasklist->middle_click_close)
- {
- ToplevelTask *task = g_object_get_data (G_OBJECT (button), toplevel_task_key);
- if (task && task->window)
- xfw_window_close (task->window, ((GdkEventButton*)event)->time, NULL);
- return TRUE;
- }
-
- if (((GdkEventButton*)event)->button == GDK_BUTTON_SECONDARY)
- {
- ContextMenu *menu = tasklist->context_menu;
- ToplevelTask *task = g_object_get_data (G_OBJECT (button), toplevel_task_key);
-
- g_object_set_data (G_OBJECT (menu->maximize), toplevel_task_key, task);
- g_object_set_data (G_OBJECT (menu->minimize), toplevel_task_key, task);
- g_object_set_data (G_OBJECT (menu->close), toplevel_task_key, task);
- g_object_set_data (G_OBJECT (menu->close_all), toplevel_task_key, task);
-
- /* offer "Close All Windows" whenever grouping is enabled, so the
- * option exists in Auto mode even while the windows are shown as
- * individual buttons (and not just on the group button) */
- gtk_widget_set_visible (menu->close_all,
- tasklist->grouping != WAYLAND_TASKLIST_NEVER_GROUP);
-
- gtk_menu_item_set_label (GTK_MENU_ITEM (menu->minimize),
- task->minimized ? "Unminimize" : "Minimize");
- gtk_menu_item_set_label (GTK_MENU_ITEM (menu->maximize),
- task->maximized ? "Unmaximize" : "Maximize");
-
- gtk_menu_popup_at_widget (GTK_MENU (menu->menu), button,
- GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_SOUTH_WEST, event);
- return TRUE;
+ g_signal_connect (window, "state-changed",
+ G_CALLBACK (wayland_window_state_changed), core);
+ g_signal_connect (window, "name-changed",
+ G_CALLBACK (wayland_window_name_changed), core);
+ g_signal_connect (window, "icon-changed",
+ G_CALLBACK (wayland_window_icon_changed), core);
+ g_signal_connect (window, "notify::application",
+ G_CALLBACK (wayland_window_application_changed), core);
}
else
{
- return FALSE;
+ g_signal_handlers_disconnect_by_data (window, core);
}
}
-static gboolean
-tasklist_scroll_event (GtkWidget *widget, GdkEventScroll *event, TasklistManager *tasklist)
+static void
+wayland_set_app_tracking (GtkWidget *core, gpointer app, gboolean track)
{
- ToplevelTask *child;
- GList *li, *lnew = NULL;
- GdkScrollDirection direction;
- gboolean wrap_windows = TRUE;
-
- if (!tasklist->scroll_enabled)
- return TRUE;
-
- /* get the current active window button */
- for (li = tasklist->tasks; li != NULL; li = li->next)
+ if (track)
{
- child = li->data;
- if (child->window && !child->group && xfw_window_is_active (child->window))
- break;
+ g_signal_connect (app, "icon-changed",
+ G_CALLBACK (wayland_app_icon_changed), core);
+ g_signal_connect (app, "notify::name",
+ G_CALLBACK (wayland_app_name_changed), core);
}
-
- if (li == NULL)
- return TRUE;
-
- if (event->direction != GDK_SCROLL_SMOOTH)
- direction = event->direction;
- else if (event->delta_y < 0)
- direction = GDK_SCROLL_UP;
- else if (event->delta_y > 0)
- direction = GDK_SCROLL_DOWN;
- else if (event->delta_x < 0)
- direction = GDK_SCROLL_LEFT;
- else if (event->delta_x > 0)
- direction = GDK_SCROLL_RIGHT;
else
- return TRUE;
-
- switch (direction)
- {
- case GDK_SCROLL_UP:
- /* find the previous button on the tasklist */
- for (lnew = g_list_previous (li);; lnew = lnew->prev)
- {
- if (lnew == NULL)
- {
- if (wrap_windows)
- {
- lnew = g_list_last (li);
- wrap_windows = FALSE;
- if (lnew == NULL)
- break;
- }
- else
- break;
- }
-
- child = lnew->data;
- if (child->window != NULL && !child->group)
- break;
- }
- break;
-
- case GDK_SCROLL_DOWN:
- /* find the next button on the tasklist */
- for (lnew = g_list_next (li);; lnew = lnew->next)
- {
- if (lnew == NULL)
- {
- if (wrap_windows)
- {
- lnew = g_list_first (li);
- wrap_windows = FALSE;
- if (lnew == NULL)
- break;
- }
- else
- break;
- }
-
- child = lnew->data;
- if (child->window != NULL && !child->group)
- break;
- }
- break;
-
- case GDK_SCROLL_LEFT:
- case GDK_SCROLL_RIGHT:
- default:
- return TRUE;
- }
-
- if (lnew != NULL)
{
- child = lnew->data;
- xfw_window_activate (child->window, NULL, event->time, NULL);
- }
-
- return TRUE;
-}
+ g_signal_handlers_disconnect_by_data (app, core);
+ }
+}
+
+static const TasklistBackend wayland_backend = {
+ .get_screen = wayland_get_screen,
+ .list_windows = wayland_list_windows,
+ .get_active_window = wayland_get_active_window,
+ .get_window_name = wayland_get_window_name,
+ .get_window_gicon = wayland_get_window_gicon,
+ .get_window_pixbuf = wayland_get_window_pixbuf,
+ .get_window_state = wayland_get_window_state,
+ .get_window_application = wayland_get_window_application,
+ .get_window_id = wayland_get_window_id,
+ .get_app_name = wayland_get_app_name,
+ .get_app_gicon = wayland_get_app_gicon,
+ .get_fallback_icon_name = wayland_get_fallback_icon_name,
+ .window_is_on_active_workspace = wayland_window_is_on_active_workspace,
+ .window_move_to_workspace = wayland_window_move_to_workspace,
+ .activate_window = wayland_activate_window,
+ .close_window = wayland_close_window,
+ .set_window_maximized = wayland_set_window_maximized,
+ .set_window_minimized = wayland_set_window_minimized,
+ .set_screen_tracking = wayland_set_screen_tracking,
+ .set_window_tracking = wayland_set_window_tracking,
+ .set_app_tracking = wayland_set_app_tracking,
+};
-static ToplevelTask *
-toplevel_task_new (TasklistManager *tasklist, XfwWindow *window)
+const TasklistBackend *
+wayland_tasklist_backend (void)
{
- ToplevelTask *task = g_new0 (ToplevelTask, 1);
- GtkOrientation orient;
- GtkWidget *whole_panel_box, *parent_box;
- int button_space, panel_width;
+ GdkDisplay *display;
- if (window == NULL)
- {
- g_free (task);
+ display = gdk_display_get_default ();
+ if (display == NULL || !GDK_IS_WAYLAND_DISPLAY (display))
return NULL;
- }
-
- buttons = buttons + 1;
- orient = gtk_orientable_get_orientation (GTK_ORIENTABLE (tasklist->outer_box));
- task->window = g_object_ref (window);
- task->tasklist = tasklist;
- task->button = gtk_button_new ();
- g_signal_connect (task->button, "clicked", G_CALLBACK (toplevel_task_handle_clicked), task);
-
- task->icon = gtk_image_new_from_icon_name ("unknown", icon_size);
-
- task->label = gtk_label_new ("");
- gtk_label_set_max_width_chars (GTK_LABEL (task->label), TASKLIST_TEXT_MAX_WIDTH);
- gtk_label_set_ellipsize (GTK_LABEL (task->label), PANGO_ELLIPSIZE_END);
- gtk_label_set_xalign (GTK_LABEL (task->label), 0.0);
-
- GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
- gtk_box_pack_start (GTK_BOX (box), task->icon, FALSE, FALSE, 6);
- gtk_box_pack_start (GTK_BOX (box), task->label, TRUE, TRUE, 2);
-
- gtk_container_add (GTK_CONTAINER (task->button), box);
- gtk_widget_set_name (task->button , "tasklist-button");
- gtk_widget_show_all (task->button);
-
- g_object_set_data_full (G_OBJECT (task->button),
- toplevel_task_key,
- task,
- (GDestroyNotify)toplevel_task_disconnected_from_widget);
-
- g_signal_connect (task->button, "button-press-event",
- G_CALLBACK (on_toplevel_button_press),
- tasklist);
-
- /* mouse scroll to switch windows */
- gtk_widget_add_events (task->button, GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK);
- g_signal_connect (task->button, "scroll-event",
- G_CALLBACK (tasklist_scroll_event), tasklist);
-
- /* drag-and-drop to reorder task buttons */
- gtk_drag_source_set (task->button, GDK_BUTTON1_MASK,
- source_targets, G_N_ELEMENTS (source_targets),
- GDK_ACTION_MOVE);
- gtk_drag_dest_set (task->button, GTK_DEST_DEFAULT_DROP,
- source_targets, G_N_ELEMENTS (source_targets),
- GDK_ACTION_MOVE);
- g_signal_connect (task->button, "drag-data-get",
- G_CALLBACK (tasklist_button_drag_data_get), task);
- g_signal_connect (task->button, "drag-data-received",
- G_CALLBACK (tasklist_button_drag_data_received), tasklist);
- g_signal_connect (task->button, "drag-motion",
- G_CALLBACK (tasklist_button_drag_motion), tasklist);
- g_signal_connect (task->button, "drag-begin",
- G_CALLBACK (tasklist_button_drag_begin), task);
-
- /* monitor window changes */
- task->state_changed_id = g_signal_connect (window, "state-changed",
- G_CALLBACK (window_state_changed), task);
- task->name_changed_id = g_signal_connect (window, "name-changed",
- G_CALLBACK (window_name_changed), task);
- task->icon_changed_id = g_signal_connect (window, "icon-changed",
- G_CALLBACK (window_icon_changed), task);
- task->application_changed_id = g_signal_connect (window, "notify::application",
- G_CALLBACK (window_application_changed), task);
-
- /* set initial contents */
- gtk_label_set_label (GTK_LABEL (task->label), xfw_window_get_name (window));
- update_task_icon (task);
- update_task_state (task);
-
- tasklist->tasks = g_list_append (tasklist->tasks, task);
-
- /* assign to an application group if grouping is enabled */
- tasklist_assign_to_group (tasklist, task);
-
- /* Buttons on a vertical panel are not affected by how many are needed
- * GTK handles compressing contents as needed as the window width tells
- * GTK how much space to allocate the label and icon. Buttons will use
- * the full width of a vertical panel without any special attention
- * so break out here instead of breaking the vertical panel case
- */
-
- if (orient == GTK_ORIENTATION_VERTICAL)
- return task;
-
- /* On horizontal panels, GTK does not by default limit the width of the tasklist
- * as it does not run out of space in the window until the entire panel is used,
- * leaving buttons at full width until then and overflowing all other applets
- *
- * Thus we must get the tasklist's allocated width when extra space remains,
- * which will be most of the distance between the handle and the next applet
- * From there, we can expand buttons and/or hide elements as needed
- * For some reason this function always gets called twice, so use half the value of buttons
- * but do not attempt to adjust the global value as it would get adjusted twice
- * Since we are adding a button here the true value cannot be zero
- */
- whole_panel_box = gtk_widget_get_toplevel(GTK_WIDGET (tasklist->outer_box));
- parent_box = gtk_widget_get_ancestor(GTK_WIDGET (tasklist->outer_box), GTK_TYPE_BOX);
- if (gtk_widget_get_allocated_width (parent_box) > 1)
- {
- tasklist_width = gtk_widget_get_allocated_width (parent_box);
- }
- else
- {
- tasklist_width = MAX(gtk_widget_get_allocated_width (parent_box), tasklist_width);
- }
-
- panel_width = gtk_widget_get_allocated_width (whole_panel_box);
-
- /*on startup we get an allocated with of zero, so start with 1/3 the panel width
- *as a sane default
- *This may overflow on very crowded panels where the tasklist is less than 1/3ed the
- *panel witth but will self-correct on opening or closing a few windows
- */
-
- if (tasklist_width <= 2)
- tasklist_width = panel_width / 3;
-
- /*Do not allow buttons to equal zero or the division below is a crasher*/
- buttons = MAX ((buttons), 1);
-
- /*always allow at least three buttons to fit without adjustment
- *so short window lists don't overflow
- */
- if (tasklist_width > 0)
- {
- full_button_width = MIN(max_button_width, tasklist_width / 3);
- }
-
- /*Leave a little space so the buttons don't push other applets off the panel*/
- button_space = (tasklist_width / buttons) * 0.75;
- button_space = MIN(button_space, full_button_width);
-
- /* iterate over all the buttons*/
- adjust_buttons (GTK_CONTAINER (tasklist->list), button_space, buttons, task);
-
- /*Reset the tasklist width after button adjustments*/
- if (gtk_widget_get_allocated_width (parent_box) > 1)
- {
- tasklist_width = gtk_widget_get_allocated_width (parent_box);
- }
- else
- {
- tasklist_width = MAX(gtk_widget_get_allocated_width (parent_box), tasklist_width);
- }
- return task;
-}
-
-static gboolean
-wayland_tasklist_init_if_needed (void)
-{
- GdkDisplay *gdk_display;
-
- gdk_display = gdk_display_get_default ();
- g_return_val_if_fail (gdk_display, FALSE);
- g_return_val_if_fail (GDK_IS_WAYLAND_DISPLAY (gdk_display), FALSE);
xfw_set_client_type (XFW_CLIENT_TYPE_PAGER);
- return TRUE;
-}
-
-GtkWidget*
-wayland_tasklist_new ()
-{
- if (!wayland_tasklist_init_if_needed ())
- return gtk_label_new ("Shell does not support WLR Foreign Toplevel Control");
-
- TasklistManager *tasklist = tasklist_manager_new ();
-
- if (!tasklist)
- return gtk_label_new ("Shell does not support WLR Foreign Toplevel Control");
- return tasklist->outer_box;
-}
-
-static TasklistManager *
-tasklist_widget_get_tasklist (GtkWidget* tasklist_widget)
-{
- return g_object_get_data (G_OBJECT (tasklist_widget), tasklist_manager_key);
-}
-
-void
-wayland_tasklist_set_orientation (GtkWidget* tasklist_widget, GtkOrientation orient)
-{
- TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
- g_return_if_fail(tasklist);
- gtk_orientable_set_orientation (GTK_ORIENTABLE (tasklist->list), orient);
- gtk_orientable_set_orientation (GTK_ORIENTABLE (tasklist->outer_box), orient);
-}
-
-void
-wayland_tasklist_set_middle_click_close (GtkWidget *tasklist_widget, gboolean enabled)
-{
- TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
- g_return_if_fail (tasklist);
- tasklist->middle_click_close = enabled;
-}
-
-void
-wayland_tasklist_set_scroll_enabled (GtkWidget *tasklist_widget, gboolean enabled)
-{
- TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
- g_return_if_fail (tasklist);
- tasklist->scroll_enabled = enabled;
-}
-
-static void
-tasklist_rebuild (TasklistManager *tasklist)
-{
- GHashTableIter iter;
- gpointer key, value;
- GList *t, *l;
- GList *to_destroy = NULL;
-
- /* stop tracking window changes while rebuilding */
- if (tasklist->screen)
- g_signal_handlers_disconnect_by_data (tasklist->screen, tasklist);
-
- /* guard against re-entrant rebuilds: the show/hide of buttons during
- * the rebuild triggers size-allocate, which would otherwise schedule
- * another rebuild based on a half-updated grouping state */
- tasklist->rebuilding = TRUE;
-
- /* 1. Disconnect groups from their applications and collect every group
- * button so we can destroy them in one pass below. The individual
- * task buttons are kept alive to avoid freeing XfwWindow objects
- * that libxfce4windowing may not hold a strong reference to. */
- if (tasklist->apps)
- {
- g_hash_table_iter_init (&iter, tasklist->apps);
- while (g_hash_table_iter_next (&iter, &key, &value))
- {
- GroupTask *group = value;
-
- if (group == NULL)
- continue;
-
- if (group->app)
- g_signal_handlers_disconnect_by_data (group->app, group);
-
- if (group->button)
- to_destroy = g_list_prepend (to_destroy, group->button);
-
- group->members = NULL;
- }
-
- for (l = to_destroy; l != NULL; l = l->next)
- {
- GtkWidget *btn = GTK_WIDGET (l->data);
- if (gtk_widget_get_parent (btn))
- gtk_widget_destroy (btn);
- }
- g_list_free (to_destroy);
-
- g_hash_table_remove_all (tasklist->apps);
- }
-
- if (tasklist->window_to_group)
- g_hash_table_remove_all (tasklist->window_to_group);
-
- /* 2. Clear each task's grouping state. */
- for (t = tasklist->tasks; t != NULL; t = t->next)
- {
- ToplevelTask *task = t->data;
-
- if (task)
- task->group = NULL;
- }
-
- /* 3. Show every individual task button again and assign it according to
- * the current grouping mode. */
- for (t = tasklist->tasks; t != NULL; t = t->next)
- {
- ToplevelTask *task = t->data;
-
- if (task == NULL || task->button == NULL)
- continue;
-
- gtk_widget_show (task->button);
-
- if (gtk_widget_get_parent (task->button) == NULL)
- gtk_box_pack_start (GTK_BOX (tasklist->list), task->button, TRUE, TRUE, 0);
-
- tasklist_assign_to_group (tasklist, task);
- }
-
- /* 4. Re-enable screen event tracking */
- g_signal_connect (tasklist->screen, "window-opened",
- G_CALLBACK (screen_window_opened), tasklist);
- g_signal_connect (tasklist->screen, "window-closed",
- G_CALLBACK (screen_window_closed), tasklist);
- g_signal_connect (tasklist->screen, "active-window-changed",
- G_CALLBACK (screen_active_window_changed), tasklist);
-
- tasklist->rebuilding = FALSE;
-
- if (gtk_orientable_get_orientation (GTK_ORIENTABLE (tasklist->outer_box)) != GTK_ORIENTATION_VERTICAL)
- {
- GList *children = gtk_container_get_children (GTK_CONTAINER (tasklist->list));
- guint visible = g_list_length (children);
- g_list_free (children);
-
- if (visible > 0)
- {
- GtkWidget *parent_box;
- int button_space;
-
- parent_box = gtk_widget_get_ancestor (tasklist->outer_box, GTK_TYPE_BOX);
- tasklist_width = MAX (gtk_widget_get_allocated_width (parent_box),
- tasklist_width);
- button_space = (tasklist_width / visible) * 0.75;
- button_space = MIN (button_space, full_button_width);
- adjust_buttons (GTK_CONTAINER (tasklist->list), button_space, visible, NULL);
- }
- }
-
- gtk_widget_queue_resize (tasklist->list);
-}
-
-void
-wayland_tasklist_set_grouping (GtkWidget *tasklist_widget, WaylandTasklistGroupingType grouping)
-{
- TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
- g_return_if_fail (tasklist);
-
- if (tasklist->grouping == grouping)
- return;
-
- tasklist->grouping = grouping;
-
- /* when switching away from auto-grouping, stop the space-based
- * regrouping so it doesn't fight with the new mode */
- if (grouping != WAYLAND_TASKLIST_AUTO_GROUP)
- {
- if (tasklist->auto_grouping_idle != 0)
- {
- g_source_remove (tasklist->auto_grouping_idle);
- tasklist->auto_grouping_idle = 0;
- }
- tasklist->auto_grouping = FALSE;
- tasklist->auto_grouping_applied = FALSE;
- }
-
- tasklist_rebuild (tasklist);
+ return &wayland_backend;
}
diff --git a/applets/wncklet/wayland-backend.h b/applets/wncklet/wayland-backend.h
index 7028fefc6..7f1c27512 100644
--- a/applets/wncklet/wayland-backend.h
+++ b/applets/wncklet/wayland-backend.h
@@ -30,27 +30,15 @@
#endif
#include
-#include
-typedef enum {
- WAYLAND_TASKLIST_NEVER_GROUP,
- WAYLAND_TASKLIST_AUTO_GROUP,
- WAYLAND_TASKLIST_ALWAYS_GROUP
-} WaylandTasklistGroupingType;
+#include "tasklist-backend.h"
-#ifdef __cplusplus
-extern "C" {
-#endif
+G_BEGIN_DECLS
-GtkWidget* wayland_tasklist_new (void);
-void wayland_tasklist_set_orientation (GtkWidget* tasklist_widget, GtkOrientation orient);
-void wayland_tasklist_set_middle_click_close (GtkWidget* tasklist_widget, gboolean enabled);
-void wayland_tasklist_set_scroll_enabled (GtkWidget* tasklist_widget, gboolean enabled);
-void wayland_tasklist_set_grouping (GtkWidget* tasklist_widget, WaylandTasklistGroupingType grouping);
+/* The Wayland implementation of the tasklist backend, or NULL when not
+ * running on a Wayland display. */
+const TasklistBackend *wayland_tasklist_backend (void);
-#ifdef __cplusplus
-}
-#endif
+G_END_DECLS
#endif /* _WNCKLET_APPLET_WAYLAND_BACKEND_H_ */
-
diff --git a/applets/wncklet/wayland-workspace.h b/applets/wncklet/wayland-workspace.h
deleted file mode 100644
index 36d84c47f..000000000
--- a/applets/wncklet/wayland-workspace.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/* Wncklet applet Wayland workspace backend */
-
-/*
- * Copyright (C) 2026 MATE Desktop Team
- *
- * 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 St, Fifth Floor, Boston, MA
- * 02110-1301, USA.
- */
-
-#ifndef _WNCKLET_APPLET_WAYLAND_WORKSPACE_H_
-#define _WNCKLET_APPLET_WAYLAND_WORKSPACE_H_
-
-#include
-#include
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-GtkWidget* wayland_workspace_new (void);
-void wayland_workspace_set_orientation(GtkWidget *pager_widget,
- GtkOrientation orientation);
-void wayland_workspace_set_rows (GtkWidget *pager_widget,
- int n_rows);
-void wayland_workspace_set_show_all (GtkWidget *pager_widget,
- gboolean show_all);
-void wayland_workspace_set_show_names (GtkWidget *pager_widget,
- gboolean show_names);
-int wayland_workspace_get_count (GtkWidget *pager_widget);
-const char* wayland_workspace_get_name (GtkWidget *pager_widget,
- int index);
-int wayland_workspace_get_active_index (GtkWidget *pager_widget);
-void wayland_workspace_activate_nth (GtkWidget *pager_widget,
- int index);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* _WNCKLET_APPLET_WAYLAND_WORKSPACE_H_ */
diff --git a/applets/wncklet/window-list.c b/applets/wncklet/window-list.c
index cc09c4178..66af5d8ac 100644
--- a/applets/wncklet/window-list.c
+++ b/applets/wncklet/window-list.c
@@ -22,45 +22,44 @@
#ifdef HAVE_X11
#include
#include
-#define WNCK_I_KNOW_THIS_IS_UNSTABLE
-#include
+#include
#endif /* HAVE_X11 */
-#ifndef HAVE_X11
-#include
-#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
-#endif
-
#ifdef HAVE_WAYLAND
#include
-#include "wayland-backend.h"
#endif /* HAVE_WAYLAND */
#define MATE_DESKTOP_USE_UNSTABLE_API
#include
+#include "tasklist-backend.h"
#include "wncklet.h"
#include "window-list.h"
+#ifdef HAVE_X11
+#include "x11-backend.h"
+#endif /* HAVE_X11 */
+
+#ifdef HAVE_WAYLAND
+#include "wayland-backend.h"
+#endif /* HAVE_WAYLAND */
+
#define WINDOW_LIST_ICON "mate-panel-window-list"
#define WINDOW_LIST_SCHEMA "org.mate.panel.applet.window-list"
#define WINDOW_LIST_PREVIEW_SCHEMA "org.mate.panel.applet.window-list-previews"
-typedef enum {
- TASKLIST_NEVER_GROUP,
- TASKLIST_AUTO_GROUP,
- TASKLIST_ALWAYS_GROUP
-} TasklistGroupingType;
-
typedef struct {
GtkWidget* applet;
GtkWidget* tasklist;
GtkWidget* preview;
-#ifdef HAVE_X11
- WnckHandle* wnck_handle;
-#endif
+ /* NULL when no backend matches the display in use. */
+ const TasklistBackend* backend;
+
+ /* TRUE when backend is the X11 one. Window previews are X11-only, so
+ * the hover handlers must not be hooked up for any other backend. */
+ gboolean backend_is_x11;
gboolean show_window_thumbnails;
gint thumbnail_size;
@@ -73,9 +72,6 @@ typedef struct {
GtkOrientation orientation;
int size;
-#if !defined(WNCKLET_INPROCESS) && !GTK_CHECK_VERSION (3, 23, 0)
- gboolean needs_hints;
-#endif
/* Properties: */
GtkWidget* properties_dialog;
@@ -118,101 +114,36 @@ static void tasklist_update(TasklistData* tasklist)
gtk_widget_set_size_request(GTK_WIDGET(tasklist->tasklist), tasklist->size, -1);
}
-#ifdef HAVE_X11
- if (WNCK_IS_TASKLIST(tasklist->tasklist))
- {
- WnckTasklistGroupingType grouping;
- switch (tasklist->grouping)
- {
- case TASKLIST_NEVER_GROUP:
- grouping = WNCK_TASKLIST_NEVER_GROUP;
- break;
- case TASKLIST_AUTO_GROUP:
- grouping = WNCK_TASKLIST_AUTO_GROUP;
- break;
- case TASKLIST_ALWAYS_GROUP:
- grouping = WNCK_TASKLIST_ALWAYS_GROUP;
- break;
- default:
- grouping = WNCK_TASKLIST_NEVER_GROUP;
- }
- wnck_tasklist_set_grouping(WNCK_TASKLIST(tasklist->tasklist), grouping);
- wnck_tasklist_set_include_all_workspaces(WNCK_TASKLIST(tasklist->tasklist), tasklist->include_all_workspaces);
- wnck_tasklist_set_switch_workspace_on_unminimize(WNCK_TASKLIST(tasklist->tasklist), tasklist->move_unminimized_windows);
- wnck_tasklist_set_scroll_enabled (WNCK_TASKLIST(tasklist->tasklist), tasklist->scroll_enable);
- wnck_tasklist_set_middle_click_close (WNCK_TASKLIST (tasklist->tasklist), tasklist->middle_click_close);
- }
-#endif /* HAVE_X11 */
+ if (tasklist->backend == NULL)
+ return;
-#ifdef HAVE_WAYLAND
- if (GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default()))
- {
- WaylandTasklistGroupingType grouping;
- switch (tasklist->grouping)
- {
- case TASKLIST_NEVER_GROUP:
- grouping = WAYLAND_TASKLIST_NEVER_GROUP;
- break;
- case TASKLIST_AUTO_GROUP:
- grouping = WAYLAND_TASKLIST_AUTO_GROUP;
- break;
- case TASKLIST_ALWAYS_GROUP:
- grouping = WAYLAND_TASKLIST_ALWAYS_GROUP;
- break;
- default:
- grouping = WAYLAND_TASKLIST_NEVER_GROUP;
- }
- wayland_tasklist_set_grouping (tasklist->tasklist, grouping);
- wayland_tasklist_set_scroll_enabled (tasklist->tasklist, tasklist->scroll_enable);
- wayland_tasklist_set_middle_click_close (tasklist->tasklist, tasklist->middle_click_close);
- }
-#endif /* HAVE_WAYLAND */
+ tasklist_core_set_grouping (tasklist->tasklist, tasklist->grouping);
+ tasklist_core_set_scroll_enabled (tasklist->tasklist, tasklist->scroll_enable);
+ tasklist_core_set_middle_click_close (tasklist->tasklist, tasklist->middle_click_close);
+ tasklist_core_set_include_all_workspaces (tasklist->tasklist, tasklist->include_all_workspaces);
+ tasklist_core_set_switch_workspace_on_unminimize (tasklist->tasklist, tasklist->move_unminimized_windows);
}
static void tasklist_apply_orientation(TasklistData* tasklist)
{
-#ifdef HAVE_X11
- if (WNCK_IS_TASKLIST(tasklist->tasklist))
- {
- wnck_tasklist_set_orientation(WNCK_TASKLIST(tasklist->tasklist), tasklist->orientation);
- }
-#endif /* HAVE_X11 */
-
-#ifdef HAVE_WAYLAND
- if (GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default()))
- {
- wayland_tasklist_set_orientation(tasklist->tasklist, tasklist->orientation);
- }
-#endif
+ if (tasklist->backend != NULL)
+ tasklist_core_set_orientation (tasklist->tasklist, tasklist->orientation);
}
static void tasklist_set_button_relief(TasklistData* tasklist, GtkReliefStyle relief)
{
-#ifdef HAVE_X11
- if (WNCK_IS_TASKLIST(tasklist->tasklist))
- {
- wnck_tasklist_set_button_relief(WNCK_TASKLIST(tasklist->tasklist), relief);
- }
-#endif /* HAVE_X11 */
-
- /* Not implemented for Wayland */
+ /* Not implemented for the shared core */
}
static const int* tasklist_get_size_hint_list(TasklistData* tasklist, int* n_elements)
{
-#ifdef HAVE_X11
- if (WNCK_IS_TASKLIST(tasklist->tasklist))
+ if (tasklist->backend == NULL)
{
- return wnck_tasklist_get_size_hint_list(WNCK_TASKLIST(tasklist->tasklist), n_elements);
- }
- else
-#endif /* HAVE_X11 */
-
- {
- /* Not implemented for Wayland */
*n_elements = 0;
return NULL;
}
+
+ return tasklist_core_get_size_hint_list (tasklist->tasklist, n_elements);
}
static void response_cb(GtkWidget* widget, int id, TasklistData* tasklist)
@@ -267,7 +198,7 @@ static void applet_change_background(MatePanelApplet* applet, MatePanelAppletBac
#ifdef HAVE_X11
static cairo_surface_t*
-preview_window_thumbnail (WnckWindow *wnck_window,
+preview_window_thumbnail (gpointer window_handle,
TasklistData *tasklist,
int *thumbnail_width,
int *thumbnail_height,
@@ -285,8 +216,16 @@ preview_window_thumbnail (WnckWindow *wnck_window,
int width, height, scale;
int src_x = 0, src_y = 0;
- win = wnck_window_get_xid (wnck_window);
display = gdk_display_get_default ();
+
+ if (display == NULL || !GDK_IS_X11_DISPLAY (display))
+ return NULL;
+
+ win = (Window) xfw_window_x11_get_xid (window_handle);
+
+ if (win == 0)
+ return NULL;
+
xdpy = GDK_DISPLAY_XDISPLAY (display);
/* Find the frame window (WM parent) which includes decorations */
@@ -415,8 +354,61 @@ static int find_offset(GList *list, gdouble target)
}
#define PREVIEW_PADDING 5
+/* Gather the offsets of every task button below @widget. Buttons are nested
+ * inside an inner box, so the search recurses. The offsets are kept in sorted
+ * lists because grouped buttons are appended last even though they are drawn
+ * first. */
+static void
+collect_button_allocations (GtkWidget *widget,
+ GList **alloc_x_list,
+ GList **alloc_y_list,
+ GtkAllocation *last_alloc,
+ gboolean *have_allocation)
+{
+ GList *children = gtk_container_get_children (GTK_CONTAINER (widget));
+ GList *l;
+
+ for (l = children; l != NULL; l = l->next)
+ {
+ GtkWidget *child = l->data;
+
+ if (g_strcmp0 (gtk_widget_get_name (child), "tasklist-button") == 0)
+ {
+ GtkAllocation alloc;
+
+ gtk_widget_get_allocation (child, &alloc);
+
+ /* Skip grouped buttons: these usually have alloc width/height=1,
+ * except right after grouping is toggled. Then simply open or
+ * close a new window to get the correct offset. */
+ if (alloc.width < 2 || alloc.height < 2)
+ continue;
+
+ *alloc_x_list = g_list_insert_sorted (*alloc_x_list,
+ GINT_TO_POINTER (alloc.x),
+ g_int_compare);
+ *alloc_y_list = g_list_insert_sorted (*alloc_y_list,
+ GINT_TO_POINTER (alloc.y),
+ g_int_compare);
+
+ /* The width/height from the last allocation is used for centering
+ * the preview. It might be off by a pixel because not all buttons
+ * have the exact same width/height but this isn't critical. */
+ *last_alloc = alloc;
+ *have_allocation = TRUE;
+ }
+ else if (GTK_IS_CONTAINER (child))
+ {
+ collect_button_allocations (child, alloc_x_list, alloc_y_list,
+ last_alloc, have_allocation);
+ }
+ }
+
+ g_list_free (children);
+}
+
static void
-preview_window_reposition (WnckTasklist *tl,
+preview_window_reposition (GtkWidget *tl,
TasklistData *tasklist,
int width,
int height,
@@ -470,31 +462,19 @@ preview_window_reposition (WnckTasklist *tl,
* This allows us to avoid off-by-one errors that would cause the preview to be positioned over the adjacent button. */
GList *alloc_x_list = NULL;
GList *alloc_y_list = NULL;
- GtkAllocation last_alloc;
- GList* children = gtk_container_get_children (GTK_CONTAINER(tl));
- while (children != NULL)
- {
- if (g_strcmp0 (gtk_widget_get_name (children->data), "tasklist-button") == 0) {
- GtkAllocation alloc;
- gtk_widget_get_allocation (children->data, &alloc);
-
- /* Skip grouped buttons: these usually have alloc width/heigh=1, except right after grouping is toggled.
- * Then simply open or close a new window to get the correct offset. */
- if (alloc.width < 2 || alloc.height < 2)
- {
- children = children->next;
- continue;
- }
+ GtkAllocation last_alloc = { 0, 0, 0, 0 };
+ gboolean have_allocation = FALSE;
- /* Keep x and y offsets in sorted lists */
- alloc_x_list = g_list_insert_sorted (alloc_x_list, GINT_TO_POINTER(alloc.x), g_int_compare);
- alloc_y_list = g_list_insert_sorted (alloc_y_list, GINT_TO_POINTER(alloc.y), g_int_compare);
+ /* The task buttons live inside an inner box rather than directly on the
+ * outer box, so walk the whole subtree. */
+ collect_button_allocations (tl, &alloc_x_list, &alloc_y_list, &last_alloc, &have_allocation);
- /* The width/height from the last allocation will be used for centering the preview.
- * It might be off by a pixel because not all buttons have the exact same width/height but this isn't critical. */
- last_alloc = alloc;
- }
- children = children->next;
+ /* Nothing to center against yet (the tasklist may still be laying out). */
+ if (!have_allocation)
+ {
+ g_list_free (alloc_x_list);
+ g_list_free (alloc_y_list);
+ return;
}
/* Center preview at the midpoint of the tasklist button */
@@ -527,10 +507,10 @@ static gboolean preview_window_draw (GtkWidget *widget, cairo_t *cr, cairo_surfa
return FALSE;
}
-static gboolean applet_enter_notify_event (WnckTasklist *tl, GList *wnck_windows, TasklistData *tasklist)
+static gboolean applet_enter_notify_event (GtkWidget *tl, GList *windows, TasklistData *tasklist)
{
cairo_surface_t *thumbnail;
- WnckWindow *wnck_window = NULL;
+ gpointer window = NULL;
int n_windows;
int thumbnail_width;
int thumbnail_height;
@@ -542,27 +522,26 @@ static gboolean applet_enter_notify_event (WnckTasklist *tl, GList *wnck_windows
tasklist->preview = NULL;
}
- if (!tasklist->show_window_thumbnails || wnck_windows == NULL)
+ if (!tasklist->show_window_thumbnails || windows == NULL)
return FALSE;
- n_windows = g_list_length (wnck_windows);
+ n_windows = g_list_length (windows);
/* TODO: Display a list of stacked thumbnails for grouped windows. */
if (n_windows == 1)
{
- GList* l = wnck_windows;
+ GList* l = windows;
if (l != NULL)
- wnck_window = (WnckWindow*)l->data;
+ window = l->data;
}
- if (wnck_window == NULL)
+ if (window == NULL)
return FALSE;
/* Do not show preview if window is not visible nor in current workspace */
- if (!wnck_window_is_visible_on_workspace (wnck_window,
- wnck_screen_get_active_workspace (wncklet_get_screen (tasklist->wnck_handle, tasklist->applet))))
+ if (!tasklist->backend->window_is_on_active_workspace (window))
return FALSE;
- thumbnail = preview_window_thumbnail (wnck_window, tasklist, &thumbnail_width, &thumbnail_height, &thumbnail_scale);
+ thumbnail = preview_window_thumbnail (window, tasklist, &thumbnail_width, &thumbnail_height, &thumbnail_scale);
if (thumbnail == NULL)
return FALSE;
@@ -585,7 +564,7 @@ static gboolean applet_enter_notify_event (WnckTasklist *tl, GList *wnck_windows
return FALSE;
}
-static gboolean applet_leave_notify_event (WnckTasklist *tl, GList *wnck_windows, TasklistData *tasklist)
+static gboolean applet_leave_notify_event (GtkWidget *tl, GList *windows, TasklistData *tasklist)
{
if (tasklist->preview != NULL)
{
@@ -604,6 +583,11 @@ static void applet_change_pixel_size(MatePanelApplet* applet, gint size, Tasklis
tasklist->size = size;
+ /* The core widget is a plain label when there is no backend, and the
+ * core getter would object to being handed that. */
+ if (tasklist->backend != NULL)
+ tasklist_core_set_panel_size (tasklist->tasklist, size);
+
tasklist_update(tasklist);
}
@@ -832,27 +816,11 @@ static void applet_size_allocate(GtkWidget *widget, GtkAllocation *allocation, T
g_assert(len % 2 == 0);
-#if !defined(WNCKLET_INPROCESS) && !GTK_CHECK_VERSION (3, 23, 0)
- /* HACK: When loading the WnckTasklist initially, it reports size hints as though there were
- * no elements in the Tasklist. This causes a rendering issue when built out-of-process in
- * HiDPI displays. We keep a flag to skip size hinting until WnckTasklist has something to
- * show. */
- if (!tasklist->needs_hints)
- {
- int i;
- for (i = 0; i < len; i++)
- {
- if (size_hints[i])
- {
- tasklist->needs_hints = TRUE;
- break;
- }
- }
- }
-
- if (tasklist->needs_hints)
-#endif
- mate_panel_applet_set_size_hints(MATE_PANEL_APPLET(tasklist->applet), size_hints, len, 0);
+ /* The core reports no hints at all until it has a window to show, and
+ * only reports real widths afterwards, so there is nothing to guard
+ * against the way an empty WnckTasklist used to report a list of
+ * zeroes. */
+ mate_panel_applet_set_size_hints(MATE_PANEL_APPLET(tasklist->applet), size_hints, len, 0);
}
gboolean window_list_applet_fill(MatePanelApplet* applet)
@@ -899,10 +867,6 @@ gboolean window_list_applet_fill(MatePanelApplet* applet)
tasklist->size = mate_panel_applet_get_size(applet);
-#if !defined(WNCKLET_INPROCESS) && !GTK_CHECK_VERSION (3, 23, 0)
- tasklist->needs_hints = FALSE;
-#endif
-
switch (mate_panel_applet_get_orient(applet))
{
case MATE_PANEL_APPLET_ORIENT_LEFT:
@@ -916,30 +880,55 @@ gboolean window_list_applet_fill(MatePanelApplet* applet)
break;
}
+ /* The backend is chosen by display type rather than at compile time, so
+ * a build with both backends still works whichever one is in use. */
#ifdef HAVE_X11
- if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
- {
- tasklist->wnck_handle = wnck_handle_new(WNCK_CLIENT_TYPE_PAGER);
- tasklist->tasklist = wnck_tasklist_new_with_handle(tasklist->wnck_handle);
-
- g_signal_connect (tasklist->tasklist, "task-enter-notify",
- G_CALLBACK (applet_enter_notify_event),
- tasklist);
- g_signal_connect (tasklist->tasklist, "task-leave-notify",
- G_CALLBACK (applet_leave_notify_event),
- tasklist);
- }
- else
+ tasklist->backend = x11_tasklist_backend ();
+ tasklist->backend_is_x11 = (tasklist->backend != NULL);
#endif /* HAVE_X11 */
-
#ifdef HAVE_WAYLAND
- if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ()))
+ if (tasklist->backend == NULL)
+ tasklist->backend = wayland_tasklist_backend ();
+#endif /* HAVE_WAYLAND */
+
+ if (tasklist->backend != NULL)
{
- tasklist->tasklist = wayland_tasklist_new();
+ tasklist->tasklist = tasklist_core_new (tasklist->backend);
+
+ if (tasklist->tasklist == NULL)
+ tasklist->backend = NULL;
+
+ if (tasklist->backend != NULL)
+ {
+#ifdef HAVE_X11
+ /* Previews grab window pixels with Xlib, which xfw has no
+ * equivalent for, so they stay X11-only. Both the build
+ * guard and the backend test are needed: a dual-backend
+ * build on Wayland compiles these in but must not
+ * connect them, or hovering would hand a Wayland
+ * handle to preview_window_thumbnail(). */
+ if (tasklist->backend_is_x11)
+ {
+ g_signal_connect (tasklist->tasklist, "task-enter-notify",
+ G_CALLBACK (applet_enter_notify_event),
+ tasklist);
+ g_signal_connect (tasklist->tasklist, "task-leave-notify",
+ G_CALLBACK (applet_leave_notify_event),
+ tasklist);
+ }
+#endif /* HAVE_X11 */
+
+ /* Choose the icon size before the first update builds
+ * buttons, so they are never laid out at the default
+ * and then torn down and laid out again. tasklist->size
+ * is already the applet's real thickness here. */
+ tasklist_core_set_panel_size (tasklist->tasklist, tasklist->size);
+
+ tasklist_update (tasklist);
+ }
}
- else
-#endif /* HAVE_WAYLAND */
+ if (tasklist->backend == NULL)
{
tasklist->tasklist = gtk_label_new ("[Tasklist not supported on this platform]");
}
@@ -1296,9 +1285,5 @@ static void destroy_tasklist(GtkWidget* widget, TasklistData* tasklist)
if (tasklist->preview)
gtk_widget_destroy(tasklist->preview);
-#ifdef HAVE_X11
- g_clear_object(&tasklist->wnck_handle);
-#endif
-
g_free(tasklist);
}
diff --git a/applets/wncklet/window-menu.c b/applets/wncklet/window-menu.c
index 21d2117f4..cdf8cd77d 100644
--- a/applets/wncklet/window-menu.c
+++ b/applets/wncklet/window-menu.c
@@ -35,32 +35,15 @@
#include
#include
-#ifdef HAVE_X11
-#include
-#define WNCK_I_KNOW_THIS_IS_UNSTABLE
-#include
-#endif /* HAVE_X11 */
-
-#ifdef HAVE_WAYLAND
-#include
-#include "wayland-window-menu.h"
-#endif /* HAVE_WAYLAND */
-
-#ifndef HAVE_X11
-#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
-#endif
-
#include "wncklet.h"
#include "window-menu.h"
+#include "xfw-window-menu.h"
#define WINDOW_MENU_ICON "mate-panel-window-menu"
typedef struct {
GtkWidget* applet;
GtkWidget* selector;
-#ifdef HAVE_X11
- WnckHandle* wnck_handle;
-#endif
int size;
MatePanelAppletOrient orient;
} WindowMenu;
@@ -131,9 +114,6 @@ static const GtkActionEntry window_menu_actions[] = {
static void window_menu_destroy(GtkWidget* widget, WindowMenu* window_menu)
{
-#ifdef HAVE_X11
- g_clear_object(&window_menu->wnck_handle);
-#endif
g_free(window_menu);
}
@@ -264,26 +244,7 @@ gboolean window_menu_applet_fill(MatePanelApplet* applet)
action_group);
g_object_unref(action_group);
-#ifdef HAVE_X11
- if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
- {
- window_menu->wnck_handle = wnck_handle_new(WNCK_CLIENT_TYPE_PAGER);
- window_menu->selector = wnck_selector_new_with_handle(window_menu->wnck_handle);
- }
- else
-#endif /* HAVE_X11 */
-
-#ifdef HAVE_WAYLAND
- if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ()))
- {
- window_menu->selector = wayland_window_menu_new ();
- }
- else
-#endif /* HAVE_WAYLAND */
-
- {
- window_menu->selector = gtk_label_new ("[Window menu not supported on this platform]");
- }
+ window_menu->selector = xfw_window_menu_new ();
gtk_container_add(GTK_CONTAINER(window_menu->applet), window_menu->selector);
diff --git a/applets/wncklet/wncklet.c b/applets/wncklet/wncklet.c
index 5662aeadb..0d0582516 100644
--- a/applets/wncklet/wncklet.c
+++ b/applets/wncklet/wncklet.c
@@ -31,18 +31,8 @@
#include
#include
-#ifdef HAVE_X11
-#include
-#define WNCK_I_KNOW_THIS_IS_UNSTABLE
-#include
-#include "workspace-switcher.h"
-#endif
-#ifndef HAVE_X11
-#include
-#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
-#endif
-
#include "wncklet.h"
+#include "workspace-switcher.h"
#include "window-menu.h"
#include "window-list.h"
#include "showdesktop.h"
@@ -99,23 +89,6 @@ void wncklet_display_help(GtkWidget* widget, const char* doc_id, const char* lin
}
}
-#ifdef HAVE_X11
-WnckScreen* wncklet_get_screen(WnckHandle* handle, GtkWidget* applet)
-{
- g_return_val_if_fail (WNCK_IS_HANDLE (handle), NULL);
- g_return_val_if_fail (GDK_IS_X11_DISPLAY (gdk_display_get_default ()), NULL);
-
- int screen_num;
-
- if (!gtk_widget_has_screen(applet))
- return wnck_handle_get_default_screen(handle);
-
- screen_num = gdk_x11_screen_get_screen_number(gtk_widget_get_screen(applet));
-
- return wnck_handle_get_screen(handle, screen_num);
-}
-#endif /* HAVE_X11 */
-
void wncklet_connect_while_alive(gpointer object, const char* signal, GCallback func, gpointer func_data, gpointer alive_object)
{
GClosure* closure;
diff --git a/applets/wncklet/wncklet.h b/applets/wncklet/wncklet.h
index 4b1dccead..b073d7a20 100644
--- a/applets/wncklet/wncklet.h
+++ b/applets/wncklet/wncklet.h
@@ -28,9 +28,6 @@
#include
#include
-#define WNCK_I_KNOW_THIS_IS_UNSTABLE
-#include
-
#define WNCKLET_RESOURCE_PATH "/org/mate/panel/applet/wncklet/"
#ifdef __cplusplus
@@ -39,8 +36,6 @@ extern "C" {
void wncklet_display_help(GtkWidget* widget, const char* doc_id, const char* link_id, const char* icon_name);
-WnckScreen* wncklet_get_screen(WnckHandle* handle, GtkWidget* applet);
-
void wncklet_connect_while_alive(gpointer object, const char* signal, GCallback func, gpointer func_data, gpointer alive_object);
#ifdef __cplusplus
diff --git a/applets/wncklet/workspace-switcher.c b/applets/wncklet/workspace-switcher.c
index b4ae493dc..5dd741430 100644
--- a/applets/wncklet/workspace-switcher.c
+++ b/applets/wncklet/workspace-switcher.c
@@ -1,6 +1,6 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
/*
- * libwnck based pager applet.
+ * libxfce4windowing based pager applet.
* (C) 2001 Alexander Larsson
* (C) 2001 Red Hat, Inc
*
@@ -22,17 +22,7 @@
#include
#include
#include
-
-#ifdef HAVE_X11
-#include
-#define WNCK_I_KNOW_THIS_IS_UNSTABLE
-#include
-#endif /* HAVE_X11 */
-
-#ifdef HAVE_WAYLAND
-#include
-#include "wayland-workspace.h"
-#endif /* HAVE_WAYLAND */
+#include
#include
@@ -40,6 +30,8 @@
#include "wncklet.h"
+#include "xfw-pager.h"
+
/* even 16 is pretty darn dubious. */
#define MAX_REASONABLE_ROWS 16
#define DEFAULT_ROWS 1
@@ -54,7 +46,7 @@
#define WORKSPACE_SWITCHER_ICON "mate-panel-workspace-switcher"
-/* Container for the WnckPager to work around the sizing issues we have in the
+/* Container for the pager to work around the sizing issues we have in the
* panel. See
* https://github.com/mate-desktop/mate-panel/issues/1230#issuecomment-1046235088 */
@@ -201,29 +193,13 @@ pager_container_set_orientation (PagerContainer *self,
/* Pager applet itself */
-typedef enum {
- PAGER_WM_MARCO,
- PAGER_WM_METACITY,
- PAGER_WM_COMPIZ,
- PAGER_WM_I3,
- PAGER_WM_XMONAD,
- PAGER_WM_UNKNOWN
-} PagerWM;
-
typedef struct {
GtkWidget* applet;
GtkWidget* pager_container;
GtkWidget* pager;
-#ifdef HAVE_X11
- WnckHandle* wnck_handle;
- WnckScreen* screen;
-#endif
-#ifdef HAVE_WAYLAND
- gboolean is_wayland;
-#endif
- PagerWM wm;
+ gulong screen_handler; /* handler id for XfwScreen::window-manager-changed */
/* Properties: */
GtkWidget* properties_dialog;
@@ -254,187 +230,121 @@ static void display_properties_dialog(GtkAction* action, PagerData* pager);
static void display_help_dialog(GtkAction* action, PagerData* pager);
static void display_about_dialog(GtkAction* action, PagerData* pager);
static void destroy_pager(GtkWidget* widget, PagerData* pager);
+static void update_workspaces_model(PagerData* pager);
-#ifdef HAVE_X11
-static void pager_update_wnck(PagerData* pager, WnckPager* wnck_pager)
+static XfwWorkspaceManager *
+pager_get_workspace_manager (void)
{
- WnckPagerDisplayMode display_mode = WNCK_PAGER_DISPLAY_CONTENT;
+ XfwScreen *screen = xfw_screen_get_default ();
- if (pager->display_names && (
- pager->wm == PAGER_WM_MARCO ||
- pager->wm == PAGER_WM_METACITY ||
- pager->wm == PAGER_WM_I3 ||
- pager->wm == PAGER_WM_XMONAD))
- {
- display_mode = WNCK_PAGER_DISPLAY_NAME;
- }
+ if (!screen)
+ return NULL;
- wnck_pager_set_orientation(wnck_pager, pager->orientation);
- wnck_pager_set_n_rows(wnck_pager, pager->n_rows);
- wnck_pager_set_show_all(wnck_pager, pager->display_all);
- wnck_pager_set_display_mode(wnck_pager, display_mode);
+ return xfw_screen_get_workspace_manager (screen);
}
-#endif /* HAVE_X11 */
-static void pager_update(PagerData* pager)
+/* The panel only ever shows a single pager, so we drive the first workspace
+ * group, which is the one whose workspaces the pager widget displays. Both
+ * list_workspace_groups() and the workspaces they hand out are owned by the
+ * manager and must not be freed. */
+static XfwWorkspaceGroup *
+pager_get_workspace_group (void)
{
-#ifdef HAVE_X11
- if (WNCK_IS_PAGER(pager->pager))
- {
- pager_update_wnck(pager, WNCK_PAGER(pager->pager));
- }
-#endif /* HAVE_X11 */
+ XfwWorkspaceManager *manager = pager_get_workspace_manager ();
+ GList *groups;
-#ifdef HAVE_WAYLAND
- if (pager->is_wayland)
- {
- wayland_workspace_set_orientation(pager->pager, pager->orientation);
- wayland_workspace_set_rows(pager->pager, pager->n_rows);
- wayland_workspace_set_show_all(pager->pager, pager->display_all);
- wayland_workspace_set_show_names(pager->pager, pager->display_names);
- }
-#endif /* HAVE_WAYLAND */
+ if (!manager)
+ return NULL;
+
+ groups = xfw_workspace_manager_list_workspace_groups (manager);
+ if (!groups)
+ return NULL;
+
+ return groups->data;
}
-static void update_properties_for_wm(PagerData* pager)
+static void pager_update(PagerData* pager)
{
- switch (pager->wm)
- {
- case PAGER_WM_MARCO:
- if (pager->workspaces_frame)
- gtk_widget_show(pager->workspaces_frame);
- if (pager->workspace_names_label)
- gtk_widget_show(pager->workspace_names_label);
- if (pager->workspace_names_scroll)
- gtk_widget_show(pager->workspace_names_scroll);
- if (pager->display_workspaces_toggle)
- gtk_widget_show(pager->display_workspaces_toggle);
- if (pager->cell)
- g_object_set (pager->cell, "editable", TRUE, NULL);
- break;
- case PAGER_WM_METACITY:
- if (pager->workspaces_frame)
- gtk_widget_show(pager->workspaces_frame);
- if (pager->workspace_names_label)
- gtk_widget_show(pager->workspace_names_label);
- if (pager->workspace_names_scroll)
- gtk_widget_show(pager->workspace_names_scroll);
- if (pager->display_workspaces_toggle)
- gtk_widget_show(pager->display_workspaces_toggle);
- if (pager->cell)
- g_object_set (pager->cell, "editable", TRUE, NULL);
- break;
- case PAGER_WM_I3:
- if (pager->workspaces_frame)
- gtk_widget_show(pager->workspaces_frame);
- if (pager->num_workspaces_spin)
- gtk_widget_set_sensitive(pager->num_workspaces_spin, FALSE);
- if (pager->workspace_names_label)
- gtk_widget_hide(pager->workspace_names_label);
- if (pager->workspace_names_scroll)
- gtk_widget_hide(pager->workspace_names_scroll);
- if (pager->display_workspaces_toggle)
- gtk_widget_show(pager->display_workspaces_toggle);
- if (pager->cell)
- g_object_set (pager->cell, "editable", FALSE, NULL);
- break;
- case PAGER_WM_XMONAD:
- if (pager->workspaces_frame)
- gtk_widget_show(pager->workspaces_frame);
- if (pager->num_workspaces_spin)
- gtk_widget_set_sensitive(pager->num_workspaces_spin, FALSE);
- if (pager->workspace_names_label)
- gtk_widget_hide(pager->workspace_names_label);
- if (pager->workspace_names_scroll)
- gtk_widget_hide(pager->workspace_names_scroll);
- if (pager->display_workspaces_toggle)
- gtk_widget_show(pager->display_workspaces_toggle);
- if (pager->cell)
- g_object_set (pager->cell, "editable", FALSE, NULL);
- break;
- case PAGER_WM_COMPIZ:
- if (pager->workspaces_frame)
- gtk_widget_show(pager->workspaces_frame);
- if (pager->workspace_names_label)
- gtk_widget_hide(pager->workspace_names_label);
- if (pager->workspace_names_scroll)
- gtk_widget_hide(pager->workspace_names_scroll);
- if (pager->display_workspaces_toggle)
- gtk_widget_hide(pager->display_workspaces_toggle);
- if (pager->cell)
- g_object_set (pager->cell, "editable", FALSE, NULL);
- break;
- case PAGER_WM_UNKNOWN:
- if (pager->workspaces_frame)
- gtk_widget_hide(pager->workspaces_frame);
- break;
- default:
- g_assert_not_reached();
- }
+ xfw_pager_set_orientation (pager->pager, pager->orientation);
+ xfw_pager_set_rows (pager->pager, pager->n_rows);
+ xfw_pager_set_show_all (pager->pager, pager->display_all);
+ xfw_pager_set_show_names (pager->pager, pager->display_names);
+}
- if (pager->properties_dialog) {
- gtk_widget_hide (pager->properties_dialog);
- gtk_widget_unrealize (pager->properties_dialog);
- gtk_widget_show (pager->properties_dialog);
- }
+/* libxfce4windowing tells us what the window manager in use is capable of
+ * instead of naming it, which is both more robust than matching on a window
+ * manager name and available on every display type. */
+static gboolean
+pager_group_can_create_workspaces (XfwWorkspaceGroup *group)
+{
+ return group != NULL &&
+ (xfw_workspace_group_get_capabilities (group) &
+ XFW_WORKSPACE_GROUP_CAPABILITIES_CREATE_WORKSPACE) != 0;
}
-#ifdef HAVE_X11
-static void window_manager_changed(WnckScreen* screen, PagerData* pager)
+static void update_properties_for_capabilities(PagerData* pager)
{
- const char *wm_name = NULL;
+ XfwWorkspaceGroup *group = pager_get_workspace_group ();
+ gboolean can_create = pager_group_can_create_workspaces (group);
+
+ if (pager->workspaces_frame)
+ gtk_widget_set_visible (pager->workspaces_frame, can_create);
- if (pager->screen)
+ if (can_create)
{
- wm_name = wnck_screen_get_window_manager_name (pager->screen);
+ /* Workspace names are listed for reference only: libxfce4windowing
+ * offers no way to rename a workspace, so the list is read-only. */
+ if (pager->workspace_names_label)
+ gtk_widget_show (pager->workspace_names_label);
+ if (pager->workspace_names_scroll)
+ gtk_widget_show (pager->workspace_names_scroll);
+ if (pager->display_workspaces_toggle)
+ gtk_widget_show (pager->display_workspaces_toggle);
+ if (pager->cell)
+ g_object_set (pager->cell, "editable", FALSE, NULL);
+ if (pager->num_workspaces_spin)
+ gtk_widget_set_sensitive (pager->num_workspaces_spin, TRUE);
}
- if (!wm_name)
- pager->wm = PAGER_WM_UNKNOWN;
- else if (strcmp(wm_name, "Metacity (Marco)") == 0)
- pager->wm = PAGER_WM_MARCO;
- else if (strcmp(wm_name, "Metacity") == 0)
- pager->wm = PAGER_WM_METACITY;
- else if (strcmp(wm_name, "i3") == 0)
- pager->wm = PAGER_WM_I3;
- else if (strcmp(wm_name, "xmonad") == 0)
- pager->wm = PAGER_WM_XMONAD;
- else if (strcmp(wm_name, "Compiz") == 0)
- pager->wm = PAGER_WM_COMPIZ;
- else
- pager->wm = PAGER_WM_UNKNOWN;
+ if (pager->properties_dialog)
+ {
+ /* force a relayout after showing/hiding the workspace controls */
+ gtk_widget_hide (pager->properties_dialog);
+ gtk_widget_unrealize (pager->properties_dialog);
+ gtk_widget_show (pager->properties_dialog);
+ }
+}
- update_properties_for_wm(pager);
- pager_update(pager);
+static void window_manager_changed(XfwScreen *screen, PagerData* pager)
+{
+ update_properties_for_capabilities (pager);
}
-#endif /* HAVE_X11 */
static void applet_realized(MatePanelApplet* applet, PagerData* pager)
{
-#ifdef HAVE_X11
- if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
- {
- pager->screen = wncklet_get_screen(pager->wnck_handle, GTK_WIDGET(applet));
- wncklet_connect_while_alive(pager->screen, "window_manager_changed", G_CALLBACK(window_manager_changed), pager, pager->applet);
- window_manager_changed(pager->screen, pager);
- return;
- }
-#endif /* HAVE_X11 */
+ XfwScreen *screen;
-#ifdef HAVE_WAYLAND
- if (pager->is_wayland)
+ pager_update (pager);
+
+ screen = xfw_screen_get_default ();
+ if (screen && pager->screen_handler == 0)
{
- pager_update(pager);
+ pager->screen_handler = g_signal_connect (screen,
+ "window-manager-changed",
+ G_CALLBACK (window_manager_changed),
+ pager);
}
-#endif /* HAVE_WAYLAND */
}
static void applet_unrealized(MatePanelApplet* applet, PagerData* pager)
{
-#ifdef HAVE_X11
- pager->screen = NULL;
-#endif /* HAVE_X11 */
- pager->wm = PAGER_WM_UNKNOWN;
+ XfwScreen *screen = xfw_screen_get_default ();
+
+ if (screen && pager->screen_handler != 0)
+ {
+ g_signal_handler_disconnect (screen, pager->screen_handler);
+ pager->screen_handler = 0;
+ }
}
static void applet_change_orient(MatePanelApplet* applet, MatePanelAppletOrient orient, PagerData* pager)
@@ -473,14 +383,6 @@ static void applet_change_background(MatePanelApplet* applet, MatePanelAppletBac
new_context = gtk_style_context_new ();
gtk_style_context_set_path (new_context, gtk_widget_get_path (GTK_WIDGET (pager->pager)));
g_object_unref (new_context);
-
-#ifdef HAVE_X11
- if (WNCK_IS_PAGER(pager->pager))
- {
- wnck_pager_set_shadow_type (WNCK_PAGER (pager->pager),
- type == PANEL_NO_BACKGROUND ? GTK_SHADOW_NONE : GTK_SHADOW_IN);
- }
-#endif /* HAVE_X11 */
}
static void applet_style_updated (MatePanelApplet *applet, GtkStyleContext *context)
@@ -494,7 +396,10 @@ static void applet_style_updated (MatePanelApplet *applet, GtkStyleContext *cont
gtk_style_context_lookup_color (context, "theme_selected_bg_color", &color);
color_str = gdk_rgba_to_string (&color);
- bg_css = g_strconcat (".wnck-pager:selected {\n"
+ bg_css = g_strconcat (".wnck-pager:selected,\n"
+ ".wnck-pager button:checked,\n"
+ ".wnck-pager button:checked:hover,\n"
+ ".wnck-pager button:checked:active {\n"
" background-color:", color_str, ";\n"
"}", NULL);
gtk_css_provider_load_from_data (provider, bg_css, -1, NULL);
@@ -507,18 +412,14 @@ static void applet_style_updated (MatePanelApplet *applet, GtkStyleContext *cont
g_object_unref (provider);
}
-/* Replacement for the default scroll handler that also cares about the wrapping property.
- * Alternative: Add behaviour to libwnck (to the WnckPager widget).
- */
+/* Replacement for the default scroll handler that also cares about the wrapping property. */
static gboolean applet_scroll(MatePanelApplet* applet, GdkEventScroll* event, PagerData* pager)
{
-#ifdef HAVE_X11
GdkScrollDirection absolute_direction;
int index;
int n_workspaces;
int n_columns;
int in_last_row;
-#endif /* HAVE_X11 */
if (event->type != GDK_SCROLL)
return FALSE;
@@ -526,113 +427,21 @@ static gboolean applet_scroll(MatePanelApplet* applet, GdkEventScroll* event, Pa
if (event->direction == GDK_SCROLL_SMOOTH)
return FALSE;
-#ifdef HAVE_WAYLAND
- if (pager->is_wayland)
- {
- GdkScrollDirection absolute_direction;
- int index;
- int n_workspaces;
- int n_columns;
- int in_last_row;
-
- index = wayland_workspace_get_active_index (pager->pager);
- n_workspaces = wayland_workspace_get_count (pager->pager);
-
- if (n_workspaces <= 0)
- return TRUE;
-
- n_columns = n_workspaces / pager->n_rows;
- if (n_workspaces % pager->n_rows != 0)
- n_columns++;
-
- in_last_row = n_workspaces % n_columns;
-
- absolute_direction = event->direction;
-
- if (gtk_widget_get_direction (GTK_WIDGET (applet)) == GTK_TEXT_DIR_RTL)
- {
- switch (event->direction)
- {
- case GDK_SCROLL_RIGHT:
- absolute_direction = GDK_SCROLL_LEFT;
- break;
- case GDK_SCROLL_LEFT:
- absolute_direction = GDK_SCROLL_RIGHT;
- break;
- default:
- break;
- }
- }
-
- switch (absolute_direction)
- {
- case GDK_SCROLL_DOWN:
- if (index + n_columns < n_workspaces)
- index += n_columns;
- else if (pager->wrap_workspaces && index == n_workspaces - 1)
- index = 0;
- else if ((index < n_workspaces - 1 && index + in_last_row != n_workspaces - 1) ||
- (index == n_workspaces - 1 && in_last_row != 0))
- index = (index % n_columns) + 1;
- break;
-
- case GDK_SCROLL_RIGHT:
- if (index < n_workspaces - 1)
- index++;
- else if (pager->wrap_workspaces)
- index = 0;
- break;
-
- case GDK_SCROLL_UP:
- if (index - n_columns >= 0)
- index -= n_columns;
- else if (index > 0)
- index = ((pager->n_rows - 1) * n_columns) + (index % n_columns) - 1;
- else if (pager->wrap_workspaces)
- index = n_workspaces - 1;
-
- if (index >= n_workspaces)
- index -= n_columns;
- break;
-
- case GDK_SCROLL_LEFT:
- if (index > 0)
- index--;
- else if (pager->wrap_workspaces)
- index = n_workspaces - 1;
- break;
+ index = xfw_pager_get_active_index (pager->pager);
+ n_workspaces = xfw_pager_get_count (pager->pager);
- default:
- return FALSE;
- }
-
- wayland_workspace_activate_nth (pager->pager, index);
+ if (n_workspaces <= 0)
return TRUE;
- }
-#endif /* HAVE_WAYLAND */
-
-#ifdef HAVE_X11
- if (pager->screen)
- {
- index = wnck_workspace_get_number(wnck_screen_get_active_workspace(pager->screen));
- n_workspaces = wnck_screen_get_workspace_count(pager->screen);
- }
- else
- {
- index = 0;
- n_workspaces = 1;
- }
n_columns = n_workspaces / pager->n_rows;
-
if (n_workspaces % pager->n_rows != 0)
n_columns++;
- in_last_row = n_workspaces % n_columns;
+ in_last_row = n_workspaces % n_columns;
absolute_direction = event->direction;
- if (gtk_widget_get_direction(GTK_WIDGET(applet)) == GTK_TEXT_DIR_RTL)
+ if (gtk_widget_get_direction (GTK_WIDGET (applet)) == GTK_TEXT_DIR_RTL)
{
switch (event->direction)
{
@@ -651,43 +460,28 @@ static gboolean applet_scroll(MatePanelApplet* applet, GdkEventScroll* event, Pa
{
case GDK_SCROLL_DOWN:
if (index + n_columns < n_workspaces)
- {
index += n_columns;
- }
else if (pager->wrap_workspaces && index == n_workspaces - 1)
- {
index = 0;
- }
- else if ((index < n_workspaces - 1 && index + in_last_row != n_workspaces - 1) || (index == n_workspaces - 1 && in_last_row != 0))
- {
+ else if ((index < n_workspaces - 1 && index + in_last_row != n_workspaces - 1) ||
+ (index == n_workspaces - 1 && in_last_row != 0))
index = (index % n_columns) + 1;
- }
break;
case GDK_SCROLL_RIGHT:
if (index < n_workspaces - 1)
- {
index++;
- }
else if (pager->wrap_workspaces)
- {
- index = 0;
- }
+ index = 0;
break;
case GDK_SCROLL_UP:
if (index - n_columns >= 0)
- {
index -= n_columns;
- }
else if (index > 0)
- {
index = ((pager->n_rows - 1) * n_columns) + (index % n_columns) - 1;
- }
else if (pager->wrap_workspaces)
- {
index = n_workspaces - 1;
- }
if (index >= n_workspaces)
index -= n_columns;
@@ -695,24 +489,16 @@ static gboolean applet_scroll(MatePanelApplet* applet, GdkEventScroll* event, Pa
case GDK_SCROLL_LEFT:
if (index > 0)
- {
index--;
- }
else if (pager->wrap_workspaces)
- {
index = n_workspaces - 1;
- }
break;
+
default:
- g_assert_not_reached();
- break;
+ return FALSE;
}
- if (pager->screen)
- {
- wnck_workspace_activate(wnck_screen_get_workspace(pager->screen, index), event->time);
- }
-#endif /* HAVE_X11 */
+ xfw_pager_activate_nth (pager->pager, index);
return TRUE;
}
@@ -747,17 +533,9 @@ static const GtkActionEntry pager_menu_actions[] = {
static void num_rows_changed(GSettings* settings, gchar* key, PagerData* pager)
{
int n_rows;
- int ws_count = MAX_REASONABLE_ROWS;
-
-#ifdef HAVE_X11
- if (pager->screen)
- ws_count = wnck_screen_get_workspace_count (pager->screen);
-#endif /* HAVE_X11 */
-
-#ifdef HAVE_WAYLAND
- if (pager->is_wayland)
- ws_count = wayland_workspace_get_count (pager->pager);
-#endif /* HAVE_WAYLAND */
+ /* Guard against an empty workspace list so the clamp below cannot produce
+ * zero rows, which would divide by zero when handling scroll events. */
+ int ws_count = MAX (xfw_pager_get_count (pager->pager), 1);
n_rows = CLAMP (g_settings_get_int (settings, key),
1,
@@ -886,42 +664,17 @@ gboolean workspace_switcher_applet_fill(MatePanelApplet* applet)
break;
}
-#ifdef HAVE_X11
- if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
- {
- pager->wnck_handle = wnck_handle_new(WNCK_CLIENT_TYPE_PAGER);
- pager->pager = wnck_pager_new_with_handle(pager->wnck_handle);
- wnck_pager_set_shadow_type(WNCK_PAGER(pager->pager), GTK_SHADOW_IN);
- }
- else
-#endif /* HAVE_X11 */
-
-#ifdef HAVE_WAYLAND
- if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ()))
- {
- pager->is_wayland = TRUE;
- pager->pager = wayland_workspace_new();
- }
- else
-#endif /* HAVE_WAYLAND */
-
- {
- pager->pager = gtk_label_new ("[Pager not supported on this platform]");
- }
-
- pager->wm = PAGER_WM_UNKNOWN;
+pager->pager = xfw_pager_new ();
GtkStyleContext *context;
context = gtk_widget_get_style_context (GTK_WIDGET (applet));
gtk_style_context_add_class (context, "wnck-applet");
- context = gtk_widget_get_style_context (pager->pager);
- gtk_style_context_add_class (context, "wnck-pager");
g_signal_connect (pager->pager, "destroy",
G_CALLBACK (destroy_pager),
pager);
- /* overwrite default WnckPager widget scroll-event */
+ /* replace the pager widget's default scroll handling */
g_signal_connect (pager->pager, "scroll-event",
G_CALLBACK (applet_scroll),
pager);
@@ -1031,40 +784,9 @@ static void num_rows_value_changed(GtkSpinButton* button, PagerData* pager)
g_settings_set_int(pager->settings, "num-rows", gtk_spin_button_get_value_as_int(button));
}
-static const char *get_workspace_name(PagerData* pager, int workspace_index)
-{
-#ifdef HAVE_X11
- if (pager->screen)
- {
- WnckWorkspace *workspace = wnck_screen_get_workspace(pager->screen, workspace_index);
- return wnck_workspace_get_name(workspace);
- }
-#endif /* HAVE_X11 */
-
-#ifdef HAVE_WAYLAND
- if (pager->is_wayland)
- {
- return wayland_workspace_get_name(pager->pager, workspace_index);
- }
-#endif /* HAVE_WAYLAND */
-
- /* Fallback */
- return "workspace";
-}
-
static void update_workspaces_model(PagerData* pager)
{
- int nr_ws = 1;
-
-#ifdef HAVE_X11
- if (pager->screen)
- nr_ws = wnck_screen_get_workspace_count (pager->screen);
-#endif /* HAVE_X11 */
-
-#ifdef HAVE_WAYLAND
- if (pager->is_wayland)
- nr_ws = wayland_workspace_get_count (pager->pager);
-#endif /* HAVE_WAYLAND */
+ int nr_ws = xfw_pager_get_count (pager->pager);
if (nr_ws == 0)
nr_ws = 1;
@@ -1082,92 +804,101 @@ static void update_workspaces_model(PagerData* pager)
for (i = 0; i < nr_ws; i++)
{
gtk_list_store_append(pager->workspaces_store, &iter);
- const char* name = get_workspace_name(pager, i);
- gtk_list_store_set(pager->workspaces_store, &iter, 0, name, -1);
+ gtk_list_store_set(pager->workspaces_store, &iter,
+ 0, xfw_pager_get_name (pager->pager, i), -1);
}
}
}
-#ifdef HAVE_X11
-static void workspace_renamed(WnckWorkspace* space, PagerData* pager)
-{
- GtkTreeIter iter;
-
- g_return_if_fail(WNCK_IS_WORKSPACE(space));
-
- if (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (pager->workspaces_store), &iter, NULL, wnck_workspace_get_number (space)))
- gtk_list_store_set(pager->workspaces_store, &iter, 0, wnck_workspace_get_name(space), -1);
-}
-
-static void workspace_created(WnckScreen* screen, WnckWorkspace* space, PagerData* pager)
-{
- g_return_if_fail(WNCK_IS_SCREEN(screen));
-
- update_workspaces_model(pager);
-
- wncklet_connect_while_alive(space, "name_changed", G_CALLBACK(workspace_renamed), pager, pager->properties_dialog);
-}
-
-static void workspace_destroyed(WnckScreen* screen, WnckWorkspace* space, PagerData* pager)
+static void
+on_workspace_count_changed (XfwWorkspaceManager *manager,
+ XfwWorkspace *workspace,
+ PagerData *pager)
{
- g_return_if_fail(WNCK_IS_SCREEN(screen));
- update_workspaces_model(pager);
+ update_workspaces_model (pager);
}
-#endif /* HAVE_X11 */
static void
on_num_workspaces_value_changed (GtkSpinButton *button,
PagerData *pager)
{
-#ifdef HAVE_X11
- if (pager->screen)
- {
- int workspace_count = gtk_spin_button_get_value_as_int (button);
- wnck_screen_change_workspace_count(pager->screen, workspace_count);
- if (workspace_count < pager->n_rows)
- g_settings_set_int (pager->settings, "num-rows", workspace_count);
- }
-#endif /* HAVE_X11 */
-}
+ XfwWorkspaceGroup *group;
+ int requested;
+ int current;
+ GError *error = NULL;
-static gboolean workspaces_tree_focused_out(GtkTreeView* treeview, GdkEventFocus* event, PagerData* pager)
-{
- GtkTreeSelection* selection;
+ requested = gtk_spin_button_get_value_as_int (button);
+ current = xfw_pager_get_count (pager->pager);
- selection = gtk_tree_view_get_selection(treeview);
- gtk_tree_selection_unselect_all(selection);
- return TRUE;
-}
+ if (requested == current || requested < 1)
+ return;
-static void workspace_name_edited(GtkCellRendererText* cell_renderer_text, const gchar* path, const gchar* new_text, PagerData* pager)
-{
-#ifdef HAVE_X11
- if (pager->screen)
+ group = pager_get_workspace_group ();
+ if (!group)
+ return;
+
+ if (requested > current)
{
- const gint* indices;
- WnckWorkspace* workspace;
- GtkTreePath* p;
+ int i;
- p = gtk_tree_path_new_from_string(path);
- indices = gtk_tree_path_get_indices(p);
- workspace = wnck_screen_get_workspace(pager->screen, indices[0]);
+ if (!pager_group_can_create_workspaces (group))
+ return;
- if (workspace != NULL)
+ for (i = current; i < requested; i++)
{
- gchar* temp_name = g_strdup(new_text);
+ char *name = g_strdup_printf ("%d", i + 1);
+ gboolean created = xfw_workspace_group_create_workspace (group, name, &error);
- wnck_workspace_change_name(workspace, g_strstrip(temp_name));
+ g_free (name);
- g_free(temp_name);
+ if (!created)
+ {
+ g_warning ("Failed to create workspace: %s",
+ error ? error->message : "unknown error");
+ g_clear_error (&error);
+ break;
+ }
}
- else
+ }
+ else
+ {
+ GList *workspaces = xfw_workspace_group_list_workspaces (group);
+ GList *l = g_list_last (workspaces);
+
+ while (current > requested && l != NULL)
{
- g_warning("Edited name of workspace %d which no longer exists", indices[0]);
- }
+ XfwWorkspace *workspace = l->data;
- gtk_tree_path_free(p);
+ if ((xfw_workspace_get_capabilities (workspace) &
+ XFW_WORKSPACE_CAPABILITIES_REMOVE) != 0)
+ {
+ if (!xfw_workspace_remove (workspace, &error))
+ {
+ g_warning ("Failed to remove workspace: %s",
+ error ? error->message : "unknown error");
+ g_clear_error (&error);
+ break;
+ }
+ }
+
+ current--;
+ l = l->prev;
+ }
}
-#endif
+
+ if (requested < pager->n_rows)
+ g_settings_set_int (pager->settings, "num-rows", requested);
+
+ update_workspaces_model (pager);
+}
+
+static gboolean workspaces_tree_focused_out(GtkTreeView* treeview, GdkEventFocus* event, PagerData* pager)
+{
+ GtkTreeSelection* selection;
+
+ selection = gtk_tree_view_get_selection(treeview);
+ gtk_tree_selection_unselect_all(selection);
+ return TRUE;
}
static void properties_dialog_destroyed(GtkWidget* widget, PagerData* pager)
@@ -1266,6 +997,7 @@ static void setup_dialog(GtkBuilder* builder, PagerData* pager)
GtkCellRenderer* cell;
GSettings *marco_general_settings = NULL;
GSettings *marco_workspaces_settings = NULL;
+ XfwWorkspaceManager *manager;
if (mate_gsettings_schema_exists(MARCO_GENERAL_SCHEMA))
marco_general_settings = g_settings_new (MARCO_GENERAL_SCHEMA);
@@ -1354,22 +1086,17 @@ static void setup_dialog(GtkBuilder* builder, PagerData* pager)
g_signal_connect(WID("done_button"), "clicked", (GCallback) close_dialog, pager);
-#ifdef HAVE_X11
- if (pager->screen)
+ /* Keep the workspace count and name list in sync with the window manager. */
+ manager = pager_get_workspace_manager ();
+ if (manager)
{
- int i, nr_ws;
- gtk_spin_button_set_value(GTK_SPIN_BUTTON(pager->num_workspaces_spin), wnck_screen_get_workspace_count(pager->screen));
- wncklet_connect_while_alive(pager->screen, "workspace_created", G_CALLBACK(workspace_created), pager, pager->properties_dialog);
- wncklet_connect_while_alive(pager->screen, "workspace_destroyed", G_CALLBACK(workspace_destroyed), pager, pager->properties_dialog);
-
- nr_ws = wnck_screen_get_workspace_count(pager->screen);
-
- for (i = 0; i < nr_ws; i++)
- {
- wncklet_connect_while_alive(G_OBJECT(wnck_screen_get_workspace(pager->screen, i)), "name_changed", G_CALLBACK(workspace_renamed), pager, pager->properties_dialog);
- }
+ wncklet_connect_while_alive (manager, "workspace-created",
+ G_CALLBACK (on_workspace_count_changed),
+ pager, pager->properties_dialog);
+ wncklet_connect_while_alive (manager, "workspace-destroyed",
+ G_CALLBACK (on_workspace_count_changed),
+ pager, pager->properties_dialog);
}
-#endif /* HAVE_X11 */
g_signal_connect (pager->num_workspaces_spin, "value-changed",
G_CALLBACK (on_num_workspaces_value_changed),
@@ -1385,13 +1112,12 @@ static void setup_dialog(GtkBuilder* builder, PagerData* pager)
g_object_unref(pager->workspaces_store);
- cell = g_object_new(GTK_TYPE_CELL_RENDERER_TEXT, "editable", TRUE, NULL);
+ cell = g_object_new(GTK_TYPE_CELL_RENDERER_TEXT, NULL);
pager->cell = cell;
column = gtk_tree_view_column_new_with_attributes("workspace", cell, "text", 0, NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(pager->workspaces_tree), column);
- g_signal_connect(cell, "edited", (GCallback) workspace_name_edited, pager);
- update_properties_for_wm(pager);
+ update_properties_for_capabilities(pager);
}
static void display_properties_dialog(GtkAction* action, PagerData* pager)
@@ -1427,14 +1153,16 @@ static void destroy_pager(GtkWidget* widget, PagerData* pager)
if (pager->properties_dialog)
gtk_widget_destroy(pager->properties_dialog);
-#ifdef HAVE_X11
- g_clear_object(&pager->wnck_handle);
-#endif
+ /* the applet is normally unrealized first, but make sure we never leave a
+ * handler pointing at freed data */
+ if (pager->screen_handler != 0)
+ {
+ XfwScreen *screen = xfw_screen_get_default ();
-#ifdef HAVE_WAYLAND
- /* The Wayland pager widget cleanup is handled by the
- * destroy notify on the outer_box's g_object_set_data */
-#endif
+ if (screen)
+ g_signal_handler_disconnect (screen, pager->screen_handler);
+ pager->screen_handler = 0;
+ }
g_free(pager);
}
diff --git a/applets/wncklet/workspace-switcher.h b/applets/wncklet/workspace-switcher.h
index 7d7dead58..d6bb69f7f 100644
--- a/applets/wncklet/workspace-switcher.h
+++ b/applets/wncklet/workspace-switcher.h
@@ -1,5 +1,5 @@
/*
- * libwnck based pager Apple.
+ * libxfce4windowing based pager Applet.
* (C) 2001 Alexander Larsson
*
* Authors: Alexander Larsson
diff --git a/applets/wncklet/x11-backend.c b/applets/wncklet/x11-backend.c
new file mode 100644
index 000000000..cd009344f
--- /dev/null
+++ b/applets/wncklet/x11-backend.c
@@ -0,0 +1,357 @@
+/* Wncklet applet X11 backend */
+
+/*
+ * Copyright (C) 2019 William Wold
+ * Copyright (C) 2026 Aleksey Samoilov
+ *
+ * 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 St, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/* Like wayland-backend.c, this file only implements the windowing-system
+ * half of the tasklist. All of the widget logic lives in tasklist-core.c and
+ * is shared with the other backends.
+ *
+ * X11 goes through libxfce4windowing as well, which implements its X11
+ * support on top of libwnck. Going through xfw rather than talking to libwnck
+ * directly is what lets the two backends share the same core: window state,
+ * workspaces, icons and actions all have a common vocabulary.
+ *
+ * One thing xfw has no API for is grabbing a window's pixels, so window
+ * previews stay in window-list.c and reach the XID through
+ * xfw_window_x11_get_xid().
+ */
+
+#include
+
+#ifndef HAVE_X11
+#error file should only be compiled when HAVE_X11 is enabled
+#endif
+
+#include
+#include
+#include
+#include
+
+#include "tasklist-backend.h"
+#include "x11-backend.h"
+
+static gpointer
+x11_get_screen (void)
+{
+ return xfw_screen_get_default ();
+}
+
+static GList *
+x11_list_windows (gpointer screen)
+{
+ return xfw_screen_get_windows (screen);
+}
+
+static gpointer
+x11_get_active_window (gpointer screen)
+{
+ return xfw_screen_get_active_window (screen);
+}
+
+static const gchar *
+x11_get_window_name (gpointer window)
+{
+ return xfw_window_get_name (window);
+}
+
+static GIcon *
+x11_get_window_gicon (gpointer window)
+{
+ return xfw_window_get_gicon (window);
+}
+
+static GdkPixbuf *
+x11_get_window_pixbuf (gpointer window, gint size, gint scale)
+{
+ return xfw_window_get_icon (window, size, scale);
+}
+
+static TasklistWindowState
+x11_get_window_state (gpointer window)
+{
+ XfwWindowState state;
+ TasklistWindowState result = 0;
+
+ state = xfw_window_get_state (window);
+
+ if (state & XFW_WINDOW_STATE_ACTIVE)
+ result |= TASKLIST_STATE_ACTIVE;
+ if (state & XFW_WINDOW_STATE_MAXIMIZED)
+ result |= TASKLIST_STATE_MAXIMIZED;
+ if (state & XFW_WINDOW_STATE_MINIMIZED)
+ result |= TASKLIST_STATE_MINIMIZED;
+ if (state & XFW_WINDOW_STATE_FULLSCREEN)
+ result |= TASKLIST_STATE_FULLSCREEN;
+ if (state & XFW_WINDOW_STATE_URGENT)
+ result |= TASKLIST_STATE_URGENT;
+
+ return result;
+}
+
+static gpointer
+x11_get_window_application (gpointer window)
+{
+ return xfw_window_get_application (window);
+}
+
+static gulong
+x11_get_window_id (gpointer window)
+{
+ /* X11 really does have a window id, and the drag-and-drop payload and
+ * the window previews both want it */
+ return (gulong) xfw_window_x11_get_xid (window);
+}
+
+static const gchar *
+x11_get_app_name (gpointer app)
+{
+ return xfw_application_get_name (app);
+}
+
+static GIcon *
+x11_get_app_gicon (gpointer app)
+{
+ return xfw_application_get_gicon (app);
+}
+
+static const gchar *
+x11_get_fallback_icon_name (void)
+{
+ return "unknown";
+}
+
+/* xfw reports a single workspace per window, so the active one is the active
+ * workspace of the group that window's workspace belongs to. */
+static gboolean
+x11_window_is_on_active_workspace (gpointer window)
+{
+ XfwWorkspace *workspace, *active;
+ XfwWorkspaceGroup *group;
+
+ workspace = xfw_window_get_workspace (window);
+ if (workspace == NULL)
+ return TRUE;
+
+ group = xfw_workspace_get_workspace_group (workspace);
+ if (group == NULL)
+ return TRUE;
+
+ active = xfw_workspace_group_get_active_workspace (group);
+ if (active == NULL)
+ return TRUE;
+
+ return xfw_window_is_on_workspace (window, active);
+}
+
+static void
+x11_window_move_to_workspace (gpointer window)
+{
+ XfwWorkspace *workspace;
+ GError *error = NULL;
+
+ workspace = xfw_window_get_workspace (window);
+ if (workspace == NULL)
+ return;
+
+ if (!xfw_workspace_activate (workspace, &error))
+ g_clear_error (&error);
+}
+
+static void
+x11_activate_window (gpointer window, guint32 user_time)
+{
+ xfw_window_activate (window, NULL, user_time, NULL);
+}
+
+static void
+x11_close_window (gpointer window, guint32 timestamp)
+{
+ xfw_window_close (window, timestamp, NULL);
+}
+
+static void
+x11_set_window_maximized (gpointer window, gboolean maximized)
+{
+ xfw_window_set_maximized (window, maximized, NULL);
+}
+
+static void
+x11_set_window_minimized (gpointer window, gboolean minimized)
+{
+ xfw_window_set_minimized (window, minimized, NULL);
+}
+
+/* The core widget is passed in as the signal user data, so that the core can
+ * stop every handler it owns with a single
+ * g_signal_handlers_disconnect_by_data() on itself. */
+
+static void
+x11_screen_window_opened (XfwScreen *screen, XfwWindow *window, gpointer core)
+{
+ tasklist_core_window_added (GTK_WIDGET (core), window);
+}
+
+static void
+x11_screen_window_closed (XfwScreen *screen, XfwWindow *window, gpointer core)
+{
+ tasklist_core_window_removed (GTK_WIDGET (core), window);
+}
+
+static void
+x11_screen_active_window_changed (XfwScreen *screen,
+ XfwWindow *previous_window,
+ gpointer core)
+{
+ tasklist_core_active_window_changed (GTK_WIDGET (core));
+}
+
+static void
+x11_window_state_changed (XfwWindow *window,
+ XfwWindowState changed_mask,
+ XfwWindowState new_state,
+ gpointer core)
+{
+ tasklist_core_window_state_changed (GTK_WIDGET (core), window);
+}
+
+static void
+x11_window_name_changed (XfwWindow *window, gpointer core)
+{
+ tasklist_core_window_name_changed (GTK_WIDGET (core), window);
+}
+
+static void
+x11_window_icon_changed (XfwWindow *window, gpointer core)
+{
+ tasklist_core_window_icon_changed (GTK_WIDGET (core), window);
+}
+
+static void
+x11_window_application_changed (XfwWindow *window,
+ GParamSpec *pspec,
+ gpointer core)
+{
+ tasklist_core_window_application_changed (GTK_WIDGET (core), window);
+}
+
+static void
+x11_app_icon_changed (XfwApplication *app, gpointer core)
+{
+ tasklist_core_app_icon_changed (GTK_WIDGET (core), app);
+}
+
+static void
+x11_app_name_changed (XfwApplication *app, GParamSpec *pspec, gpointer core)
+{
+ tasklist_core_app_name_changed (GTK_WIDGET (core), app);
+}
+
+static void
+x11_set_screen_tracking (GtkWidget *core, gpointer screen, gboolean track)
+{
+ if (track)
+ {
+ g_signal_connect (screen, "window-opened",
+ G_CALLBACK (x11_screen_window_opened), core);
+ g_signal_connect (screen, "window-closed",
+ G_CALLBACK (x11_screen_window_closed), core);
+ g_signal_connect (screen, "active-window-changed",
+ G_CALLBACK (x11_screen_active_window_changed), core);
+ }
+ else
+ {
+ g_signal_handlers_disconnect_by_data (screen, core);
+ }
+}
+
+static void
+x11_set_window_tracking (GtkWidget *core, gpointer window, gboolean track)
+{
+ if (track)
+ {
+ g_signal_connect (window, "state-changed",
+ G_CALLBACK (x11_window_state_changed), core);
+ g_signal_connect (window, "name-changed",
+ G_CALLBACK (x11_window_name_changed), core);
+ g_signal_connect (window, "icon-changed",
+ G_CALLBACK (x11_window_icon_changed), core);
+ g_signal_connect (window, "notify::application",
+ G_CALLBACK (x11_window_application_changed), core);
+ }
+ else
+ {
+ g_signal_handlers_disconnect_by_data (window, core);
+ }
+}
+
+static void
+x11_set_app_tracking (GtkWidget *core, gpointer app, gboolean track)
+{
+ if (track)
+ {
+ g_signal_connect (app, "icon-changed",
+ G_CALLBACK (x11_app_icon_changed), core);
+ g_signal_connect (app, "notify::name",
+ G_CALLBACK (x11_app_name_changed), core);
+ }
+ else
+ {
+ g_signal_handlers_disconnect_by_data (app, core);
+ }
+}
+
+static const TasklistBackend x11_backend = {
+ .get_screen = x11_get_screen,
+ .list_windows = x11_list_windows,
+ .get_active_window = x11_get_active_window,
+ .get_window_name = x11_get_window_name,
+ .get_window_gicon = x11_get_window_gicon,
+ .get_window_pixbuf = x11_get_window_pixbuf,
+ .get_window_state = x11_get_window_state,
+ .get_window_application = x11_get_window_application,
+ .get_window_id = x11_get_window_id,
+ .get_app_name = x11_get_app_name,
+ .get_app_gicon = x11_get_app_gicon,
+ .get_fallback_icon_name = x11_get_fallback_icon_name,
+ .window_is_on_active_workspace = x11_window_is_on_active_workspace,
+ .window_move_to_workspace = x11_window_move_to_workspace,
+ .activate_window = x11_activate_window,
+ .close_window = x11_close_window,
+ .set_window_maximized = x11_set_window_maximized,
+ .set_window_minimized = x11_set_window_minimized,
+ .set_screen_tracking = x11_set_screen_tracking,
+ .set_window_tracking = x11_set_window_tracking,
+ .set_app_tracking = x11_set_app_tracking,
+};
+
+const TasklistBackend *
+x11_tasklist_backend (void)
+{
+ GdkDisplay *display;
+
+ display = gdk_display_get_default ();
+ if (display == NULL || !GDK_IS_X11_DISPLAY (display))
+ return NULL;
+
+ xfw_set_client_type (XFW_CLIENT_TYPE_PAGER);
+
+ return &x11_backend;
+}
diff --git a/applets/wncklet/x11-backend.h b/applets/wncklet/x11-backend.h
new file mode 100644
index 000000000..a0c8d1a68
--- /dev/null
+++ b/applets/wncklet/x11-backend.h
@@ -0,0 +1,16 @@
+/* Wncklet applet X11 backend */
+
+#ifndef _WNCKLET_APPLET_X11_BACKEND_H_
+#define _WNCKLET_APPLET_X11_BACKEND_H_
+
+#include "tasklist-backend.h"
+
+G_BEGIN_DECLS
+
+/* Returns NULL unless the default GdkDisplay is an X11 one, so that the
+ * caller can pick a backend by display type rather than at compile time. */
+const TasklistBackend *x11_tasklist_backend (void);
+
+G_END_DECLS
+
+#endif /* _WNCKLET_APPLET_X11_BACKEND_H_ */
diff --git a/applets/wncklet/wayland-workspace.c b/applets/wncklet/xfw-pager.c
similarity index 80%
rename from applets/wncklet/wayland-workspace.c
rename to applets/wncklet/xfw-pager.c
index 646ab8024..7f66ac2a5 100644
--- a/applets/wncklet/wayland-workspace.c
+++ b/applets/wncklet/xfw-pager.c
@@ -22,7 +22,7 @@
#include
#include
-#include "wayland-workspace.h"
+#include "xfw-pager.h"
typedef struct _GroupSignal GroupSignal;
@@ -31,9 +31,9 @@ struct _GroupSignal {
gulong handlers[3]; /* active-changed, added, removed */
};
-typedef struct _WaylandWorkspaceData WaylandWorkspaceData;
+typedef struct _XfwPagerData XfwPagerData;
-struct _WaylandWorkspaceData {
+struct _XfwPagerData {
GtkWidget *grid;
XfwScreen *screen;
@@ -47,12 +47,12 @@ struct _WaylandWorkspaceData {
gboolean ignore_toggle;
};
-static void rebuild_ui (WaylandWorkspaceData *data);
+static void rebuild_ui (XfwPagerData *data);
/* ---- Helpers ---- */
static int
-count_visible_workspaces (WaylandWorkspaceData *data)
+count_visible_workspaces (XfwPagerData *data)
{
GList *workspaces, *l;
int n = 0;
@@ -72,7 +72,7 @@ count_visible_workspaces (WaylandWorkspaceData *data)
}
static XfwWorkspace *
-get_nth_workspace (WaylandWorkspaceData *data, int index)
+get_nth_workspace (XfwPagerData *data, int index)
{
GList *workspaces, *l;
int i = 0;
@@ -98,7 +98,7 @@ get_nth_workspace (WaylandWorkspaceData *data, int index)
/* ---- Build / rebuild the button grid ---- */
static void
-on_button_toggled (GtkToggleButton *button, WaylandWorkspaceData *data)
+on_button_toggled (GtkToggleButton *button, XfwPagerData *data)
{
int index;
@@ -130,7 +130,7 @@ on_button_toggled (GtkToggleButton *button, WaylandWorkspaceData *data)
}
static void
-rebuild_ui (WaylandWorkspaceData *data)
+rebuild_ui (XfwPagerData *data)
{
GList *children, *l;
int n_spaces, i, col, row;
@@ -223,7 +223,7 @@ rebuild_ui (WaylandWorkspaceData *data)
static void
on_active_workspace_changed (XfwWorkspaceGroup *group,
XfwWorkspace *previous,
- WaylandWorkspaceData *data)
+ XfwPagerData *data)
{
rebuild_ui (data);
}
@@ -231,7 +231,7 @@ on_active_workspace_changed (XfwWorkspaceGroup *group,
static void
on_workspace_added (XfwWorkspaceGroup *group,
XfwWorkspace *workspace,
- WaylandWorkspaceData *data)
+ XfwPagerData *data)
{
rebuild_ui (data);
}
@@ -239,7 +239,7 @@ on_workspace_added (XfwWorkspaceGroup *group,
static void
on_workspace_removed (XfwWorkspaceGroup *group,
XfwWorkspace *workspace,
- WaylandWorkspaceData *data)
+ XfwPagerData *data)
{
rebuild_ui (data);
}
@@ -247,7 +247,7 @@ on_workspace_removed (XfwWorkspaceGroup *group,
/* ---- Group signal management ---- */
static void
-disconnect_group_signals (WaylandWorkspaceData *data)
+disconnect_group_signals (XfwPagerData *data)
{
for (GList *l = data->group_signals; l != NULL; l = l->next)
{
@@ -263,7 +263,7 @@ disconnect_group_signals (WaylandWorkspaceData *data)
}
static void
-connect_group_signals (WaylandWorkspaceData *data)
+connect_group_signals (XfwPagerData *data)
{
GList *groups, *l;
@@ -293,7 +293,7 @@ connect_group_signals (WaylandWorkspaceData *data)
static void
on_workspace_group_created (XfwWorkspaceManager *manager,
XfwWorkspaceGroup *group,
- WaylandWorkspaceData *data)
+ XfwPagerData *data)
{
connect_group_signals (data);
rebuild_ui (data);
@@ -302,7 +302,7 @@ on_workspace_group_created (XfwWorkspaceManager *manager,
static void
on_workspace_group_destroyed (XfwWorkspaceManager *manager,
XfwWorkspaceGroup *group,
- WaylandWorkspaceData *data)
+ XfwPagerData *data)
{
connect_group_signals (data);
rebuild_ui (data);
@@ -311,7 +311,7 @@ on_workspace_group_destroyed (XfwWorkspaceManager *manager,
static void
on_workspace_created (XfwWorkspaceManager *manager,
XfwWorkspace *workspace,
- WaylandWorkspaceData *data)
+ XfwPagerData *data)
{
rebuild_ui (data);
}
@@ -319,7 +319,7 @@ on_workspace_created (XfwWorkspaceManager *manager,
static void
on_workspace_destroyed (XfwWorkspaceManager *manager,
XfwWorkspace *workspace,
- WaylandWorkspaceData *data)
+ XfwPagerData *data)
{
rebuild_ui (data);
}
@@ -327,7 +327,7 @@ on_workspace_destroyed (XfwWorkspaceManager *manager,
/* ---- Cleanup ---- */
static void
-wayland_pager_data_free (WaylandWorkspaceData *data)
+xfw_pager_data_free (XfwPagerData *data)
{
disconnect_group_signals (data);
g_free (data);
@@ -336,13 +336,13 @@ wayland_pager_data_free (WaylandWorkspaceData *data)
/* ---- Public API ---- */
GtkWidget *
-wayland_workspace_new (void)
+xfw_pager_new (void)
{
- WaylandWorkspaceData *data;
+ XfwPagerData *data;
xfw_set_client_type (XFW_CLIENT_TYPE_PAGER);
- data = g_new0 (WaylandWorkspaceData, 1);
+ data = g_new0 (XfwPagerData, 1);
data->orientation = GTK_ORIENTATION_HORIZONTAL;
data->n_rows = 1;
data->display_all = TRUE;
@@ -356,9 +356,9 @@ wayland_workspace_new (void)
gtk_style_context_add_class (context, "wnck-pager");
g_object_set_data_full (G_OBJECT (data->grid),
- "wayland_pager_data",
+ "xfw_pager_data",
data,
- (GDestroyNotify) wayland_pager_data_free);
+ (GDestroyNotify) xfw_pager_data_free);
data->screen = xfw_screen_get_default ();
if (!data->screen)
@@ -390,16 +390,16 @@ wayland_workspace_new (void)
return data->grid;
}
-static WaylandWorkspaceData *
+static XfwPagerData *
pager_widget_get_data (GtkWidget *pager_widget)
{
- return g_object_get_data (G_OBJECT (pager_widget), "wayland_pager_data");
+ return g_object_get_data (G_OBJECT (pager_widget), "xfw_pager_data");
}
void
-wayland_workspace_set_orientation (GtkWidget *pager_widget, GtkOrientation orientation)
+xfw_pager_set_orientation (GtkWidget *pager_widget, GtkOrientation orientation)
{
- WaylandWorkspaceData *data = pager_widget_get_data (pager_widget);
+ XfwPagerData *data = pager_widget_get_data (pager_widget);
g_return_if_fail (data);
if (data->orientation == orientation)
@@ -410,9 +410,9 @@ wayland_workspace_set_orientation (GtkWidget *pager_widget, GtkOrientation orien
}
void
-wayland_workspace_set_rows (GtkWidget *pager_widget, int n_rows)
+xfw_pager_set_rows (GtkWidget *pager_widget, int n_rows)
{
- WaylandWorkspaceData *data = pager_widget_get_data (pager_widget);
+ XfwPagerData *data = pager_widget_get_data (pager_widget);
g_return_if_fail (data);
n_rows = CLAMP (n_rows, 1, 16);
@@ -425,9 +425,9 @@ wayland_workspace_set_rows (GtkWidget *pager_widget, int n_rows)
}
void
-wayland_workspace_set_show_all (GtkWidget *pager_widget, gboolean show_all)
+xfw_pager_set_show_all (GtkWidget *pager_widget, gboolean show_all)
{
- WaylandWorkspaceData *data = pager_widget_get_data (pager_widget);
+ XfwPagerData *data = pager_widget_get_data (pager_widget);
g_return_if_fail (data);
if (data->display_all == show_all)
@@ -438,9 +438,9 @@ wayland_workspace_set_show_all (GtkWidget *pager_widget, gboolean show_all)
}
void
-wayland_workspace_set_show_names (GtkWidget *pager_widget, gboolean show_names)
+xfw_pager_set_show_names (GtkWidget *pager_widget, gboolean show_names)
{
- WaylandWorkspaceData *data = pager_widget_get_data (pager_widget);
+ XfwPagerData *data = pager_widget_get_data (pager_widget);
g_return_if_fail (data);
if (data->display_names == show_names)
@@ -451,29 +451,42 @@ wayland_workspace_set_show_names (GtkWidget *pager_widget, gboolean show_names)
}
int
-wayland_workspace_get_count (GtkWidget *pager_widget)
+xfw_pager_get_count (GtkWidget *pager_widget)
{
- WaylandWorkspaceData *data = pager_widget_get_data (pager_widget);
+ XfwPagerData *data = pager_widget_get_data (pager_widget);
g_return_val_if_fail (data, 0);
return g_list_length (xfw_workspace_manager_list_workspaces (data->workspace_manager));
}
+/* Names are indexed against the full workspace list, like
+ * xfw_pager_get_count(), rather than the subset of workspaces the pager
+ * buttons are built from. This is what a workspace inventory needs when the
+ * "show all workspaces" option is off and some workspaces are hidden. */
const char *
-wayland_workspace_get_name (GtkWidget *pager_widget, int index)
+xfw_pager_get_name (GtkWidget *pager_widget, int index)
{
- WaylandWorkspaceData *data = pager_widget_get_data (pager_widget);
+ XfwPagerData *data = pager_widget_get_data (pager_widget);
XfwWorkspace *ws;
+ const char *name;
+ GList *workspaces;
+ int i = 0;
g_return_val_if_fail (data, "workspace");
- ws = get_nth_workspace (data, index);
- if (ws)
+ workspaces = xfw_workspace_manager_list_workspaces (data->workspace_manager);
+
+ for (; workspaces != NULL; workspaces = workspaces->next, i++)
{
- const char *name = xfw_workspace_get_name (ws);
+ if (i != index)
+ continue;
+
+ ws = workspaces->data;
+ name = xfw_workspace_get_name (ws);
if (name && name[0] != '\0')
return name;
+
return xfw_workspace_get_id (ws);
}
@@ -481,9 +494,9 @@ wayland_workspace_get_name (GtkWidget *pager_widget, int index)
}
int
-wayland_workspace_get_active_index (GtkWidget *pager_widget)
+xfw_pager_get_active_index (GtkWidget *pager_widget)
{
- WaylandWorkspaceData *data = pager_widget_get_data (pager_widget);
+ XfwPagerData *data = pager_widget_get_data (pager_widget);
GList *workspaces, *l;
int i = 0;
@@ -505,9 +518,9 @@ wayland_workspace_get_active_index (GtkWidget *pager_widget)
}
void
-wayland_workspace_activate_nth (GtkWidget *pager_widget, int index)
+xfw_pager_activate_nth (GtkWidget *pager_widget, int index)
{
- WaylandWorkspaceData *data = pager_widget_get_data (pager_widget);
+ XfwPagerData *data = pager_widget_get_data (pager_widget);
XfwWorkspace *ws;
g_return_if_fail (data);
diff --git a/applets/wncklet/xfw-pager.h b/applets/wncklet/xfw-pager.h
new file mode 100644
index 000000000..5ddc8a7d2
--- /dev/null
+++ b/applets/wncklet/xfw-pager.h
@@ -0,0 +1,52 @@
+/* Wncklet applet workspace pager backed by libxfce4windowing */
+
+/*
+ * Copyright (C) 2026 MATE Desktop Team
+ *
+ * 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 St, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef _WNCKLET_APPLET_XFW_PAGER_H_
+#define _WNCKLET_APPLET_XFW_PAGER_H_
+
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+GtkWidget* xfw_pager_new (void);
+void xfw_pager_set_orientation(GtkWidget *pager_widget,
+ GtkOrientation orientation);
+void xfw_pager_set_rows (GtkWidget *pager_widget,
+ int n_rows);
+void xfw_pager_set_show_all (GtkWidget *pager_widget,
+ gboolean show_all);
+void xfw_pager_set_show_names (GtkWidget *pager_widget,
+ gboolean show_names);
+int xfw_pager_get_count (GtkWidget *pager_widget);
+const char* xfw_pager_get_name (GtkWidget *pager_widget,
+ int index);
+int xfw_pager_get_active_index (GtkWidget *pager_widget);
+void xfw_pager_activate_nth (GtkWidget *pager_widget,
+ int index);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _WNCKLET_APPLET_XFW_PAGER_H_ */
\ No newline at end of file
diff --git a/applets/wncklet/wayland-window-menu.c b/applets/wncklet/xfw-window-menu.c
similarity index 93%
rename from applets/wncklet/wayland-window-menu.c
rename to applets/wncklet/xfw-window-menu.c
index 5080db15b..145fd0771 100644
--- a/applets/wncklet/wayland-window-menu.c
+++ b/applets/wncklet/xfw-window-menu.c
@@ -1,4 +1,4 @@
-/* Wayland backend for the Window Selector applet */
+/* libxfce4windowing backend for the Window Selector applet */
/*
* Copyright (C) 2026 MATE Desktop Team
@@ -21,16 +21,11 @@
#include
-#ifndef HAVE_WAYLAND
-#error file should only be compiled when HAVE_WAYLAND is enabled
-#endif
-
#include
-#include
#include
#include
-#include "wayland-window-menu.h"
+#include "xfw-window-menu.h"
#define SELECTOR_MAX_WIDTH 50
@@ -49,14 +44,14 @@ typedef struct {
GHashTable *window_hash;
XfwScreen *screen;
XfwWorkspaceManager *workspace_manager;
-} WaylandWindowMenu;
+} XfwWindowMenu;
-static void rebuild_menu (WaylandWindowMenu *data);
+static void rebuild_menu (XfwWindowMenu *data);
/* ---- Helpers ---- */
static XfwWorkspace *
-get_active_workspace (WaylandWindowMenu *data)
+get_active_workspace (XfwWindowMenu *data)
{
GList *workspaces, *l;
@@ -76,7 +71,7 @@ get_active_workspace (WaylandWindowMenu *data)
}
static void
-update_root_icon (WaylandWindowMenu *data)
+update_root_icon (XfwWindowMenu *data)
{
XfwWindow *window;
GIcon *icon = NULL;
@@ -227,7 +222,7 @@ on_window_state_changed (XfwWindow *window, XfwWindowState changed_mask,
/* ---- Menu item creation ---- */
static GtkWidget *
-create_window_item (WaylandWindowMenu *data, XfwWindow *window)
+create_window_item (XfwWindowMenu *data, XfwWindow *window)
{
GtkWidget *item, *box, *image, *label;
const char *name;
@@ -315,14 +310,14 @@ create_window_item (WaylandWindowMenu *data, XfwWindow *window)
}
static void
-append_window_item (WaylandWindowMenu *data, XfwWindow *window)
+append_window_item (XfwWindowMenu *data, XfwWindow *window)
{
GtkWidget *item = create_window_item (data, window);
gtk_menu_shell_append (GTK_MENU_SHELL (data->submenu), item);
}
static void
-add_workspace_label (WaylandWindowMenu *data, XfwWorkspace *ws)
+add_workspace_label (XfwWindowMenu *data, XfwWorkspace *ws)
{
GtkWidget *item, *label;
const char *ws_name;
@@ -349,7 +344,7 @@ add_workspace_label (WaylandWindowMenu *data, XfwWorkspace *ws)
/* ---- Disconnect / rebuild ---- */
static void
-disconnect_window_signals (WaylandWindowMenu *data)
+disconnect_window_signals (XfwWindowMenu *data)
{
GHashTableIter iter;
gpointer key, value;
@@ -362,7 +357,7 @@ disconnect_window_signals (WaylandWindowMenu *data)
}
static void
-rebuild_menu (WaylandWindowMenu *data)
+rebuild_menu (XfwWindowMenu *data)
{
GList *children, *l, *wins, *workspaces, *wl;
XfwWorkspace *active_ws;
@@ -495,7 +490,7 @@ rebuild_menu (WaylandWindowMenu *data)
static void
on_window_opened (XfwScreen *screen, XfwWindow *window,
- WaylandWindowMenu *data)
+ XfwWindowMenu *data)
{
/* refresh the button icon for the initially-enumerated windows too */
update_root_icon (data);
@@ -518,7 +513,7 @@ on_window_opened (XfwScreen *screen, XfwWindow *window,
static void
on_window_closed (XfwScreen *screen, XfwWindow *window,
- WaylandWindowMenu *data)
+ XfwWindowMenu *data)
{
GtkWidget *item;
@@ -543,7 +538,7 @@ on_window_closed (XfwScreen *screen, XfwWindow *window,
static void
on_active_window_changed (XfwScreen *screen, XfwWindow *previous_window,
- WaylandWindowMenu *data)
+ XfwWindowMenu *data)
{
update_root_icon (data);
}
@@ -552,14 +547,14 @@ on_active_window_changed (XfwScreen *screen, XfwWindow *previous_window,
static void
on_workspace_created (XfwWorkspaceManager *manager, XfwWorkspace *workspace,
- WaylandWindowMenu *data)
+ XfwWindowMenu *data)
{
/* Defer to next rebuild; the menu groups by workspace. */
}
static void
on_workspace_destroyed (XfwWorkspaceManager *manager, XfwWorkspace *workspace,
- WaylandWindowMenu *data)
+ XfwWindowMenu *data)
{
/* Defer to next rebuild. */
}
@@ -568,7 +563,7 @@ on_workspace_destroyed (XfwWorkspaceManager *manager, XfwWorkspace *workspace,
static gboolean
on_scroll_event (GtkWidget *widget, GdkEventScroll *event,
- WaylandWindowMenu *data)
+ XfwWindowMenu *data)
{
XfwWorkspace *active_ws;
GList *windows, *l;
@@ -647,7 +642,7 @@ on_scroll_event (GtkWidget *widget, GdkEventScroll *event,
/* ---- Menu show handler ---- */
static void
-on_menu_show (GtkWidget *menu, WaylandWindowMenu *data)
+on_menu_show (GtkWidget *menu, XfwWindowMenu *data)
{
rebuild_menu (data);
}
@@ -655,7 +650,7 @@ on_menu_show (GtkWidget *menu, WaylandWindowMenu *data)
/* ---- Cleanup ---- */
static void
-wayland_window_menu_free (WaylandWindowMenu *data)
+xfw_window_menu_free (XfwWindowMenu *data)
{
if (data->window_hash != NULL)
{
@@ -685,25 +680,19 @@ wayland_window_menu_free (WaylandWindowMenu *data)
/* ---- Public API ---- */
GtkWidget *
-wayland_window_menu_new (void)
+xfw_window_menu_new (void)
{
- WaylandWindowMenu *data;
+ XfwWindowMenu *data;
XfwScreen *screen;
- GdkDisplay *display;
GtkCssProvider *provider;
- display = gdk_display_get_default ();
- if (display == NULL || !GDK_IS_WAYLAND_DISPLAY (display))
- return gtk_label_new (_ ("[Window menu not supported on this platform]"));
-
xfw_set_client_type (XFW_CLIENT_TYPE_PAGER);
screen = xfw_screen_get_default ();
if (screen == NULL)
- return gtk_label_new (_ ("[Shell does not support "
- "WLR Foreign Toplevel Control]"));
+ return gtk_label_new (_ ("[Window menu not supported on this platform]"));
- data = g_new0 (WaylandWindowMenu, 1);
+ data = g_new0 (XfwWindowMenu, 1);
/* xfw_screen_get_default() returns a referenced screen */
data->screen = screen;
data->window_hash = g_hash_table_new_full (g_direct_hash,
@@ -748,9 +737,9 @@ wayland_window_menu_new (void)
g_object_unref (provider);
g_object_set_data_full (G_OBJECT (data->menu),
- "wayland_window_menu_data",
+ "xfw_window_menu_data",
data,
- (GDestroyNotify) wayland_window_menu_free);
+ (GDestroyNotify) xfw_window_menu_free);
g_signal_connect (data->submenu, "show",
G_CALLBACK (on_menu_show), data);
diff --git a/applets/wncklet/wayland-window-menu.h b/applets/wncklet/xfw-window-menu.h
similarity index 70%
rename from applets/wncklet/wayland-window-menu.h
rename to applets/wncklet/xfw-window-menu.h
index 2a1d39f01..0833627cf 100644
--- a/applets/wncklet/wayland-window-menu.h
+++ b/applets/wncklet/xfw-window-menu.h
@@ -1,4 +1,4 @@
-/* Wayland backend for the Window Selector applet */
+/* libxfce4windowing backend for the Window Selector applet */
/*
* Copyright (C) 2026 MATE Desktop Team
@@ -19,14 +19,8 @@
* 02110-1301, USA.
*/
-#ifndef _WNCKLET_APPLET_WAYLAND_WINDOW_MENU_H_
-#define _WNCKLET_APPLET_WAYLAND_WINDOW_MENU_H_
-
-#ifdef PACKAGE_NAME
-#ifndef HAVE_WAYLAND
-#error file should only be included when HAVE_WAYLAND is enabled
-#endif
-#endif
+#ifndef _WNCKLET_APPLET_XFW_WINDOW_MENU_H_
+#define _WNCKLET_APPLET_XFW_WINDOW_MENU_H_
#include
@@ -34,10 +28,10 @@
extern "C" {
#endif
-GtkWidget *wayland_window_menu_new (void);
+GtkWidget *xfw_window_menu_new (void);
#ifdef __cplusplus
}
#endif
-#endif /* _WNCKLET_APPLET_WAYLAND_WINDOW_MENU_H_ */
+#endif /* _WNCKLET_APPLET_XFW_WINDOW_MENU_H_ */
diff --git a/configure.ac b/configure.ac
index 4ce9ae3f6..cdcd90cdd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -61,7 +61,6 @@ LIBMATE_MENU_REQUIRED=1.21.0
CAIRO_REQUIRED=1.0.0
DCONF_REQUIRED=0.13.4
GTK_REQUIRED=3.24.0
-LIBWNCK_REQUIRED=43.0
WEATHER_REQUIRED=1.17.0
dnl pkg-config dependency checks
@@ -87,7 +86,7 @@ PKG_CHECK_MODULES(NOTIFICATION_AREA, gtk+-3.0 >= $GTK_REQUIRED mate-desktop-2.0
AC_SUBST(NOTIFICATION_AREA_CFLAGS)
AC_SUBST(NOTIFICATION_AREA_LIBS)
-PKG_CHECK_MODULES(WNCKLET, gtk+-3.0 >= $GTK_REQUIRED libwnck-3.0 >= $LIBWNCK_REQUIRED mate-desktop-2.0 >= $LIBMATE_DESKTOP_REQUIRED)
+PKG_CHECK_MODULES(WNCKLET, gtk+-3.0 >= $GTK_REQUIRED mate-desktop-2.0 >= $LIBMATE_DESKTOP_REQUIRED)
AC_SUBST(WNCKLET_CFLAGS)
AC_SUBST(WNCKLET_LIBS)
@@ -214,17 +213,6 @@ if test "x$enable_wayland" != "xno"; then
])
fi
-have_xfw=no
-if test "x$have_wayland" = "xyes"; then
- PKG_CHECK_MODULES(XFW, libxfce4windowing-0 >= 4.20.2, have_xfw=yes, [
- if test "x$enable_wayland" = "xyes"; then
- AC_MSG_ERROR([Wayland support requires libxfce4windowing-0 >= 4.20.2])
- else
- have_wayland=no
- fi
- ])
-fi
-
AM_CONDITIONAL(ENABLE_WAYLAND, [test "x$have_wayland" = "xyes"])
if test "x$have_wayland" = "xyes"; then
@@ -250,6 +238,23 @@ if test "x$have_x11" = "xyes"; then
AC_DEFINE(HAVE_X11, 1, [Have the X11 development library])
fi
+# Both backends talk to the window manager through libxfce4windowing, so it is
+# needed as soon as either one is enabled.
+have_xfw=no
+if test "x$have_wayland" = "xyes" -o "x$have_x11" = "xyes"; then
+ PKG_CHECK_MODULES(XFW, libxfce4windowing-0 >= 4.20.2, have_xfw=yes, [
+ if test "x$enable_wayland" = "xyes"; then
+ AC_MSG_ERROR([Wayland support requires libxfce4windowing-0 >= 4.20.2])
+ elif test "x$enable_x11" = "xyes"; then
+ AC_MSG_ERROR([X11 support requires libxfce4windowing-0 >= 4.20.2])
+ elif test "x$have_wayland" = "xyes"; then
+ have_wayland=no
+ else
+ have_x11=no
+ fi
+ ])
+fi
+
if test "x$have_x11" != "xyes" -a "x$have_wayland" != "xyes"; then
if test "x$enable_wayland" = "xno" -a "x$enable_x11" = "xno"; then
AC_MSG_ERROR([At least one backend must be enabled])
diff --git a/meson.build b/meson.build
index a969141e3..49b5e4d44 100644
--- a/meson.build
+++ b/meson.build
@@ -160,8 +160,6 @@ if enable_deprecation_flags
'-DLIBGLADE_DISABLE_SINGLE_INCLUDES',
'-DVTE_DISABLE_DEPRECATED',
'-DVTE_DISABLE_SINGLE_INCLUDES',
- '-DWNCK_DISABLE_DEPRECATED',
- '-DWNCK_DISABLE_SINGLE_INCLUDES',
'-DLIBSOUP_DISABLE_DEPRECATED',
'-DLIBSOUP_DISABLE_SINGLE_INCLUDES',
]
@@ -184,7 +182,6 @@ cairo_dep = dependency('cairo', version: '>= 1.0.0')
mate_desktop_dep = dependency('mate-desktop-2.0', version: '>= 1.28.2')
mate_menu_dep = dependency('libmate-menu', version: '>= 1.21.0')
dconf_dep = dependency('dconf', version: '>= 0.13.4')
-wnck_dep = dependency('libwnck-3.0', version: '>= 43.0')
mateweather_dep = dependency('mateweather', version: '>= 1.17.0')
gmodule_dep = dependency('gmodule-2.0', required: false)
ice_dep = dependency('ice')
@@ -204,17 +201,24 @@ wayland_deps = [
dependency('wayland-client', required: wayland_opt),
]
have_wayland = wayland_deps[0].found() and wayland_deps[1].found()
-if have_wayland
+config_h.set('HAVE_WAYLAND', have_wayland)
+
+# Both backends talk to the window manager through libxfce4windowing, so it is
+# needed as soon as either one is enabled.
+xfw_dep = dependency('', required: false)
+if have_x11 or have_wayland
xfw_dep = dependency('libxfce4windowing-0', version: '>= 4.20.2', required: false)
if not xfw_dep.found()
if wayland_opt.enabled()
error('Wayland support requires libxfce4windowing-0 >= 4.20.2')
+ elif x11_opt.enabled()
+ error('X11 support requires libxfce4windowing-0 >= 4.20.2')
else
have_wayland = false
+ have_x11 = false
endif
endif
endif
-config_h.set('HAVE_WAYLAND', have_wayland)
if not have_x11 and not have_wayland
if wayland_opt.disabled() and x11_opt.disabled()