summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Samoilov <[email protected]>2026-07-18 10:41:07 +0400
committerLuke from DC <[email protected]>2026-07-31 07:57:23 +0000
commitcbb9c2589291bcee42ded23ddc7ed2005609db5e (patch)
treed08e8a283882b38439f92cce06c128d14d4502c2
parentfa05c51b6f86d3bc705b9b9e9063255fd21b4dcf (diff)
downloadmate-panel-cbb9c2589291bcee42ded23ddc7ed2005609db5e.tar.bz2
mate-panel-cbb9c2589291bcee42ded23ddc7ed2005609db5e.tar.xz
[Wayland] Add support for ext-workspace-v1 in pager
-rw-r--r--applets/wncklet/Makefile.am21
-rw-r--r--applets/wncklet/org.mate.panel.Wncklet.mate-panel-applet.desktop.in.in2
-rw-r--r--applets/wncklet/wayland-protocol/ext-workspace-v1-client.h885
-rw-r--r--applets/wncklet/wayland-protocol/ext-workspace-v1-code.c120
-rw-r--r--applets/wncklet/wayland-protocol/ext-workspace-v1.xml422
-rw-r--r--applets/wncklet/wayland-workspace.c698
-rw-r--r--applets/wncklet/wayland-workspace.h49
-rw-r--r--applets/wncklet/wncklet.c2
-rw-r--r--applets/wncklet/workspace-switcher.c78
9 files changed, 2253 insertions, 24 deletions
diff --git a/applets/wncklet/Makefile.am b/applets/wncklet/Makefile.am
index 2acab8d0..9d84c331 100644
--- a/applets/wncklet/Makefile.am
+++ b/applets/wncklet/Makefile.am
@@ -21,13 +21,9 @@ WNCKLET_SOURCES = \
window-list.h \
showdesktop.c \
showdesktop.h \
- $(BUILT_SOURCES)
-
-if ENABLE_X11
-WNCKLET_SOURCES += \
workspace-switcher.c \
- workspace-switcher.h
-endif
+ workspace-switcher.h \
+ $(BUILT_SOURCES)
@@ -45,8 +41,12 @@ if ENABLE_WAYLAND
WNCKLET_SOURCES += \
wayland-backend.c \
wayland-backend.h \
+ wayland-workspace.c \
+ wayland-workspace.h \
wayland-protocol/wlr-foreign-toplevel-management-unstable-v1-code.c \
- wayland-protocol/wlr-foreign-toplevel-management-unstable-v1-client.h
+ wayland-protocol/wlr-foreign-toplevel-management-unstable-v1-client.h \
+ wayland-protocol/ext-workspace-v1-code.c \
+ wayland-protocol/ext-workspace-v1-client.h
WNCKLET_LDADD += \
$(WAYLAND_LIBS)
@@ -104,15 +104,10 @@ ui_FILES = \
showdesktop-menu.xml \
window-list-menu.xml \
window-list.ui \
- window-menu-menu.xml
-
-if ENABLE_X11
-ui_FILES += \
+ window-menu-menu.xml \
workspace-switcher-menu.xml \
workspace-switcher.ui
-endif
-
diff --git a/applets/wncklet/org.mate.panel.Wncklet.mate-panel-applet.desktop.in.in b/applets/wncklet/org.mate.panel.Wncklet.mate-panel-applet.desktop.in.in
index ec53ade3..6a2285af 100644
--- a/applets/wncklet/org.mate.panel.Wncklet.mate-panel-applet.desktop.in.in
+++ b/applets/wncklet/org.mate.panel.Wncklet.mate-panel-applet.desktop.in.in
@@ -24,7 +24,7 @@ Description=Switch between workspaces
# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
Icon=mate-panel-workspace-switcher
MateComponentId=OAFIID:MATE_WorkspaceSwitcherApplet;OAFIID:MATE_PagerApplet;
-Platforms=X11;
+Platforms=X11;Wayland;
X-MATE-Bugzilla-Bugzilla=MATE
X-MATE-Bugzilla-Product=mate-panel
X-MATE-Bugzilla-Component=workspace switcher
diff --git a/applets/wncklet/wayland-protocol/ext-workspace-v1-client.h b/applets/wncklet/wayland-protocol/ext-workspace-v1-client.h
new file mode 100644
index 00000000..844600f0
--- /dev/null
+++ b/applets/wncklet/wayland-protocol/ext-workspace-v1-client.h
@@ -0,0 +1,885 @@
+/* Generated by wayland-scanner 1.23.1 */
+
+#ifndef EXT_WORKSPACE_V1_CLIENT_PROTOCOL_H
+#define EXT_WORKSPACE_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client-core.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_ext_workspace_v1 The ext_workspace_v1 protocol
+ * @section page_ifaces_ext_workspace_v1 Interfaces
+ * - @subpage page_iface_ext_workspace_manager_v1 - list and control workspaces
+ * - @subpage page_iface_ext_workspace_group_handle_v1 - a workspace group assigned to a set of outputs
+ * - @subpage page_iface_ext_workspace_handle_v1 - a workspace handing a group of surfaces
+ * @section page_copyright_ext_workspace_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2019 Christopher Billington
+ * Copyright © 2020 Ilia Bozhinov
+ * Copyright © 2022 Victoria Brekenfeld
+ *
+ * Permission to use, copy, modify, distribute, and sell this
+ * software and its documentation for any purpose is hereby granted
+ * without fee, provided that the above copyright notice appear in
+ * all copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of
+ * the copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ * </pre>
+ */
+struct ext_workspace_group_handle_v1;
+struct ext_workspace_handle_v1;
+struct ext_workspace_manager_v1;
+struct wl_output;
+
+#ifndef EXT_WORKSPACE_MANAGER_V1_INTERFACE
+#define EXT_WORKSPACE_MANAGER_V1_INTERFACE
+/**
+ * @page page_iface_ext_workspace_manager_v1 ext_workspace_manager_v1
+ * @section page_iface_ext_workspace_manager_v1_desc Description
+ *
+ * Workspaces, also called virtual desktops, are groups of surfaces. A
+ * compositor with a concept of workspaces may only show some such groups of
+ * surfaces (those of 'active' workspaces) at a time. 'Activating' a
+ * workspace is a request for the compositor to display that workspace's
+ * surfaces as normal, whereas the compositor may hide or otherwise
+ * de-emphasise surfaces that are associated only with 'inactive' workspaces.
+ * Workspaces are grouped by which sets of outputs they correspond to, and
+ * may contain surfaces only from those outputs. In this way, it is possible
+ * for each output to have its own set of workspaces, or for all outputs (or
+ * any other arbitrary grouping) to share workspaces. Compositors may
+ * optionally conceptually arrange each group of workspaces in an
+ * N-dimensional grid.
+ *
+ * The purpose of this protocol is to enable the creation of taskbars and
+ * docks by providing them with a list of workspaces and their properties,
+ * and allowing them to activate and deactivate workspaces.
+ *
+ * After a client binds the ext_workspace_manager_v1, each workspace will be
+ * sent via the workspace event.
+ * @section page_iface_ext_workspace_manager_v1_api API
+ * See @ref iface_ext_workspace_manager_v1.
+ */
+/**
+ * @defgroup iface_ext_workspace_manager_v1 The ext_workspace_manager_v1 interface
+ *
+ * Workspaces, also called virtual desktops, are groups of surfaces. A
+ * compositor with a concept of workspaces may only show some such groups of
+ * surfaces (those of 'active' workspaces) at a time. 'Activating' a
+ * workspace is a request for the compositor to display that workspace's
+ * surfaces as normal, whereas the compositor may hide or otherwise
+ * de-emphasise surfaces that are associated only with 'inactive' workspaces.
+ * Workspaces are grouped by which sets of outputs they correspond to, and
+ * may contain surfaces only from those outputs. In this way, it is possible
+ * for each output to have its own set of workspaces, or for all outputs (or
+ * any other arbitrary grouping) to share workspaces. Compositors may
+ * optionally conceptually arrange each group of workspaces in an
+ * N-dimensional grid.
+ *
+ * The purpose of this protocol is to enable the creation of taskbars and
+ * docks by providing them with a list of workspaces and their properties,
+ * and allowing them to activate and deactivate workspaces.
+ *
+ * After a client binds the ext_workspace_manager_v1, each workspace will be
+ * sent via the workspace event.
+ */
+extern const struct wl_interface ext_workspace_manager_v1_interface;
+#endif
+#ifndef EXT_WORKSPACE_GROUP_HANDLE_V1_INTERFACE
+#define EXT_WORKSPACE_GROUP_HANDLE_V1_INTERFACE
+/**
+ * @page page_iface_ext_workspace_group_handle_v1 ext_workspace_group_handle_v1
+ * @section page_iface_ext_workspace_group_handle_v1_desc Description
+ *
+ * A ext_workspace_group_handle_v1 object represents a workspace group
+ * that is assigned a set of outputs and contains a number of workspaces.
+ *
+ * The set of outputs assigned to the workspace group is conveyed to the client via
+ * output_enter and output_leave events, and its workspaces are conveyed with
+ * workspace events.
+ *
+ * For example, a compositor which has a set of workspaces for each output may
+ * advertise a workspace group (and its workspaces) per output, whereas a compositor
+ * where a workspace spans all outputs may advertise a single workspace group for all
+ * outputs.
+ * @section page_iface_ext_workspace_group_handle_v1_api API
+ * See @ref iface_ext_workspace_group_handle_v1.
+ */
+/**
+ * @defgroup iface_ext_workspace_group_handle_v1 The ext_workspace_group_handle_v1 interface
+ *
+ * A ext_workspace_group_handle_v1 object represents a workspace group
+ * that is assigned a set of outputs and contains a number of workspaces.
+ *
+ * The set of outputs assigned to the workspace group is conveyed to the client via
+ * output_enter and output_leave events, and its workspaces are conveyed with
+ * workspace events.
+ *
+ * For example, a compositor which has a set of workspaces for each output may
+ * advertise a workspace group (and its workspaces) per output, whereas a compositor
+ * where a workspace spans all outputs may advertise a single workspace group for all
+ * outputs.
+ */
+extern const struct wl_interface ext_workspace_group_handle_v1_interface;
+#endif
+#ifndef EXT_WORKSPACE_HANDLE_V1_INTERFACE
+#define EXT_WORKSPACE_HANDLE_V1_INTERFACE
+/**
+ * @page page_iface_ext_workspace_handle_v1 ext_workspace_handle_v1
+ * @section page_iface_ext_workspace_handle_v1_desc Description
+ *
+ * A ext_workspace_handle_v1 object represents a workspace that handles a
+ * group of surfaces.
+ *
+ * Each workspace has:
+ * - a name, conveyed to the client with the name event
+ * - potentially an id conveyed with the id event
+ * - a list of states, conveyed to the client with the state event
+ * - and optionally a set of coordinates, conveyed to the client with the
+ * coordinates event
+ *
+ * The client may request that the compositor activate or deactivate the workspace.
+ *
+ * Each workspace can belong to only a single workspace group.
+ * Depending on the compositor policy, there might be workspaces with
+ * the same name in different workspace groups, but these workspaces are still
+ * separate (e.g. one of them might be active while the other is not).
+ * @section page_iface_ext_workspace_handle_v1_api API
+ * See @ref iface_ext_workspace_handle_v1.
+ */
+/**
+ * @defgroup iface_ext_workspace_handle_v1 The ext_workspace_handle_v1 interface
+ *
+ * A ext_workspace_handle_v1 object represents a workspace that handles a
+ * group of surfaces.
+ *
+ * Each workspace has:
+ * - a name, conveyed to the client with the name event
+ * - potentially an id conveyed with the id event
+ * - a list of states, conveyed to the client with the state event
+ * - and optionally a set of coordinates, conveyed to the client with the
+ * coordinates event
+ *
+ * The client may request that the compositor activate or deactivate the workspace.
+ *
+ * Each workspace can belong to only a single workspace group.
+ * Depending on the compositor policy, there might be workspaces with
+ * the same name in different workspace groups, but these workspaces are still
+ * separate (e.g. one of them might be active while the other is not).
+ */
+extern const struct wl_interface ext_workspace_handle_v1_interface;
+#endif
+
+/**
+ * @ingroup iface_ext_workspace_manager_v1
+ * @struct ext_workspace_manager_v1_listener
+ */
+struct ext_workspace_manager_v1_listener {
+ /**
+ * a workspace group has been created
+ *
+ * This event is emitted whenever a new workspace group has been
+ * created.
+ *
+ * All initial details of the workspace group (outputs) will be
+ * sent immediately after this event via the corresponding events
+ * in ext_workspace_group_handle_v1 and ext_workspace_handle_v1.
+ */
+ void (*workspace_group)(void *data,
+ struct ext_workspace_manager_v1 *ext_workspace_manager_v1,
+ struct ext_workspace_group_handle_v1 *workspace_group);
+ /**
+ * workspace has been created
+ *
+ * This event is emitted whenever a new workspace has been
+ * created.
+ *
+ * All initial details of the workspace (name, coordinates, state)
+ * will be sent immediately after this event via the corresponding
+ * events in ext_workspace_handle_v1.
+ *
+ * Workspaces start off unassigned to any workspace group.
+ */
+ void (*workspace)(void *data,
+ struct ext_workspace_manager_v1 *ext_workspace_manager_v1,
+ struct ext_workspace_handle_v1 *workspace);
+ /**
+ * all information about the workspaces and workspace groups has been sent
+ *
+ * This event is sent after all changes in all workspaces and
+ * workspace groups have been sent.
+ *
+ * This allows changes to one or more ext_workspace_group_handle_v1
+ * properties and ext_workspace_handle_v1 properties to be seen as
+ * atomic, even if they happen via multiple events. In particular,
+ * an output moving from one workspace group to another sends an
+ * output_enter event and an output_leave event to the two
+ * ext_workspace_group_handle_v1 objects in question. The
+ * compositor sends the done event only after updating the output
+ * information in both workspace groups.
+ */
+ void (*done)(void *data,
+ struct ext_workspace_manager_v1 *ext_workspace_manager_v1);
+ /**
+ * the compositor has finished with the workspace_manager
+ *
+ * This event indicates that the compositor is done sending
+ * events to the ext_workspace_manager_v1. The server will destroy
+ * the object immediately after sending this request.
+ */
+ void (*finished)(void *data,
+ struct ext_workspace_manager_v1 *ext_workspace_manager_v1);
+};
+
+/**
+ * @ingroup iface_ext_workspace_manager_v1
+ */
+static inline int
+ext_workspace_manager_v1_add_listener(struct ext_workspace_manager_v1 *ext_workspace_manager_v1,
+ const struct ext_workspace_manager_v1_listener *listener, void *data)
+{
+ return wl_proxy_add_listener((struct wl_proxy *) ext_workspace_manager_v1,
+ (void (**)(void)) listener, data);
+}
+
+#define EXT_WORKSPACE_MANAGER_V1_COMMIT 0
+#define EXT_WORKSPACE_MANAGER_V1_STOP 1
+
+/**
+ * @ingroup iface_ext_workspace_manager_v1
+ */
+#define EXT_WORKSPACE_MANAGER_V1_WORKSPACE_GROUP_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_manager_v1
+ */
+#define EXT_WORKSPACE_MANAGER_V1_WORKSPACE_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_manager_v1
+ */
+#define EXT_WORKSPACE_MANAGER_V1_DONE_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_manager_v1
+ */
+#define EXT_WORKSPACE_MANAGER_V1_FINISHED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_ext_workspace_manager_v1
+ */
+#define EXT_WORKSPACE_MANAGER_V1_COMMIT_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_manager_v1
+ */
+#define EXT_WORKSPACE_MANAGER_V1_STOP_SINCE_VERSION 1
+
+/** @ingroup iface_ext_workspace_manager_v1 */
+static inline void
+ext_workspace_manager_v1_set_user_data(struct ext_workspace_manager_v1 *ext_workspace_manager_v1, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) ext_workspace_manager_v1, user_data);
+}
+
+/** @ingroup iface_ext_workspace_manager_v1 */
+static inline void *
+ext_workspace_manager_v1_get_user_data(struct ext_workspace_manager_v1 *ext_workspace_manager_v1)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) ext_workspace_manager_v1);
+}
+
+static inline uint32_t
+ext_workspace_manager_v1_get_version(struct ext_workspace_manager_v1 *ext_workspace_manager_v1)
+{
+ return wl_proxy_get_version((struct wl_proxy *) ext_workspace_manager_v1);
+}
+
+/** @ingroup iface_ext_workspace_manager_v1 */
+static inline void
+ext_workspace_manager_v1_destroy(struct ext_workspace_manager_v1 *ext_workspace_manager_v1)
+{
+ wl_proxy_destroy((struct wl_proxy *) ext_workspace_manager_v1);
+}
+
+/**
+ * @ingroup iface_ext_workspace_manager_v1
+ *
+ * The client must send this request after it has finished sending other
+ * requests. The compositor must process a series of requests preceding a
+ * commit request atomically.
+ *
+ * This allows changes to the workspace properties to be seen as atomic,
+ * even if they happen via multiple events, and even if they involve
+ * multiple ext_workspace_handle_v1 objects, for example, deactivating one
+ * workspace and activating another.
+ */
+static inline void
+ext_workspace_manager_v1_commit(struct ext_workspace_manager_v1 *ext_workspace_manager_v1)
+{
+ wl_proxy_marshal_flags((struct wl_proxy *) ext_workspace_manager_v1,
+ EXT_WORKSPACE_MANAGER_V1_COMMIT, NULL, wl_proxy_get_version((struct wl_proxy *) ext_workspace_manager_v1), 0);
+}
+
+/**
+ * @ingroup iface_ext_workspace_manager_v1
+ *
+ * Indicates the client no longer wishes to receive events for new
+ * workspace groups. However the compositor may emit further workspace
+ * events, until the finished event is emitted. The compositor is expected
+ * to send the finished event eventually once the stop request has been processed.
+ *
+ * The client must not send any requests after this one, doing so will raise a wl_display
+ * invalid_object error.
+ */
+static inline void
+ext_workspace_manager_v1_stop(struct ext_workspace_manager_v1 *ext_workspace_manager_v1)
+{
+ wl_proxy_marshal_flags((struct wl_proxy *) ext_workspace_manager_v1,
+ EXT_WORKSPACE_MANAGER_V1_STOP, NULL, wl_proxy_get_version((struct wl_proxy *) ext_workspace_manager_v1), 0);
+}
+
+#ifndef EXT_WORKSPACE_GROUP_HANDLE_V1_GROUP_CAPABILITIES_ENUM
+#define EXT_WORKSPACE_GROUP_HANDLE_V1_GROUP_CAPABILITIES_ENUM
+enum ext_workspace_group_handle_v1_group_capabilities {
+ /**
+ * create_workspace request is available
+ */
+ EXT_WORKSPACE_GROUP_HANDLE_V1_GROUP_CAPABILITIES_CREATE_WORKSPACE = 1,
+};
+#endif /* EXT_WORKSPACE_GROUP_HANDLE_V1_GROUP_CAPABILITIES_ENUM */
+
+/**
+ * @ingroup iface_ext_workspace_group_handle_v1
+ * @struct ext_workspace_group_handle_v1_listener
+ */
+struct ext_workspace_group_handle_v1_listener {
+ /**
+ * compositor capabilities
+ *
+ * This event advertises the capabilities supported by the
+ * compositor. If a capability isn't supported, clients should hide
+ * or disable the UI elements that expose this functionality. For
+ * instance, if the compositor doesn't advertise support for
+ * creating workspaces, a button triggering the create_workspace
+ * request should not be displayed.
+ *
+ * The compositor will ignore requests it doesn't support. For
+ * instance, a compositor which doesn't advertise support for
+ * creating workspaces will ignore create_workspace requests.
+ *
+ * Compositors must send this event once after creation of an
+ * ext_workspace_group_handle_v1. When the capabilities change,
+ * compositors must send this event again.
+ * @param capabilities capabilities
+ */
+ void (*capabilities)(void *data,
+ struct ext_workspace_group_handle_v1 *ext_workspace_group_handle_v1,
+ uint32_t capabilities);
+ /**
+ * output assigned to workspace group
+ *
+ * This event is emitted whenever an output is assigned to the
+ * workspace group or a new `wl_output` object is bound by the
+ * client, which was already assigned to this workspace_group.
+ */
+ void (*output_enter)(void *data,
+ struct ext_workspace_group_handle_v1 *ext_workspace_group_handle_v1,
+ struct wl_output *output);
+ /**
+ * output removed from workspace group
+ *
+ * This event is emitted whenever an output is removed from the
+ * workspace group.
+ */
+ void (*output_leave)(void *data,
+ struct ext_workspace_group_handle_v1 *ext_workspace_group_handle_v1,
+ struct wl_output *output);
+ /**
+ * workspace added to workspace group
+ *
+ * This event is emitted whenever a workspace is assigned to this
+ * group. A workspace may only ever be assigned to a single group
+ * at a single point in time, but can be re-assigned during its
+ * lifetime.
+ */
+ void (*workspace_enter)(void *data,
+ struct ext_workspace_group_handle_v1 *ext_workspace_group_handle_v1,
+ struct ext_workspace_handle_v1 *workspace);
+ /**
+ * workspace removed from workspace group
+ *
+ * This event is emitted whenever a workspace is removed from
+ * this group.
+ */
+ void (*workspace_leave)(void *data,
+ struct ext_workspace_group_handle_v1 *ext_workspace_group_handle_v1,
+ struct ext_workspace_handle_v1 *workspace);
+ /**
+ * this workspace group has been removed
+ *
+ * This event is send when the group associated with the
+ * ext_workspace_group_handle_v1 has been removed. After sending
+ * this request the compositor will immediately consider the object
+ * inert. Any requests will be ignored except the destroy request.
+ * It is guaranteed there won't be any more events referencing this
+ * ext_workspace_group_handle_v1.
+ *
+ * The compositor must remove all workspaces belonging to a
+ * workspace group via a workspace_leave event before removing the
+ * workspace group.
+ */
+ void (*removed)(void *data,
+ struct ext_workspace_group_handle_v1 *ext_workspace_group_handle_v1);
+};
+
+/**
+ * @ingroup iface_ext_workspace_group_handle_v1
+ */
+static inline int
+ext_workspace_group_handle_v1_add_listener(struct ext_workspace_group_handle_v1 *ext_workspace_group_handle_v1,
+ const struct ext_workspace_group_handle_v1_listener *listener, void *data)
+{
+ return wl_proxy_add_listener((struct wl_proxy *) ext_workspace_group_handle_v1,
+ (void (**)(void)) listener, data);
+}
+
+#define EXT_WORKSPACE_GROUP_HANDLE_V1_CREATE_WORKSPACE 0
+#define EXT_WORKSPACE_GROUP_HANDLE_V1_DESTROY 1
+
+/**
+ * @ingroup iface_ext_workspace_group_handle_v1
+ */
+#define EXT_WORKSPACE_GROUP_HANDLE_V1_CAPABILITIES_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_group_handle_v1
+ */
+#define EXT_WORKSPACE_GROUP_HANDLE_V1_OUTPUT_ENTER_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_group_handle_v1
+ */
+#define EXT_WORKSPACE_GROUP_HANDLE_V1_OUTPUT_LEAVE_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_group_handle_v1
+ */
+#define EXT_WORKSPACE_GROUP_HANDLE_V1_WORKSPACE_ENTER_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_group_handle_v1
+ */
+#define EXT_WORKSPACE_GROUP_HANDLE_V1_WORKSPACE_LEAVE_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_group_handle_v1
+ */
+#define EXT_WORKSPACE_GROUP_HANDLE_V1_REMOVED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_ext_workspace_group_handle_v1
+ */
+#define EXT_WORKSPACE_GROUP_HANDLE_V1_CREATE_WORKSPACE_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_group_handle_v1
+ */
+#define EXT_WORKSPACE_GROUP_HANDLE_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_ext_workspace_group_handle_v1 */
+static inline void
+ext_workspace_group_handle_v1_set_user_data(struct ext_workspace_group_handle_v1 *ext_workspace_group_handle_v1, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) ext_workspace_group_handle_v1, user_data);
+}
+
+/** @ingroup iface_ext_workspace_group_handle_v1 */
+static inline void *
+ext_workspace_group_handle_v1_get_user_data(struct ext_workspace_group_handle_v1 *ext_workspace_group_handle_v1)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) ext_workspace_group_handle_v1);
+}
+
+static inline uint32_t
+ext_workspace_group_handle_v1_get_version(struct ext_workspace_group_handle_v1 *ext_workspace_group_handle_v1)
+{
+ return wl_proxy_get_version((struct wl_proxy *) ext_workspace_group_handle_v1);
+}
+
+/**
+ * @ingroup iface_ext_workspace_group_handle_v1
+ *
+ * Request that the compositor create a new workspace with the given name
+ * and assign it to this group.
+ *
+ * There is no guarantee that the compositor will create a new workspace,
+ * or that the created workspace will have the provided name.
+ */
+static inline void
+ext_workspace_group_handle_v1_create_workspace(struct ext_workspace_group_handle_v1 *ext_workspace_group_handle_v1, const char *workspace)
+{
+ wl_proxy_marshal_flags((struct wl_proxy *) ext_workspace_group_handle_v1,
+ EXT_WORKSPACE_GROUP_HANDLE_V1_CREATE_WORKSPACE, NULL, wl_proxy_get_version((struct wl_proxy *) ext_workspace_group_handle_v1), 0, workspace);
+}
+
+/**
+ * @ingroup iface_ext_workspace_group_handle_v1
+ *
+ * Destroys the ext_workspace_group_handle_v1 object.
+ *
+ * This request should be send either when the client does not want to
+ * use the workspace group object any more or after the removed event to finalize
+ * the destruction of the object.
+ */
+static inline void
+ext_workspace_group_handle_v1_destroy(struct ext_workspace_group_handle_v1 *ext_workspace_group_handle_v1)
+{
+ wl_proxy_marshal_flags((struct wl_proxy *) ext_workspace_group_handle_v1,
+ EXT_WORKSPACE_GROUP_HANDLE_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) ext_workspace_group_handle_v1), WL_MARSHAL_FLAG_DESTROY);
+}
+
+#ifndef EXT_WORKSPACE_HANDLE_V1_STATE_ENUM
+#define EXT_WORKSPACE_HANDLE_V1_STATE_ENUM
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ * types of states on the workspace
+ *
+ * The different states that a workspace can have.
+ */
+enum ext_workspace_handle_v1_state {
+ /**
+ * the workspace is active
+ */
+ EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE = 1,
+ /**
+ * the workspace requests attention
+ */
+ EXT_WORKSPACE_HANDLE_V1_STATE_URGENT = 2,
+ /**
+ * the workspace is not visible
+ *
+ * The workspace is not visible in its workspace group, and
+ * clients attempting to visualize the compositor workspace state
+ * should not display such workspaces.
+ */
+ EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN = 4,
+};
+#endif /* EXT_WORKSPACE_HANDLE_V1_STATE_ENUM */
+
+#ifndef EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ENUM
+#define EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ENUM
+enum ext_workspace_handle_v1_workspace_capabilities {
+ /**
+ * activate request is available
+ */
+ EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE = 1,
+ /**
+ * deactivate request is available
+ */
+ EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_DEACTIVATE = 2,
+ /**
+ * remove request is available
+ */
+ EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_REMOVE = 4,
+ /**
+ * assign request is available
+ */
+ EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ASSIGN = 8,
+};
+#endif /* EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ENUM */
+
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ * @struct ext_workspace_handle_v1_listener
+ */
+struct ext_workspace_handle_v1_listener {
+ /**
+ * workspace id
+ *
+ * If this event is emitted, it will be send immediately after
+ * the ext_workspace_handle_v1 is created or when an id is assigned
+ * to a workspace (at most once during its lifetime).
+ *
+ * An id will never change during the lifetime of the
+ * `ext_workspace_handle_v1` and is guaranteed to be unique during
+ * its lifetime.
+ *
+ * Ids are not human-readable and shouldn't be displayed, use
+ * `name` for that purpose.
+ *
+ * Compositors are expected to only send ids for workspaces likely
+ * stable across multiple sessions and can be used by clients to
+ * store preferences for workspaces. Workspaces without ids should
+ * be considered temporary and any data associated with them should
+ * be deleted once the respective object is lost.
+ */
+ void (*id)(void *data,
+ struct ext_workspace_handle_v1 *ext_workspace_handle_v1,
+ const char *id);
+ /**
+ * workspace name changed
+ *
+ * This event is emitted immediately after the
+ * ext_workspace_handle_v1 is created and whenever the name of the
+ * workspace changes.
+ *
+ * A name is meant to be human-readable and can be displayed to a
+ * user. Unlike the id it is neither stable nor unique.
+ */
+ void (*name)(void *data,
+ struct ext_workspace_handle_v1 *ext_workspace_handle_v1,
+ const char *name);
+ /**
+ * workspace coordinates changed
+ *
+ * This event is used to organize workspaces into an
+ * N-dimensional grid within a workspace group, and if supported,
+ * is emitted immediately after the ext_workspace_handle_v1 is
+ * created and whenever the coordinates of the workspace change.
+ * Compositors may not send this event if they do not conceptually
+ * arrange workspaces in this way. If compositors simply number
+ * workspaces, without any geometric interpretation, they may send
+ * 1D coordinates, which clients should not interpret as implying
+ * any geometry. Sending an empty array means that the compositor
+ * no longer orders the workspace geometrically.
+ *
+ * Coordinates have an arbitrary number of dimensions N with an
+ * uint32 position along each dimension. By convention if N > 1,
+ * the first dimension is X, the second Y, the third Z, and so on.
+ * The compositor may chose to utilize these events for a more
+ * novel workspace layout convention, however. No guarantee is made
+ * about the grid being filled or bounded; there may be a workspace
+ * at coordinate 1 and another at coordinate 1000 and none in
+ * between. Within a workspace group, however, workspaces must have
+ * unique coordinates of equal dimensionality.
+ */
+ void (*coordinates)(void *data,
+ struct ext_workspace_handle_v1 *ext_workspace_handle_v1,
+ struct wl_array *coordinates);
+ /**
+ * the state of the workspace changed
+ *
+ * This event is emitted immediately after the
+ * ext_workspace_handle_v1 is created and each time the workspace
+ * state changes, either because of a compositor action or because
+ * of a request in this protocol.
+ *
+ * Missing states convey the opposite meaning, e.g. an unset active
+ * bit means the workspace is currently inactive.
+ */
+ void (*state)(void *data,
+ struct ext_workspace_handle_v1 *ext_workspace_handle_v1,
+ uint32_t state);
+ /**
+ * compositor capabilities
+ *
+ * This event advertises the capabilities supported by the
+ * compositor. If a capability isn't supported, clients should hide
+ * or disable the UI elements that expose this functionality. For
+ * instance, if the compositor doesn't advertise support for
+ * removing workspaces, a button triggering the remove request
+ * should not be displayed.
+ *
+ * The compositor will ignore requests it doesn't support. For
+ * instance, a compositor which doesn't advertise support for
+ * remove will ignore remove requests.
+ *
+ * Compositors must send this event once after creation of an
+ * ext_workspace_handle_v1 . When the capabilities change,
+ * compositors must send this event again.
+ * @param capabilities capabilities
+ */
+ void (*capabilities)(void *data,
+ struct ext_workspace_handle_v1 *ext_workspace_handle_v1,
+ uint32_t capabilities);
+ /**
+ * this workspace has been removed
+ *
+ * This event is send when the workspace associated with the
+ * ext_workspace_handle_v1 has been removed. After sending this
+ * request, the compositor will immediately consider the object
+ * inert. Any requests will be ignored except the destroy request.
+ *
+ * It is guaranteed there won't be any more events referencing this
+ * ext_workspace_handle_v1.
+ *
+ * The compositor must only remove a workspaces not currently
+ * belonging to any workspace_group.
+ */
+ void (*removed)(void *data,
+ struct ext_workspace_handle_v1 *ext_workspace_handle_v1);
+};
+
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ */
+static inline int
+ext_workspace_handle_v1_add_listener(struct ext_workspace_handle_v1 *ext_workspace_handle_v1,
+ const struct ext_workspace_handle_v1_listener *listener, void *data)
+{
+ return wl_proxy_add_listener((struct wl_proxy *) ext_workspace_handle_v1,
+ (void (**)(void)) listener, data);
+}
+
+#define EXT_WORKSPACE_HANDLE_V1_DESTROY 0
+#define EXT_WORKSPACE_HANDLE_V1_ACTIVATE 1
+#define EXT_WORKSPACE_HANDLE_V1_DEACTIVATE 2
+#define EXT_WORKSPACE_HANDLE_V1_ASSIGN 3
+#define EXT_WORKSPACE_HANDLE_V1_REMOVE 4
+
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ */
+#define EXT_WORKSPACE_HANDLE_V1_ID_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ */
+#define EXT_WORKSPACE_HANDLE_V1_NAME_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ */
+#define EXT_WORKSPACE_HANDLE_V1_COORDINATES_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ */
+#define EXT_WORKSPACE_HANDLE_V1_STATE_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ */
+#define EXT_WORKSPACE_HANDLE_V1_CAPABILITIES_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ */
+#define EXT_WORKSPACE_HANDLE_V1_REMOVED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ */
+#define EXT_WORKSPACE_HANDLE_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ */
+#define EXT_WORKSPACE_HANDLE_V1_ACTIVATE_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ */
+#define EXT_WORKSPACE_HANDLE_V1_DEACTIVATE_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ */
+#define EXT_WORKSPACE_HANDLE_V1_ASSIGN_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ */
+#define EXT_WORKSPACE_HANDLE_V1_REMOVE_SINCE_VERSION 1
+
+/** @ingroup iface_ext_workspace_handle_v1 */
+static inline void
+ext_workspace_handle_v1_set_user_data(struct ext_workspace_handle_v1 *ext_workspace_handle_v1, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) ext_workspace_handle_v1, user_data);
+}
+
+/** @ingroup iface_ext_workspace_handle_v1 */
+static inline void *
+ext_workspace_handle_v1_get_user_data(struct ext_workspace_handle_v1 *ext_workspace_handle_v1)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) ext_workspace_handle_v1);
+}
+
+static inline uint32_t
+ext_workspace_handle_v1_get_version(struct ext_workspace_handle_v1 *ext_workspace_handle_v1)
+{
+ return wl_proxy_get_version((struct wl_proxy *) ext_workspace_handle_v1);
+}
+
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ *
+ * Destroys the ext_workspace_handle_v1 object.
+ *
+ * This request should be made either when the client does not want to
+ * use the workspace object any more or after the remove event to finalize
+ * the destruction of the object.
+ */
+static inline void
+ext_workspace_handle_v1_destroy(struct ext_workspace_handle_v1 *ext_workspace_handle_v1)
+{
+ wl_proxy_marshal_flags((struct wl_proxy *) ext_workspace_handle_v1,
+ EXT_WORKSPACE_HANDLE_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) ext_workspace_handle_v1), WL_MARSHAL_FLAG_DESTROY);
+}
+
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ *
+ * Request that this workspace be activated.
+ *
+ * There is no guarantee the workspace will be actually activated, and
+ * behaviour may be compositor-dependent. For example, activating a
+ * workspace may or may not deactivate all other workspaces in the same
+ * group.
+ */
+static inline void
+ext_workspace_handle_v1_activate(struct ext_workspace_handle_v1 *ext_workspace_handle_v1)
+{
+ wl_proxy_marshal_flags((struct wl_proxy *) ext_workspace_handle_v1,
+ EXT_WORKSPACE_HANDLE_V1_ACTIVATE, NULL, wl_proxy_get_version((struct wl_proxy *) ext_workspace_handle_v1), 0);
+}
+
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ *
+ * Request that this workspace be deactivated.
+ *
+ * There is no guarantee the workspace will be actually deactivated.
+ */
+static inline void
+ext_workspace_handle_v1_deactivate(struct ext_workspace_handle_v1 *ext_workspace_handle_v1)
+{
+ wl_proxy_marshal_flags((struct wl_proxy *) ext_workspace_handle_v1,
+ EXT_WORKSPACE_HANDLE_V1_DEACTIVATE, NULL, wl_proxy_get_version((struct wl_proxy *) ext_workspace_handle_v1), 0);
+}
+
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ *
+ * Requests that this workspace is assigned to the given workspace group.
+ *
+ * There is no guarantee the workspace will be assigned.
+ */
+static inline void
+ext_workspace_handle_v1_assign(struct ext_workspace_handle_v1 *ext_workspace_handle_v1, struct ext_workspace_group_handle_v1 *workspace_group)
+{
+ wl_proxy_marshal_flags((struct wl_proxy *) ext_workspace_handle_v1,
+ EXT_WORKSPACE_HANDLE_V1_ASSIGN, NULL, wl_proxy_get_version((struct wl_proxy *) ext_workspace_handle_v1), 0, workspace_group);
+}
+
+/**
+ * @ingroup iface_ext_workspace_handle_v1
+ *
+ * Request that this workspace be removed.
+ *
+ * There is no guarantee the workspace will be actually removed.
+ */
+static inline void
+ext_workspace_handle_v1_remove(struct ext_workspace_handle_v1 *ext_workspace_handle_v1)
+{
+ wl_proxy_marshal_flags((struct wl_proxy *) ext_workspace_handle_v1,
+ EXT_WORKSPACE_HANDLE_V1_REMOVE, NULL, wl_proxy_get_version((struct wl_proxy *) ext_workspace_handle_v1), 0);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/applets/wncklet/wayland-protocol/ext-workspace-v1-code.c b/applets/wncklet/wayland-protocol/ext-workspace-v1-code.c
new file mode 100644
index 00000000..682be28f
--- /dev/null
+++ b/applets/wncklet/wayland-protocol/ext-workspace-v1-code.c
@@ -0,0 +1,120 @@
+/* Generated by wayland-scanner 1.23.1 */
+
+/*
+ * Copyright © 2019 Christopher Billington
+ * Copyright © 2020 Ilia Bozhinov
+ * Copyright © 2022 Victoria Brekenfeld
+ *
+ * Permission to use, copy, modify, distribute, and sell this
+ * software and its documentation for any purpose is hereby granted
+ * without fee, provided that the above copyright notice appear in
+ * all copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of
+ * the copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include "wayland-util.h"
+
+#ifndef __has_attribute
+# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
+#endif
+
+#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
+#define WL_PRIVATE __attribute__ ((visibility("hidden")))
+#else
+#define WL_PRIVATE
+#endif
+
+extern const struct wl_interface ext_workspace_group_handle_v1_interface;
+extern const struct wl_interface ext_workspace_handle_v1_interface;
+extern const struct wl_interface wl_output_interface;
+
+static const struct wl_interface *ext_workspace_v1_types[] = {
+ NULL,
+ &ext_workspace_group_handle_v1_interface,
+ &ext_workspace_handle_v1_interface,
+ &wl_output_interface,
+ &wl_output_interface,
+ &ext_workspace_handle_v1_interface,
+ &ext_workspace_handle_v1_interface,
+ &ext_workspace_group_handle_v1_interface,
+};
+
+static const struct wl_message ext_workspace_manager_v1_requests[] = {
+ { "commit", "", ext_workspace_v1_types + 0 },
+ { "stop", "", ext_workspace_v1_types + 0 },
+};
+
+static const struct wl_message ext_workspace_manager_v1_events[] = {
+ { "workspace_group", "n", ext_workspace_v1_types + 1 },
+ { "workspace", "n", ext_workspace_v1_types + 2 },
+ { "done", "", ext_workspace_v1_types + 0 },
+ { "finished", "", ext_workspace_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface ext_workspace_manager_v1_interface = {
+ "ext_workspace_manager_v1", 1,
+ 2, ext_workspace_manager_v1_requests,
+ 4, ext_workspace_manager_v1_events,
+};
+
+static const struct wl_message ext_workspace_group_handle_v1_requests[] = {
+ { "create_workspace", "s", ext_workspace_v1_types + 0 },
+ { "destroy", "", ext_workspace_v1_types + 0 },
+};
+
+static const struct wl_message ext_workspace_group_handle_v1_events[] = {
+ { "capabilities", "u", ext_workspace_v1_types + 0 },
+ { "output_enter", "o", ext_workspace_v1_types + 3 },
+ { "output_leave", "o", ext_workspace_v1_types + 4 },
+ { "workspace_enter", "o", ext_workspace_v1_types + 5 },
+ { "workspace_leave", "o", ext_workspace_v1_types + 6 },
+ { "removed", "", ext_workspace_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface ext_workspace_group_handle_v1_interface = {
+ "ext_workspace_group_handle_v1", 1,
+ 2, ext_workspace_group_handle_v1_requests,
+ 6, ext_workspace_group_handle_v1_events,
+};
+
+static const struct wl_message ext_workspace_handle_v1_requests[] = {
+ { "destroy", "", ext_workspace_v1_types + 0 },
+ { "activate", "", ext_workspace_v1_types + 0 },
+ { "deactivate", "", ext_workspace_v1_types + 0 },
+ { "assign", "o", ext_workspace_v1_types + 7 },
+ { "remove", "", ext_workspace_v1_types + 0 },
+};
+
+static const struct wl_message ext_workspace_handle_v1_events[] = {
+ { "id", "s", ext_workspace_v1_types + 0 },
+ { "name", "s", ext_workspace_v1_types + 0 },
+ { "coordinates", "a", ext_workspace_v1_types + 0 },
+ { "state", "u", ext_workspace_v1_types + 0 },
+ { "capabilities", "u", ext_workspace_v1_types + 0 },
+ { "removed", "", ext_workspace_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface ext_workspace_handle_v1_interface = {
+ "ext_workspace_handle_v1", 1,
+ 5, ext_workspace_handle_v1_requests,
+ 6, ext_workspace_handle_v1_events,
+};
+
diff --git a/applets/wncklet/wayland-protocol/ext-workspace-v1.xml b/applets/wncklet/wayland-protocol/ext-workspace-v1.xml
new file mode 100644
index 00000000..7b58d45b
--- /dev/null
+++ b/applets/wncklet/wayland-protocol/ext-workspace-v1.xml
@@ -0,0 +1,422 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="ext_workspace_v1">
+ <copyright>
+ Copyright © 2019 Christopher Billington
+ Copyright © 2020 Ilia Bozhinov
+ Copyright © 2022 Victoria Brekenfeld
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+ </copyright>
+
+ <interface name="ext_workspace_manager_v1" version="1">
+ <description summary="list and control workspaces">
+ Workspaces, also called virtual desktops, are groups of surfaces. A
+ compositor with a concept of workspaces may only show some such groups of
+ surfaces (those of 'active' workspaces) at a time. 'Activating' a
+ workspace is a request for the compositor to display that workspace's
+ surfaces as normal, whereas the compositor may hide or otherwise
+ de-emphasise surfaces that are associated only with 'inactive' workspaces.
+ Workspaces are grouped by which sets of outputs they correspond to, and
+ may contain surfaces only from those outputs. In this way, it is possible
+ for each output to have its own set of workspaces, or for all outputs (or
+ any other arbitrary grouping) to share workspaces. Compositors may
+ optionally conceptually arrange each group of workspaces in an
+ N-dimensional grid.
+
+ The purpose of this protocol is to enable the creation of taskbars and
+ docks by providing them with a list of workspaces and their properties,
+ and allowing them to activate and deactivate workspaces.
+
+ After a client binds the ext_workspace_manager_v1, each workspace will be
+ sent via the workspace event.
+ </description>
+
+ <event name="workspace_group">
+ <description summary="a workspace group has been created">
+ This event is emitted whenever a new workspace group has been created.
+
+ All initial details of the workspace group (outputs) will be
+ sent immediately after this event via the corresponding events in
+ ext_workspace_group_handle_v1 and ext_workspace_handle_v1.
+ </description>
+ <arg name="workspace_group" type="new_id" interface="ext_workspace_group_handle_v1"/>
+ </event>
+
+ <event name="workspace">
+ <description summary="workspace has been created">
+ This event is emitted whenever a new workspace has been created.
+
+ All initial details of the workspace (name, coordinates, state) will
+ be sent immediately after this event via the corresponding events in
+ ext_workspace_handle_v1.
+
+ Workspaces start off unassigned to any workspace group.
+ </description>
+ <arg name="workspace" type="new_id" interface="ext_workspace_handle_v1"/>
+ </event>
+
+ <request name="commit">
+ <description summary="all requests about the workspaces have been sent">
+ The client must send this request after it has finished sending other
+ requests. The compositor must process a series of requests preceding a
+ commit request atomically.
+
+ This allows changes to the workspace properties to be seen as atomic,
+ even if they happen via multiple events, and even if they involve
+ multiple ext_workspace_handle_v1 objects, for example, deactivating one
+ workspace and activating another.
+ </description>
+ </request>
+
+ <event name="done">
+ <description summary="all information about the workspaces and workspace groups has been sent">
+ This event is sent after all changes in all workspaces and workspace groups have been
+ sent.
+
+ This allows changes to one or more ext_workspace_group_handle_v1
+ properties and ext_workspace_handle_v1 properties
+ to be seen as atomic, even if they happen via multiple events.
+ In particular, an output moving from one workspace group to
+ another sends an output_enter event and an output_leave event to the two
+ ext_workspace_group_handle_v1 objects in question. The compositor sends
+ the done event only after updating the output information in both
+ workspace groups.
+ </description>
+ </event>
+
+ <event name="finished" type="destructor">
+ <description summary="the compositor has finished with the workspace_manager">
+ This event indicates that the compositor is done sending events to the
+ ext_workspace_manager_v1. The server will destroy the object
+ immediately after sending this request.
+ </description>
+ </event>
+
+ <request name="stop">
+ <description summary="stop sending events">
+ Indicates the client no longer wishes to receive events for new
+ workspace groups. However the compositor may emit further workspace
+ events, until the finished event is emitted. The compositor is expected
+ to send the finished event eventually once the stop request has been processed.
+
+ The client must not send any requests after this one, doing so will raise a wl_display
+ invalid_object error.
+ </description>
+ </request>
+
+ </interface>
+
+ <interface name="ext_workspace_group_handle_v1" version="1">
+ <description summary="a workspace group assigned to a set of outputs">
+ A ext_workspace_group_handle_v1 object represents a workspace group
+ that is assigned a set of outputs and contains a number of workspaces.
+
+ The set of outputs assigned to the workspace group is conveyed to the client via
+ output_enter and output_leave events, and its workspaces are conveyed with
+ workspace events.
+
+ For example, a compositor which has a set of workspaces for each output may
+ advertise a workspace group (and its workspaces) per output, whereas a compositor
+ where a workspace spans all outputs may advertise a single workspace group for all
+ outputs.
+ </description>
+
+ <enum name="group_capabilities" bitfield="true">
+ <entry name="create_workspace" value="1" summary="create_workspace request is available"/>
+ </enum>
+
+ <event name="capabilities">
+ <description summary="compositor capabilities">
+ This event advertises the capabilities supported by the compositor. If
+ a capability isn't supported, clients should hide or disable the UI
+ elements that expose this functionality. For instance, if the
+ compositor doesn't advertise support for creating workspaces, a button
+ triggering the create_workspace request should not be displayed.
+
+ The compositor will ignore requests it doesn't support. For instance,
+ a compositor which doesn't advertise support for creating workspaces will ignore
+ create_workspace requests.
+
+ Compositors must send this event once after creation of an
+ ext_workspace_group_handle_v1. When the capabilities change, compositors
+ must send this event again.
+ </description>
+ <arg name="capabilities" type="uint" summary="capabilities" enum="group_capabilities"/>
+ </event>
+
+ <event name="output_enter">
+ <description summary="output assigned to workspace group">
+ This event is emitted whenever an output is assigned to the workspace
+ group or a new `wl_output` object is bound by the client, which was already
+ assigned to this workspace_group.
+ </description>
+ <arg name="output" type="object" interface="wl_output"/>
+ </event>
+
+ <event name="output_leave">
+ <description summary="output removed from workspace group">
+ This event is emitted whenever an output is removed from the workspace
+ group.
+ </description>
+ <arg name="output" type="object" interface="wl_output"/>
+ </event>
+
+ <event name="workspace_enter">
+ <description summary="workspace added to workspace group">
+ This event is emitted whenever a workspace is assigned to this group.
+ A workspace may only ever be assigned to a single group at a single point
+ in time, but can be re-assigned during its lifetime.
+ </description>
+ <arg name="workspace" type="object" interface="ext_workspace_handle_v1"/>
+ </event>
+
+ <event name="workspace_leave">
+ <description summary="workspace removed from workspace group">
+ This event is emitted whenever a workspace is removed from this group.
+ </description>
+ <arg name="workspace" type="object" interface="ext_workspace_handle_v1"/>
+ </event>
+
+ <event name="removed">
+ <description summary="this workspace group has been removed">
+ This event is send when the group associated with the ext_workspace_group_handle_v1
+ has been removed. After sending this request the compositor will immediately consider
+ the object inert. Any requests will be ignored except the destroy request.
+ It is guaranteed there won't be any more events referencing this
+ ext_workspace_group_handle_v1.
+
+ The compositor must remove all workspaces belonging to a workspace group
+ via a workspace_leave event before removing the workspace group.
+ </description>
+ </event>
+
+ <request name="create_workspace">
+ <description summary="create a new workspace">
+ Request that the compositor create a new workspace with the given name
+ and assign it to this group.
+
+ There is no guarantee that the compositor will create a new workspace,
+ or that the created workspace will have the provided name.
+ </description>
+ <arg name="workspace" type="string"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the ext_workspace_group_handle_v1 object">
+ Destroys the ext_workspace_group_handle_v1 object.
+
+ This request should be send either when the client does not want to
+ use the workspace group object any more or after the removed event to finalize
+ the destruction of the object.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="ext_workspace_handle_v1" version="1">
+ <description summary="a workspace handing a group of surfaces">
+ A ext_workspace_handle_v1 object represents a workspace that handles a
+ group of surfaces.
+
+ Each workspace has:
+ - a name, conveyed to the client with the name event
+ - potentially an id conveyed with the id event
+ - a list of states, conveyed to the client with the state event
+ - and optionally a set of coordinates, conveyed to the client with the
+ coordinates event
+
+ The client may request that the compositor activate or deactivate the workspace.
+
+ Each workspace can belong to only a single workspace group.
+ Depending on the compositor policy, there might be workspaces with
+ the same name in different workspace groups, but these workspaces are still
+ separate (e.g. one of them might be active while the other is not).
+ </description>
+
+ <event name="id">
+ <description summary="workspace id">
+ If this event is emitted, it will be send immediately after the
+ ext_workspace_handle_v1 is created or when an id is assigned to
+ a workspace (at most once during its lifetime).
+
+ An id will never change during the lifetime of the `ext_workspace_handle_v1`
+ and is guaranteed to be unique during its lifetime.
+
+ Ids are not human-readable and shouldn't be displayed, use `name` for that purpose.
+
+ Compositors are expected to only send ids for workspaces likely stable across multiple
+ sessions and can be used by clients to store preferences for workspaces. Workspaces without
+ ids should be considered temporary and any data associated with them should be deleted once
+ the respective object is lost.
+ </description>
+ <arg name="id" type="string"/>
+ </event>
+
+ <event name="name">
+ <description summary="workspace name changed">
+ This event is emitted immediately after the ext_workspace_handle_v1 is
+ created and whenever the name of the workspace changes.
+
+ A name is meant to be human-readable and can be displayed to a user.
+ Unlike the id it is neither stable nor unique.
+ </description>
+ <arg name="name" type="string"/>
+ </event>
+
+ <event name="coordinates">
+ <description summary="workspace coordinates changed">
+ This event is used to organize workspaces into an N-dimensional grid
+ within a workspace group, and if supported, is emitted immediately after
+ the ext_workspace_handle_v1 is created and whenever the coordinates of
+ the workspace change. Compositors may not send this event if they do not
+ conceptually arrange workspaces in this way. If compositors simply
+ number workspaces, without any geometric interpretation, they may send
+ 1D coordinates, which clients should not interpret as implying any
+ geometry. Sending an empty array means that the compositor no longer
+ orders the workspace geometrically.
+
+ Coordinates have an arbitrary number of dimensions N with an uint32
+ position along each dimension. By convention if N > 1, the first
+ dimension is X, the second Y, the third Z, and so on. The compositor may
+ chose to utilize these events for a more novel workspace layout
+ convention, however. No guarantee is made about the grid being filled or
+ bounded; there may be a workspace at coordinate 1 and another at
+ coordinate 1000 and none in between. Within a workspace group, however,
+ workspaces must have unique coordinates of equal dimensionality.
+ </description>
+ <arg name="coordinates" type="array"/>
+ </event>
+
+ <enum name="state" bitfield="true">
+ <description summary="types of states on the workspace">
+ The different states that a workspace can have.
+ </description>
+
+ <entry name="active" value="1" summary="the workspace is active"/>
+ <entry name="urgent" value="2" summary="the workspace requests attention"/>
+ <entry name="hidden" value="4">
+ <description summary="the workspace is not visible">
+ The workspace is not visible in its workspace group, and clients
+ attempting to visualize the compositor workspace state should not
+ display such workspaces.
+ </description>
+ </entry>
+ </enum>
+
+ <event name="state">
+ <description summary="the state of the workspace changed">
+ This event is emitted immediately after the ext_workspace_handle_v1 is
+ created and each time the workspace state changes, either because of a
+ compositor action or because of a request in this protocol.
+
+ Missing states convey the opposite meaning, e.g. an unset active bit
+ means the workspace is currently inactive.
+ </description>
+ <arg name="state" type="uint" enum="state"/>
+ </event>
+
+ <enum name="workspace_capabilities" bitfield="true">
+ <entry name="activate" value="1" summary="activate request is available"/>
+ <entry name="deactivate" value="2" summary="deactivate request is available"/>
+ <entry name="remove" value="4" summary="remove request is available"/>
+ <entry name="assign" value="8" summary="assign request is available"/>
+ </enum>
+
+ <event name="capabilities">
+ <description summary="compositor capabilities">
+ This event advertises the capabilities supported by the compositor. If
+ a capability isn't supported, clients should hide or disable the UI
+ elements that expose this functionality. For instance, if the
+ compositor doesn't advertise support for removing workspaces, a button
+ triggering the remove request should not be displayed.
+
+ The compositor will ignore requests it doesn't support. For instance,
+ a compositor which doesn't advertise support for remove will ignore
+ remove requests.
+
+ Compositors must send this event once after creation of an
+ ext_workspace_handle_v1 . When the capabilities change, compositors
+ must send this event again.
+ </description>
+ <arg name="capabilities" type="uint" summary="capabilities" enum="workspace_capabilities"/>
+ </event>
+
+ <event name="removed">
+ <description summary="this workspace has been removed">
+ This event is send when the workspace associated with the ext_workspace_handle_v1
+ has been removed. After sending this request, the compositor will immediately consider
+ the object inert. Any requests will be ignored except the destroy request.
+
+ It is guaranteed there won't be any more events referencing this
+ ext_workspace_handle_v1.
+
+ The compositor must only remove a workspaces not currently belonging to any
+ workspace_group.
+ </description>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the ext_workspace_handle_v1 object">
+ Destroys the ext_workspace_handle_v1 object.
+
+ This request should be made either when the client does not want to
+ use the workspace object any more or after the remove event to finalize
+ the destruction of the object.
+ </description>
+ </request>
+
+ <request name="activate">
+ <description summary="activate the workspace">
+ Request that this workspace be activated.
+
+ There is no guarantee the workspace will be actually activated, and
+ behaviour may be compositor-dependent. For example, activating a
+ workspace may or may not deactivate all other workspaces in the same
+ group.
+ </description>
+ </request>
+
+ <request name="deactivate">
+ <description summary="deactivate the workspace">
+ Request that this workspace be deactivated.
+
+ There is no guarantee the workspace will be actually deactivated.
+ </description>
+ </request>
+
+ <request name="assign">
+ <description summary="assign workspace to group">
+ Requests that this workspace is assigned to the given workspace group.
+
+ There is no guarantee the workspace will be assigned.
+ </description>
+ <arg name="workspace_group" type="object" interface="ext_workspace_group_handle_v1"/>
+ </request>
+
+ <request name="remove">
+ <description summary="remove the workspace">
+ Request that this workspace be removed.
+
+ There is no guarantee the workspace will be actually removed.
+ </description>
+ </request>
+ </interface>
+</protocol>
diff --git a/applets/wncklet/wayland-workspace.c b/applets/wncklet/wayland-workspace.c
new file mode 100644
index 00000000..5f42783d
--- /dev/null
+++ b/applets/wncklet/wayland-workspace.c
@@ -0,0 +1,698 @@
+/*
+ * 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.
+ */
+
+#include <config.h>
+
+#include <gdk/gdkwayland.h>
+#include <gtk/gtk.h>
+
+#include "wayland-workspace.h"
+#include "wayland-protocol/ext-workspace-v1-client.h"
+
+typedef struct _WaylandWorkspaceData WaylandWorkspaceData;
+
+typedef struct {
+ struct ext_workspace_handle_v1 *handle;
+ WaylandWorkspaceData *pager;
+ char *name;
+ gboolean active;
+ gboolean hidden;
+ gboolean urgent;
+} WaylandWorkspace;
+
+typedef struct {
+ struct ext_workspace_group_handle_v1 *handle;
+ WaylandWorkspaceData *pager;
+ GList *workspaces;
+} WaylandWorkspaceGroup;
+
+struct _WaylandWorkspaceData {
+ GtkWidget *grid;
+ GtkWidget *outer_box;
+
+ struct ext_workspace_manager_v1 *manager;
+ GList *workspace_groups;
+ GList *pending_workspaces;
+
+ GtkOrientation orientation;
+ int n_rows;
+ gboolean display_all;
+ gboolean display_names;
+};
+
+static struct wl_registry *wl_registry_global = NULL;
+static uint32_t workspace_manager_global_id = 0;
+static uint32_t workspace_manager_global_version = 0;
+static gboolean has_initialized = FALSE;
+
+static void rebuild_ui (WaylandWorkspaceData *data);
+
+static void
+wl_registry_handle_global (void *_data,
+ struct wl_registry *registry,
+ uint32_t id,
+ const char *interface,
+ uint32_t version)
+{
+ if (strcmp (interface, ext_workspace_manager_v1_interface.name) == 0)
+ {
+ workspace_manager_global_id = id;
+ workspace_manager_global_version =
+ MIN ((uint32_t) ext_workspace_manager_v1_interface.version, version);
+ }
+}
+
+static void
+wl_registry_handle_global_remove (void *_data,
+ struct wl_registry *_registry,
+ uint32_t id)
+{
+ if (id == workspace_manager_global_id)
+ {
+ workspace_manager_global_id = 0;
+ }
+}
+
+static const struct wl_registry_listener wl_registry_listener = {
+ .global = wl_registry_handle_global,
+ .global_remove = wl_registry_handle_global_remove,
+};
+
+static void
+wayland_workspace_init_if_needed (void)
+{
+ if (has_initialized)
+ return;
+
+ GdkDisplay *gdk_display = gdk_display_get_default ();
+ g_return_if_fail (gdk_display);
+ g_return_if_fail (GDK_IS_WAYLAND_DISPLAY (gdk_display));
+
+ struct wl_display *wl_display = gdk_wayland_display_get_wl_display (gdk_display);
+ wl_registry_global = wl_display_get_registry (wl_display);
+ wl_registry_add_listener (wl_registry_global, &wl_registry_listener, NULL);
+ wl_display_roundtrip (wl_display);
+
+ if (!workspace_manager_global_id)
+ g_warning ("%s not supported by Wayland compositor",
+ ext_workspace_manager_v1_interface.name);
+
+ has_initialized = TRUE;
+}
+
+static WaylandWorkspace *
+wayland_workspace_new_from_handle (struct ext_workspace_handle_v1 *handle,
+ WaylandWorkspaceData *pager)
+{
+ WaylandWorkspace *ws = g_new0 (WaylandWorkspace, 1);
+ ws->handle = handle;
+ ws->pager = pager;
+ ws->name = g_strdup ("");
+ ws->active = FALSE;
+ ws->hidden = FALSE;
+ ws->urgent = FALSE;
+ return ws;
+}
+
+static void
+wayland_workspace_free (WaylandWorkspace *ws)
+{
+ if (ws)
+ {
+ g_free (ws->name);
+ g_free (ws);
+ }
+}
+
+static WaylandWorkspaceGroup *
+wayland_workspace_group_new (struct ext_workspace_group_handle_v1 *handle,
+ WaylandWorkspaceData *pager)
+{
+ WaylandWorkspaceGroup *group = g_new0 (WaylandWorkspaceGroup, 1);
+ group->handle = handle;
+ group->pager = pager;
+ group->workspaces = NULL;
+ return group;
+}
+
+static void
+wayland_workspace_group_free (WaylandWorkspaceGroup *group)
+{
+ if (group)
+ {
+ g_list_free_full (group->workspaces, (GDestroyNotify) wayland_workspace_free);
+ g_free (group);
+ }
+}
+
+static void
+workspace_handle_id (void *data,
+ struct ext_workspace_handle_v1 *handle,
+ const char *id)
+{
+}
+
+static void
+workspace_handle_name (void *data,
+ struct ext_workspace_handle_v1 *handle,
+ const char *name)
+{
+ WaylandWorkspace *ws = data;
+ g_free (ws->name);
+ ws->name = g_strdup (name ? name : "");
+}
+
+static void
+workspace_handle_coordinates (void *data,
+ struct ext_workspace_handle_v1 *handle,
+ struct wl_array *coordinates)
+{
+}
+
+static void
+workspace_handle_state (void *data,
+ struct ext_workspace_handle_v1 *handle,
+ uint32_t state)
+{
+ WaylandWorkspace *ws = data;
+ ws->active = (state & EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE) != 0;
+ ws->hidden = (state & EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN) != 0;
+ ws->urgent = (state & EXT_WORKSPACE_HANDLE_V1_STATE_URGENT) != 0;
+}
+
+static void
+workspace_handle_capabilities (void *data,
+ struct ext_workspace_handle_v1 *handle,
+ uint32_t capabilities)
+{
+}
+
+static void
+workspace_handle_removed (void *data,
+ struct ext_workspace_handle_v1 *handle)
+{
+}
+
+static const struct ext_workspace_handle_v1_listener workspace_handle_listener = {
+ .id = workspace_handle_id,
+ .name = workspace_handle_name,
+ .coordinates = workspace_handle_coordinates,
+ .state = workspace_handle_state,
+ .capabilities = workspace_handle_capabilities,
+ .removed = workspace_handle_removed,
+};
+
+static void
+workspace_group_handle_capabilities (void *data,
+ struct ext_workspace_group_handle_v1 *handle,
+ uint32_t capabilities)
+{
+}
+
+static void
+workspace_group_handle_output_enter (void *data,
+ struct ext_workspace_group_handle_v1 *handle,
+ struct wl_output *output)
+{
+}
+
+static void
+workspace_group_handle_output_leave (void *data,
+ struct ext_workspace_group_handle_v1 *handle,
+ struct wl_output *output)
+{
+}
+
+static void
+workspace_group_handle_workspace_enter (void *data,
+ struct ext_workspace_group_handle_v1 *handle,
+ struct ext_workspace_handle_v1 *workspace_handle)
+{
+ WaylandWorkspaceGroup *group = data;
+ WaylandWorkspaceData *pager = group->pager;
+ GList *l;
+ WaylandWorkspace *ws = NULL;
+
+ for (l = pager->pending_workspaces; l != NULL; l = l->next)
+ {
+ WaylandWorkspace *ws_candidate = l->data;
+ if (ws_candidate->handle == workspace_handle)
+ {
+ ws = ws_candidate;
+ pager->pending_workspaces = g_list_delete_link (pager->pending_workspaces, l);
+ break;
+ }
+ }
+
+ if (!ws)
+ {
+ ws = wayland_workspace_new_from_handle (workspace_handle, pager);
+ ext_workspace_handle_v1_add_listener (workspace_handle,
+ &workspace_handle_listener,
+ ws);
+ }
+
+ group->workspaces = g_list_append (group->workspaces, ws);
+}
+
+static void
+workspace_group_handle_workspace_leave (void *data,
+ struct ext_workspace_group_handle_v1 *handle,
+ struct ext_workspace_handle_v1 *workspace_handle)
+{
+ WaylandWorkspaceGroup *group = data;
+ GList *l;
+
+ for (l = group->workspaces; l != NULL; l = l->next)
+ {
+ WaylandWorkspace *ws = l->data;
+ if (ws->handle == workspace_handle)
+ {
+ group->workspaces = g_list_delete_link (group->workspaces, l);
+ wayland_workspace_free (ws);
+ break;
+ }
+ }
+}
+
+static void
+workspace_group_handle_removed (void *data,
+ struct ext_workspace_group_handle_v1 *handle)
+{
+}
+
+static const struct ext_workspace_group_handle_v1_listener workspace_group_handle_listener = {
+ .capabilities = workspace_group_handle_capabilities,
+ .output_enter = workspace_group_handle_output_enter,
+ .output_leave = workspace_group_handle_output_leave,
+ .workspace_enter = workspace_group_handle_workspace_enter,
+ .workspace_leave = workspace_group_handle_workspace_leave,
+ .removed = workspace_group_handle_removed,
+};
+
+static void
+manager_handle_workspace_group (void *data,
+ struct ext_workspace_manager_v1 *manager,
+ struct ext_workspace_group_handle_v1 *group_handle)
+{
+ WaylandWorkspaceData *pager = data;
+
+ WaylandWorkspaceGroup *group = wayland_workspace_group_new (group_handle, pager);
+ ext_workspace_group_handle_v1_add_listener (group_handle,
+ &workspace_group_handle_listener,
+ group);
+ pager->workspace_groups = g_list_append (pager->workspace_groups, group);
+}
+
+static void
+manager_handle_workspace (void *data,
+ struct ext_workspace_manager_v1 *manager,
+ struct ext_workspace_handle_v1 *workspace_handle)
+{
+ WaylandWorkspaceData *pager = data;
+ WaylandWorkspace *ws = wayland_workspace_new_from_handle (workspace_handle, pager);
+
+ ext_workspace_handle_v1_add_listener (workspace_handle,
+ &workspace_handle_listener,
+ ws);
+
+ pager->pending_workspaces = g_list_append (pager->pending_workspaces, ws);
+}
+
+static void
+manager_handle_done (void *data,
+ struct ext_workspace_manager_v1 *manager)
+{
+ WaylandWorkspaceData *pager = data;
+ GList *l;
+
+ /* Fallback: if workspaces are still pending (compositor didn't send
+ * workspace_enter for them), assign them to the first group */
+ if (pager->pending_workspaces != NULL && pager->workspace_groups != NULL)
+ {
+ WaylandWorkspaceGroup *first_group = pager->workspace_groups->data;
+
+ for (l = pager->pending_workspaces; l != NULL; l = l->next)
+ {
+ WaylandWorkspace *ws = l->data;
+ first_group->workspaces = g_list_append (first_group->workspaces, ws);
+ }
+ g_list_free (pager->pending_workspaces);
+ pager->pending_workspaces = NULL;
+ }
+
+ rebuild_ui (pager);
+}
+
+static void
+manager_handle_finished (void *data,
+ struct ext_workspace_manager_v1 *manager)
+{
+ WaylandWorkspaceData *pager = data;
+
+ pager->manager = NULL;
+ ext_workspace_manager_v1_destroy (manager);
+
+ if (pager->outer_box)
+ g_object_set_data (G_OBJECT (pager->outer_box),
+ "wayland_pager_data",
+ NULL);
+}
+
+static const struct ext_workspace_manager_v1_listener manager_listener = {
+ .workspace_group = manager_handle_workspace_group,
+ .workspace = manager_handle_workspace,
+ .done = manager_handle_done,
+ .finished = manager_handle_finished,
+};
+
+static void
+workspace_button_clicked (GtkButton *button, WaylandWorkspace *ws)
+{
+ if (ws->handle && ws->pager->manager)
+ {
+ ext_workspace_handle_v1_activate (ws->handle);
+ ext_workspace_manager_v1_commit (ws->pager->manager);
+ }
+}
+
+static void
+rebuild_ui (WaylandWorkspaceData *data)
+{
+ GList *l;
+ int n_workspaces = 0;
+ int n_cols, n_visual_rows;
+ int row, col;
+ int i;
+
+ /* Remove existing children */
+ if (data->grid)
+ {
+ gtk_widget_destroy (data->grid);
+ data->grid = NULL;
+ }
+
+ /* Count visible workspaces */
+ for (l = data->workspace_groups; l != NULL; l = l->next)
+ {
+ WaylandWorkspaceGroup *group = l->data;
+ GList *wl;
+ for (wl = group->workspaces; wl != NULL; wl = wl->next)
+ {
+ WaylandWorkspace *ws = wl->data;
+ if (!ws->hidden || data->display_all)
+ n_workspaces++;
+ }
+ }
+
+ if (n_workspaces == 0)
+ {
+ /* Show at least one empty slot */
+ n_workspaces = 1;
+ }
+
+ /* Calculate grid dimensions */
+ if (data->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ n_visual_rows = data->n_rows;
+ n_cols = n_workspaces / n_visual_rows;
+ if (n_workspaces % n_visual_rows != 0)
+ n_cols++;
+ }
+ else
+ {
+ n_cols = data->n_rows;
+ n_visual_rows = n_workspaces / n_cols;
+ if (n_workspaces % n_cols != 0)
+ n_visual_rows++;
+ }
+
+ data->grid = gtk_grid_new ();
+ gtk_grid_set_column_homogeneous (GTK_GRID (data->grid), TRUE);
+ gtk_grid_set_row_homogeneous (GTK_GRID (data->grid), TRUE);
+ gtk_container_add (GTK_CONTAINER (data->outer_box), data->grid);
+
+ /* Create buttons for each workspace */
+ i = 0;
+ for (l = data->workspace_groups; l != NULL; l = l->next)
+ {
+ WaylandWorkspaceGroup *group = l->data;
+ GList *wl;
+ for (wl = group->workspaces; wl != NULL; wl = wl->next)
+ {
+ WaylandWorkspace *ws = wl->data;
+ GtkWidget *button;
+ GtkWidget *label;
+ char *text;
+
+ if (ws->hidden && !data->display_all)
+ continue;
+
+ button = gtk_button_new ();
+ gtk_widget_set_name (button, "wnck-pager-button");
+
+ if (data->display_names && ws->name && ws->name[0] != '\0')
+ text = g_strdup (ws->name);
+ else
+ text = g_strdup_printf ("%d", i + 1);
+
+ label = gtk_label_new (text);
+ g_free (text);
+
+ gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
+ gtk_label_set_max_width_chars (GTK_LABEL (label), 8);
+ gtk_container_add (GTK_CONTAINER (button), label);
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (workspace_button_clicked), ws);
+
+ g_object_set_data (G_OBJECT (button), "workspace_data", ws);
+
+ /* Add CSS class for active workspace styling */
+ GtkStyleContext *context = gtk_widget_get_style_context (button);
+ gtk_style_context_add_class (context, "wnck-pager");
+ if (ws->active)
+ gtk_style_context_add_class (context, "selected");
+
+ if (data->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ row = i % n_visual_rows;
+ col = i / n_visual_rows;
+ }
+ else
+ {
+ col = i % n_cols;
+ row = i / n_cols;
+ }
+
+ gtk_grid_attach (GTK_GRID (data->grid), button, col, row, 1, 1);
+ gtk_widget_show_all (button);
+
+ i++;
+ }
+ }
+
+ /* If no workspaces were visible, show an empty placeholder */
+ if (i == 0)
+ {
+ GtkWidget *button = gtk_button_new ();
+ gtk_widget_set_sensitive (button, FALSE);
+ gtk_widget_set_name (button, "wnck-pager-button");
+ GtkWidget *label = gtk_label_new ("1");
+ gtk_container_add (GTK_CONTAINER (button), label);
+ gtk_grid_attach (GTK_GRID (data->grid), button, 0, 0, 1, 1);
+ gtk_widget_show_all (button);
+ }
+
+ gtk_widget_show (data->grid);
+}
+
+static void
+wayland_pager_disconnected (WaylandWorkspaceData *data)
+{
+ if (data->grid)
+ {
+ gtk_widget_destroy (data->grid);
+ data->grid = NULL;
+ }
+
+ if (data->manager)
+ ext_workspace_manager_v1_stop (data->manager);
+
+ g_list_free_full (data->workspace_groups, (GDestroyNotify) wayland_workspace_group_free);
+ data->workspace_groups = NULL;
+
+ g_list_free_full (data->pending_workspaces, (GDestroyNotify) wayland_workspace_free);
+ data->pending_workspaces = NULL;
+}
+
+GtkWidget *
+wayland_workspace_new (void)
+{
+ wayland_workspace_init_if_needed ();
+
+ WaylandWorkspaceData *data = g_new0 (WaylandWorkspaceData, 1);
+ data->orientation = GTK_ORIENTATION_HORIZONTAL;
+ data->n_rows = 1;
+ data->display_all = TRUE;
+ data->display_names = FALSE;
+
+ data->outer_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_show (data->outer_box);
+
+ g_object_set_data_full (G_OBJECT (data->outer_box),
+ "wayland_pager_data",
+ data,
+ (GDestroyNotify) wayland_pager_disconnected);
+
+ if (!workspace_manager_global_id)
+ {
+ g_warning ("ext_workspace_manager_v1 not available");
+ return data->outer_box;
+ }
+
+ data->manager = wl_registry_bind (wl_registry_global,
+ workspace_manager_global_id,
+ &ext_workspace_manager_v1_interface,
+ workspace_manager_global_version);
+
+ ext_workspace_manager_v1_add_listener (data->manager,
+ &manager_listener,
+ data);
+
+ /* Trigger initial roundtrip to get workspace state */
+ GdkDisplay *gdk_display = gdk_display_get_default ();
+ if (gdk_display)
+ {
+ struct wl_display *wl_display = gdk_wayland_display_get_wl_display (gdk_display);
+ wl_display_roundtrip (wl_display);
+ }
+
+ return data->outer_box;
+}
+
+static WaylandWorkspaceData *
+pager_widget_get_data (GtkWidget *pager_widget)
+{
+ return g_object_get_data (G_OBJECT (pager_widget), "wayland_pager_data");
+}
+
+void
+wayland_workspace_set_orientation (GtkWidget *pager_widget, GtkOrientation orientation)
+{
+ WaylandWorkspaceData *data = pager_widget_get_data (pager_widget);
+ g_return_if_fail (data);
+
+ if (data->orientation == orientation)
+ return;
+
+ data->orientation = orientation;
+
+ if (data->grid)
+ rebuild_ui (data);
+}
+
+void
+wayland_workspace_set_rows (GtkWidget *pager_widget, int n_rows)
+{
+ WaylandWorkspaceData *data = pager_widget_get_data (pager_widget);
+ g_return_if_fail (data);
+
+ n_rows = CLAMP (n_rows, 1, 16);
+
+ if (data->n_rows == n_rows)
+ return;
+
+ data->n_rows = n_rows;
+
+ if (data->grid)
+ rebuild_ui (data);
+}
+
+void
+wayland_workspace_set_show_all (GtkWidget *pager_widget, gboolean show_all)
+{
+ WaylandWorkspaceData *data = pager_widget_get_data (pager_widget);
+ g_return_if_fail (data);
+
+ if (data->display_all == show_all)
+ return;
+
+ data->display_all = show_all;
+
+ if (data->grid)
+ rebuild_ui (data);
+}
+
+void
+wayland_workspace_set_show_names (GtkWidget *pager_widget, gboolean show_names)
+{
+ WaylandWorkspaceData *data = pager_widget_get_data (pager_widget);
+ g_return_if_fail (data);
+
+ if (data->display_names == show_names)
+ return;
+
+ data->display_names = show_names;
+
+ if (data->grid)
+ rebuild_ui (data);
+}
+
+int
+wayland_workspace_get_count (GtkWidget *pager_widget)
+{
+ WaylandWorkspaceData *data = pager_widget_get_data (pager_widget);
+ int count = 0;
+ GList *l;
+
+ g_return_val_if_fail (data, 0);
+
+ for (l = data->workspace_groups; l != NULL; l = l->next)
+ {
+ WaylandWorkspaceGroup *group = l->data;
+ count += g_list_length (group->workspaces);
+ }
+
+ return count;
+}
+
+const char *
+wayland_workspace_get_name (GtkWidget *pager_widget, int index)
+{
+ WaylandWorkspaceData *data = pager_widget_get_data (pager_widget);
+ int i = 0;
+ GList *l, *wl;
+
+ g_return_val_if_fail (data, "workspace");
+
+ for (l = data->workspace_groups; l != NULL; l = l->next)
+ {
+ WaylandWorkspaceGroup *group = l->data;
+ for (wl = group->workspaces; wl != NULL; wl = wl->next)
+ {
+ WaylandWorkspace *ws = wl->data;
+ if (i == index)
+ return ws->name ? ws->name : "";
+ i++;
+ }
+ }
+
+ return "workspace";
+}
diff --git a/applets/wncklet/wayland-workspace.h b/applets/wncklet/wayland-workspace.h
new file mode 100644
index 00000000..4df1bda1
--- /dev/null
+++ b/applets/wncklet/wayland-workspace.h
@@ -0,0 +1,49 @@
+/* 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 <gtk/gtk.h>
+#include <gdk/gdk.h>
+
+#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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _WNCKLET_APPLET_WAYLAND_WORKSPACE_H_ */
diff --git a/applets/wncklet/wncklet.c b/applets/wncklet/wncklet.c
index 1ced4507..5662aead 100644
--- a/applets/wncklet/wncklet.c
+++ b/applets/wncklet/wncklet.c
@@ -131,10 +131,8 @@ static gboolean wncklet_factory(MatePanelApplet* applet, const char* iid, gpoint
if (!strcmp(iid, "WindowMenuApplet"))
retval = window_menu_applet_fill(applet);
-#ifdef HAVE_X11
else if (!strcmp(iid, "WorkspaceSwitcherApplet") || !strcmp(iid, "PagerApplet"))
retval = workspace_switcher_applet_fill(applet);
-#endif
else if (!strcmp(iid, "WindowListApplet") || !strcmp(iid, "TasklistApplet"))
retval = window_list_applet_fill(applet);
else if (!strcmp(iid, "ShowDesktopApplet"))
diff --git a/applets/wncklet/workspace-switcher.c b/applets/wncklet/workspace-switcher.c
index cba0ecbd..689979a5 100644
--- a/applets/wncklet/workspace-switcher.c
+++ b/applets/wncklet/workspace-switcher.c
@@ -31,6 +31,7 @@
#ifdef HAVE_WAYLAND
#include <gdk/gdkwayland.h>
+#include "wayland-workspace.h"
#endif /* HAVE_WAYLAND */
#include <libmate-desktop/mate-gsettings.h>
@@ -217,8 +218,11 @@ typedef struct {
#ifdef HAVE_X11
WnckHandle* wnck_handle;
-#endif
WnckScreen* screen;
+#endif
+#ifdef HAVE_WAYLAND
+ gboolean is_wayland;
+#endif
PagerWM wm;
/* Properties: */
@@ -280,6 +284,16 @@ static void pager_update(PagerData* pager)
pager_update_wnck(pager, WNCK_PAGER(pager->pager));
}
#endif /* HAVE_X11 */
+
+#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 */
}
static void update_properties_for_wm(PagerData* pager)
@@ -365,9 +379,9 @@ static void update_properties_for_wm(PagerData* pager)
}
}
+#ifdef HAVE_X11
static void window_manager_changed(WnckScreen* screen, PagerData* pager)
{
-#ifdef HAVE_X11
const char *wm_name = NULL;
if (pager->screen)
@@ -389,13 +403,11 @@ static void window_manager_changed(WnckScreen* screen, PagerData* pager)
pager->wm = PAGER_WM_COMPIZ;
else
pager->wm = PAGER_WM_UNKNOWN;
-#else
- pager->wm = PAGER_WM_UNKNOWN;
-#endif /* HAVE_X11 */
update_properties_for_wm(pager);
pager_update(pager);
}
+#endif /* HAVE_X11 */
static void applet_realized(MatePanelApplet* applet, PagerData* pager)
{
@@ -404,10 +416,17 @@ static void applet_realized(MatePanelApplet* applet, PagerData* pager)
{
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 */
- window_manager_changed(pager->screen, pager);
+#ifdef HAVE_WAYLAND
+ if (pager->is_wayland)
+ {
+ pager_update(pager);
+ }
+#endif /* HAVE_WAYLAND */
}
static void applet_unrealized(MatePanelApplet* applet, PagerData* pager)
@@ -508,6 +527,17 @@ static gboolean applet_scroll(MatePanelApplet* applet, GdkEventScroll* event, Pa
if (event->direction == GDK_SCROLL_SMOOTH)
return FALSE;
+#ifdef HAVE_WAYLAND
+ if (pager->is_wayland)
+ {
+ /* The Wayland backend handles its own scroll internally via
+ * the scroll event on the workspace widget. We don't need to
+ * do anything here since the workspace widget connects its
+ * own scroll handler. Just return TRUE to consume the event. */
+ return TRUE;
+ }
+#endif /* HAVE_WAYLAND */
+
#ifdef HAVE_X11
if (pager->screen)
{
@@ -644,10 +674,21 @@ 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 */
n_rows = CLAMP (g_settings_get_int (settings, key),
1,
- MIN (wnck_screen_get_workspace_count (pager->screen),
+ MIN (ws_count,
MAX_REASONABLE_ROWS));
pager->n_rows = n_rows;
@@ -785,7 +826,8 @@ gboolean workspace_switcher_applet_fill(MatePanelApplet* applet)
#ifdef HAVE_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ()))
{
- pager->pager = gtk_label_new ("[Pager not supported on Wayland]");
+ pager->is_wayland = TRUE;
+ pager->pager = wayland_workspace_new();
}
else
#endif /* HAVE_WAYLAND */
@@ -922,6 +964,13 @@ static const char *get_workspace_name(PagerData* pager, int workspace_index)
}
#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";
}
@@ -935,6 +984,14 @@ static void update_workspaces_model(PagerData* pager)
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 */
+
+ if (nr_ws == 0)
+ nr_ws = 1;
+
if (pager->properties_dialog)
{
int i;
@@ -1297,5 +1354,10 @@ static void destroy_pager(GtkWidget* widget, PagerData* pager)
g_clear_object(&pager->wnck_handle);
#endif
+#ifdef HAVE_WAYLAND
+ /* The Wayland pager widget cleanup is handled by the
+ * destroy notify on the outer_box's g_object_set_data */
+#endif
+
g_free(pager);
}