diff options
| author | Aleksey Samoilov <[email protected]> | 2026-07-18 10:41:07 +0400 |
|---|---|---|
| committer | Luke from DC <[email protected]> | 2026-07-31 07:57:23 +0000 |
| commit | cbb9c2589291bcee42ded23ddc7ed2005609db5e (patch) | |
| tree | d08e8a283882b38439f92cce06c128d14d4502c2 | |
| parent | fa05c51b6f86d3bc705b9b9e9063255fd21b4dcf (diff) | |
| download | mate-panel-cbb9c2589291bcee42ded23ddc7ed2005609db5e.tar.bz2 mate-panel-cbb9c2589291bcee42ded23ddc7ed2005609db5e.tar.xz | |
[Wayland] Add support for ext-workspace-v1 in pager
| -rw-r--r-- | applets/wncklet/Makefile.am | 21 | ||||
| -rw-r--r-- | applets/wncklet/org.mate.panel.Wncklet.mate-panel-applet.desktop.in.in | 2 | ||||
| -rw-r--r-- | applets/wncklet/wayland-protocol/ext-workspace-v1-client.h | 885 | ||||
| -rw-r--r-- | applets/wncklet/wayland-protocol/ext-workspace-v1-code.c | 120 | ||||
| -rw-r--r-- | applets/wncklet/wayland-protocol/ext-workspace-v1.xml | 422 | ||||
| -rw-r--r-- | applets/wncklet/wayland-workspace.c | 698 | ||||
| -rw-r--r-- | applets/wncklet/wayland-workspace.h | 49 | ||||
| -rw-r--r-- | applets/wncklet/wncklet.c | 2 | ||||
| -rw-r--r-- | applets/wncklet/workspace-switcher.c | 78 |
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); } |
