Merge pull request #2030 from Consolatis/wip/cosmic_workspaces

Initial implementation of cosmic-workspace-unstable-v1
This commit is contained in:
Johan Malm 2024-10-01 21:31:04 +01:00 committed by GitHub
commit d18e67eea8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 1578 additions and 38 deletions

View file

@ -1,6 +1,8 @@
/* SPDX-License-Identifier: GPL-2.0-only */ /* SPDX-License-Identifier: GPL-2.0-only */
#ifndef LABWC_ARRAY_H #ifndef LABWC_ARRAY_H
#define LABWC_ARRAY_H #define LABWC_ARRAY_H
#include <stdio.h>
#include <stdlib.h>
#include <wayland-server-core.h> #include <wayland-server-core.h>
/* /*
@ -41,4 +43,34 @@ wl_array_len(struct wl_array *array)
pos && (const char *)pos >= (const char *)(array)->data; \ pos && (const char *)pos >= (const char *)(array)->data; \
(pos)--) (pos)--)
/**
* array_add() - add item to wl_array and exit on allocation error
* @_arr: wl_array to add the item to
* @_val: the item to add to the array
*
* Let us illustrate the function of this macro by an example:
*
* uint32_t value = 5;
* array_add(array, value);
*
* ...is the equivalent of the code below which is how you would
* otherwise use the wl_array API:
*
* uint32_t *elm = wl_array_add(array, sizeof(uint32_t));
* if (!elm) {
* perror("failed to allocate memory");
* exit(EXIT_FAILURE);
* }
* *elm = value;
*/
#define array_add(_arr, _val) do { \
__typeof__(_val) *_entry = wl_array_add( \
(_arr), sizeof(__typeof__(_val))); \
if (!_entry) { \
perror("Failed to allocate memory"); \
exit(EXIT_FAILURE); \
} \
*_entry = (_val); \
} while (0)
#endif /* LABWC_ARRAY_H */ #endif /* LABWC_ARRAY_H */

View file

@ -299,9 +299,16 @@ struct server {
struct wlr_scene_tree *menu_tree; struct wlr_scene_tree *menu_tree;
/* Workspaces */ /* Workspaces */
struct wl_list workspaces; /* struct workspace.link */ struct {
struct workspace *workspace_current; struct wl_list all; /* struct workspace.link */
struct workspace *workspace_last; struct workspace *current;
struct workspace *last;
struct lab_cosmic_workspace_manager *cosmic_manager;
struct lab_cosmic_workspace_group *cosmic_group;
struct {
struct wl_listener layout_output_added;
} on;
} workspaces;
struct wl_list outputs; struct wl_list outputs;
struct wl_listener new_output; struct wl_listener new_output;

View file

@ -0,0 +1,61 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef LABWC_PROTOCOLS_COSMIC_WORKSPACES_INTERNAL_H
#define LABWC_PROTOCOLS_COSMIC_WORKSPACES_INTERNAL_H
struct lab_cosmic_workspace;
struct lab_cosmic_workspace_group;
struct lab_cosmic_workspace_manager;
enum pending_change {
/* group events */
CW_PENDING_WS_CREATE = 1 << 0,
/* ws events*/
CW_PENDING_WS_ACTIVATE = 1 << 1,
CW_PENDING_WS_DEACTIVATE = 1 << 2,
CW_PENDING_WS_REMOVE = 1 << 3,
};
struct transaction {
uint32_t change;
struct wl_list link;
};
struct transaction_workspace {
struct transaction base;
struct lab_cosmic_workspace *workspace;
};
struct transaction_group {
struct transaction base;
struct lab_cosmic_workspace_group *group;
char *new_workspace_name;
};
struct session_context {
int ref_count;
struct wl_list transactions;
};
struct wl_resource_addon {
struct session_context *ctx;
void *data;
};
struct wl_resource_addon *resource_addon_create(struct session_context *ctx);
void transaction_add_workspace_ev(struct lab_cosmic_workspace *ws,
struct wl_resource *resource, enum pending_change change);
void transaction_add_workspace_group_ev(struct lab_cosmic_workspace_group *group,
struct wl_resource *resource, enum pending_change change,
const char *new_workspace_name);
void resource_addon_destroy(struct wl_resource_addon *addon);
void group_output_send_initial_state(struct lab_cosmic_workspace_group *group,
struct wl_resource *group_resource);
void manager_schedule_done_event(struct lab_cosmic_workspace_manager *manager);
#endif /* LABWC_PROTOCOLS_COSMIC_WORKSPACES_INTERNAL_H */

View file

@ -0,0 +1,94 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef LABWC_PROTOCOLS_COSMIC_WORKSPACES_H
#define LABWC_PROTOCOLS_COSMIC_WORKSPACES_H
#include <stdbool.h>
#include <wayland-server-core.h>
struct wlr_output;
struct lab_cosmic_workspace_manager {
struct wl_global *global;
struct wl_list groups;
uint32_t caps;
struct wl_event_source *idle_source;
struct wl_event_loop *event_loop;
struct {
struct wl_listener display_destroy;
} on;
struct wl_list resources;
};
struct lab_cosmic_workspace_group {
struct lab_cosmic_workspace_manager *manager;
struct wl_list workspaces;
struct wl_array capabilities;
struct {
struct wl_signal create_workspace;
struct wl_signal destroy;
} events;
struct wl_list link;
struct wl_list outputs;
struct wl_list resources;
};
struct lab_cosmic_workspace {
struct lab_cosmic_workspace_group *group;
char *name;
struct wl_array coordinates;
struct wl_array capabilities;
uint32_t state; /* enum lab_cosmic_workspace_state */
uint32_t state_pending; /* enum lab_cosmic_workspace_state */
struct {
struct wl_signal activate;
struct wl_signal deactivate;
struct wl_signal remove;
struct wl_signal destroy;
} events;
struct wl_list link;
struct wl_list resources;
};
enum lab_cosmic_workspace_caps {
CW_CAP_NONE = 0,
CW_CAP_GRP_ALL = 0x000000ff,
CW_CAP_WS_ALL = 0x0000ff00,
/* group caps */
CW_CAP_GRP_WS_CREATE = 1 << 0,
/* workspace caps */
CW_CAP_WS_ACTIVATE = 1 << 8,
CW_CAP_WS_DEACTIVATE = 1 << 9,
CW_CAP_WS_REMOVE = 1 << 10,
};
struct lab_cosmic_workspace_manager *lab_cosmic_workspace_manager_create(
struct wl_display *display, uint32_t caps, uint32_t version);
struct lab_cosmic_workspace_group *lab_cosmic_workspace_group_create(
struct lab_cosmic_workspace_manager *manager);
void lab_cosmic_workspace_group_output_enter(
struct lab_cosmic_workspace_group *group, struct wlr_output *output);
void lab_cosmic_workspace_group_output_leave(
struct lab_cosmic_workspace_group *group, struct wlr_output *output);
void lab_cosmic_workspace_group_destroy(struct lab_cosmic_workspace_group *group);
struct lab_cosmic_workspace *lab_cosmic_workspace_create(struct lab_cosmic_workspace_group *group);
void lab_cosmic_workspace_set_name(struct lab_cosmic_workspace *workspace, const char *name);
void lab_cosmic_workspace_set_active(struct lab_cosmic_workspace *workspace, bool enabled);
void lab_cosmic_workspace_set_urgent(struct lab_cosmic_workspace *workspace, bool enabled);
void lab_cosmic_workspace_set_hidden(struct lab_cosmic_workspace *workspace, bool enabled);
void lab_cosmic_workspace_set_coordinates(struct lab_cosmic_workspace *workspace,
struct wl_array *coordinates);
void lab_cosmic_workspace_destroy(struct lab_cosmic_workspace *workspace);
#endif /* LABWC_PROTOCOLS_COSMIC_WORKSPACES_H */

View file

@ -4,6 +4,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <wayland-util.h> #include <wayland-util.h>
#include <wayland-server-core.h>
struct seat; struct seat;
struct server; struct server;
@ -19,6 +20,13 @@ struct workspace {
char *name; char *name;
struct wlr_scene_tree *tree; struct wlr_scene_tree *tree;
struct lab_cosmic_workspace *cosmic_workspace;
struct {
struct wl_listener activate;
struct wl_listener deactivate;
struct wl_listener remove;
} on;
}; };
void workspaces_init(struct server *server); void workspaces_init(struct server *server);

View file

@ -0,0 +1,364 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="cosmic_workspace_unstable_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="zcosmic_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 zcosmic_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 (workspaces, outputs) will be
sent immediately after this event via the corresponding events in
zcosmic_workspace_group_handle_v1.
</description>
<arg name="workspace_group" type="new_id" interface="zcosmic_workspace_group_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 zcosmic_workspace_handle_v1 objects, for example, deactivating one
workspace and activating another.
</description>
</request>
<event name="done">
<description summary="all information about the workspace groups has been sent">
This event is sent after all changes in all workspace groups have been
sent.
This allows changes to one or more zcosmic_workspace_group_handle_v1
properties and zcosmic_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
zcosmic_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">
<description summary="the compositor has finished with the workspace_manager">
This event indicates that the compositor is done sending events to the
zcosmic_workspace_manager_v1. The server will destroy the object
immediately after sending this request, so it will become invalid and
the client should free any resources associated with it.
</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 client must not send any more requests after this one.
</description>
</request>
</interface>
<interface name="zcosmic_workspace_group_handle_v1" version="1">
<description summary="a workspace group assigned to a set of outputs">
A zcosmic_workspace_group_handle_v1 object represents a 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="zcosmic_workspace_group_capabilities_v1">
<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
zcosmic_workspace_group_handle_v1 . When the capabilities change, compositors
must send this event again.
The capabilities are sent as an array of 32-bit unsigned integers in
native endianness.
</description>
<arg name="capabilities" type="array" summary="array of 32-bit 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.
</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">
<description summary="workspace added to workspace group">
This event is emitted whenever a new workspace has been created.
A workspace can only be a member of a single workspace group and cannot
be re-assigned.
All initial details of the workspace (name, coordinates, state) will
be sent immediately after this event via the corresponding events in
zcosmic_workspace_handle_v1.
</description>
<arg name="workspace" type="new_id" interface="zcosmic_workspace_handle_v1"/>
</event>
<event name="remove">
<description summary="this workspace group has been destroyed">
This event means the zcosmic_workspace_group_handle_v1 has been destroyed.
It is guaranteed there won't be any more events for this
zcosmic_workspace_group_handle_v1. The zext_workspace_group_handle_v1 becomes
inert so any requests will be ignored except the destroy request.
The compositor must remove all workspaces belonging to a workspace group
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.
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 zcosmic_workspace_group_handle_v1 object">
Destroys the zcosmic_workspace_group_handle_v1 object.
This request should be called 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>
</interface>
<interface name="zcosmic_workspace_handle_v1" version="1">
<description summary="a workspace handing a group of surfaces">
A zcosmic_workspace_handle_v1 object represents a a workspace that handles a
group of surfaces.
Each workspace has a name, conveyed to the client with the name 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.
Depepending 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="name">
<description summary="workspace name changed">
This event is emitted immediately after the zcosmic_workspace_handle_v1 is
created and whenever the name of the workspace changes.
</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 zcosmic_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>
<event name="state">
<description summary="the state of the workspace changed">
This event is emitted immediately after the zcosmic_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.
</description>
<arg name="state" type="array"/>
</event>
<enum name="state">
<description summary="types of states on the workspace">
The different states that a workspace can have.
</description>
<entry name="active" value="0" summary="the workspace is active"/>
<entry name="urgent" value="1" summary="the workspace requests attention"/>
<entry name="hidden" value="2">
<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>
<enum name="zcosmic_workspace_capabilities_v1">
<entry name="activate" value="1" summary="activate request is available"/>
<entry name="deactivate" value="2" summary="deactivate request is available"/>
<entry name="remove" value="3" summary="remove 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
zcosmic_workspace_handle_v1 . When the capabilities change, compositors
must send this event again.
The capabilities are sent as an array of 32-bit unsigned integers in
native endianness.
</description>
<arg name="capabilities" type="array" summary="array of 32-bit capabilities"/>
</event>
<event name="remove">
<description summary="this workspace has been destroyed">
This event means the zcosmic_workspace_handle_v1 has been destroyed. It is
guaranteed there won't be any more events for this
zcosmic_workspace_handle_v1. The zext_workspace_handle_v1 becomes inert so
any requests will be ignored except the destroy request.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="destroy the zcosmic_workspace_handle_v1 object">
Destroys the zcosmic_workspace_handle_v1 object.
This request should be called 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="activate the workspace">
Request that this workspace be deactivated.
There is no guarantee the workspace will be actually deactivated.
</description>
</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>

View file

@ -21,6 +21,7 @@ server_protocols = [
wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml',
wl_protocol_dir / 'staging/xwayland-shell/xwayland-shell-v1.xml', wl_protocol_dir / 'staging/xwayland-shell/xwayland-shell-v1.xml',
wl_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml', wl_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml',
'cosmic-workspace-unstable-v1.xml',
'wlr-layer-shell-unstable-v1.xml', 'wlr-layer-shell-unstable-v1.xml',
'wlr-input-inhibitor-unstable-v1.xml', 'wlr-input-inhibitor-unstable-v1.xml',
'wlr-output-power-management-unstable-v1.xml', 'wlr-output-power-management-unstable-v1.xml',

View file

@ -1110,7 +1110,7 @@ actions_run(struct view *activator, struct server *server,
* a required argument for both SendToDesktop and GoToDesktop. * a required argument for both SendToDesktop and GoToDesktop.
*/ */
struct workspace *target = workspaces_find( struct workspace *target = workspaces_find(
server->workspace_current, to, wrap); server->workspaces.current, to, wrap);
if (!target) { if (!target) {
break; break;
} }

View file

@ -106,7 +106,7 @@ get_special(struct server *server, struct wlr_scene_node *node)
} }
if (node->parent == server->view_tree) { if (node->parent == server->view_tree) {
struct workspace *workspace; struct workspace *workspace;
wl_list_for_each(workspace, &server->workspaces, link) { wl_list_for_each(workspace, &server->workspaces.all, link) {
if (&workspace->tree->node == node) { if (&workspace->tree->node == node) {
return workspace->name; return workspace->name;
} }

View file

@ -145,7 +145,7 @@ desktop_topmost_focusable_view(struct server *server)
struct view *view; struct view *view;
struct wl_list *node_list; struct wl_list *node_list;
struct wlr_scene_node *node; struct wlr_scene_node *node;
node_list = &server->workspace_current->tree->children; node_list = &server->workspaces.current->tree->children;
wl_list_for_each_reverse(node, node_list, link) { wl_list_for_each_reverse(node, node_list, link) {
if (!node->data) { if (!node->data) {
/* We found some non-view, most likely the region overlay */ /* We found some non-view, most likely the region overlay */
@ -185,7 +185,7 @@ desktop_focus_output(struct output *output)
struct wlr_scene_node *node; struct wlr_scene_node *node;
struct wlr_output_layout *layout = output->server->output_layout; struct wlr_output_layout *layout = output->server->output_layout;
struct wl_list *list_head = struct wl_list *list_head =
&output->server->workspace_current->tree->children; &output->server->workspaces.current->tree->children;
wl_list_for_each_reverse(node, list_head, link) { wl_list_for_each_reverse(node, list_head, link) {
if (!node->data) { if (!node->data) {
continue; continue;

View file

@ -57,3 +57,4 @@ subdir('decorations')
subdir('input') subdir('input')
subdir('menu') subdir('menu')
subdir('ssd') subdir('ssd')
subdir('protocols')

View file

@ -331,7 +331,7 @@ display_osd(struct output *output, struct wl_array *views)
struct server *server = output->server; struct server *server = output->server;
struct theme *theme = server->theme; struct theme *theme = server->theme;
bool show_workspace = wl_list_length(&rc.workspace_config.workspaces) > 1; bool show_workspace = wl_list_length(&rc.workspace_config.workspaces) > 1;
const char *workspace_name = server->workspace_current->name; const char *workspace_name = server->workspaces.current->name;
float scale = output->wlr_output->scale; float scale = output->wlr_output->scale;
int w = theme->osd_window_switcher_width; int w = theme->osd_window_switcher_width;

View file

@ -27,6 +27,7 @@
#include "node.h" #include "node.h"
#include "output-state.h" #include "output-state.h"
#include "output-virtual.h" #include "output-virtual.h"
#include "protocols/cosmic-workspaces.h"
#include "regions.h" #include "regions.h"
#include "view.h" #include "view.h"
#include "xwayland.h" #include "xwayland.h"
@ -299,6 +300,9 @@ add_output_to_layout(struct server *server, struct output *output)
wlr_scene_output_layout_add_output(server->scene_layout, wlr_scene_output_layout_add_output(server->scene_layout,
layout_output, output->scene_output); layout_output, output->scene_output);
} }
lab_cosmic_workspace_group_output_enter(
server->workspaces.cosmic_group, output->wlr_output);
} }
static void static void
@ -597,6 +601,10 @@ output_config_apply(struct server *server,
if (need_to_remove) { if (need_to_remove) {
regions_evacuate_output(output); regions_evacuate_output(output);
lab_cosmic_workspace_group_output_leave(
server->workspaces.cosmic_group, output->wlr_output);
/* /*
* At time of writing, wlr_output_layout_remove() * At time of writing, wlr_output_layout_remove()
* indirectly destroys the wlr_scene_output, but * indirectly destroys the wlr_scene_output, but

View file

@ -0,0 +1,651 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <assert.h>
#include <wlr/types/wlr_output.h>
#include <wlr/util/log.h>
#include "common/array.h"
#include "common/mem.h"
#include "common/list.h"
#include "cosmic-workspace-unstable-v1-protocol.h"
#include "protocols/cosmic-workspaces.h"
#include "protocols/cosmic-workspaces-internal.h"
/*
* .--------------------.
* | TODO |
* |--------------------|
* | - prevent empty |
* | done events |
* | - go through xml |
* | and verify impl |
* | - assert pub API |
* `--------------------´
*
*/
/* Only used within an assert() */
#ifndef NDEBUG
#define COSMIC_WORKSPACE_V1_VERSION 1
#endif
/* These are just *waaay* too long */
#define ZCOSMIC_CAP_WS_CREATE \
ZCOSMIC_WORKSPACE_GROUP_HANDLE_V1_ZCOSMIC_WORKSPACE_GROUP_CAPABILITIES_V1_CREATE_WORKSPACE
#define ZCOSMIC_CAP_WS_ACTIVATE \
ZCOSMIC_WORKSPACE_HANDLE_V1_ZCOSMIC_WORKSPACE_CAPABILITIES_V1_ACTIVATE
#define ZCOSMIC_CAP_WS_DEACTIVATE \
ZCOSMIC_WORKSPACE_HANDLE_V1_ZCOSMIC_WORKSPACE_CAPABILITIES_V1_DEACTIVATE
#define ZCOSMIC_CAP_WS_REMOVE \
ZCOSMIC_WORKSPACE_HANDLE_V1_ZCOSMIC_WORKSPACE_CAPABILITIES_V1_REMOVE
enum workspace_state {
CW_WS_STATE_ACTIVE = 1 << 0,
CW_WS_STATE_URGENT = 1 << 1,
CW_WS_STATE_HIDDEN = 1 << 2,
/*
* Set when creating a new workspace so we
* don't end up having to send the state twice.
*/
CW_WS_STATE_INVALID = 1 << 31,
};
static void
add_caps(struct wl_array *caps_arr, uint32_t caps)
{
if (caps == CW_CAP_NONE) {
return;
}
if (caps & CW_CAP_GRP_WS_CREATE) {
array_add(caps_arr, ZCOSMIC_CAP_WS_CREATE);
}
if (caps & CW_CAP_WS_ACTIVATE) {
array_add(caps_arr, ZCOSMIC_CAP_WS_ACTIVATE);
}
if (caps & CW_CAP_WS_DEACTIVATE) {
array_add(caps_arr, ZCOSMIC_CAP_WS_DEACTIVATE);
}
if (caps & CW_CAP_WS_REMOVE) {
array_add(caps_arr, ZCOSMIC_CAP_WS_REMOVE);
}
}
/* Workspace */
static void
workspace_handle_destroy(struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy(resource);
}
static void
workspace_handle_activate(struct wl_client *client, struct wl_resource *resource)
{
struct wl_resource_addon *addon = wl_resource_get_user_data(resource);
if (!addon) {
/* workspace was destroyed from the compositor side */
return;
}
struct lab_cosmic_workspace *workspace = addon->data;
transaction_add_workspace_ev(workspace, resource, CW_PENDING_WS_ACTIVATE);
}
static void
workspace_handle_deactivate(struct wl_client *client, struct wl_resource *resource)
{
struct wl_resource_addon *addon = wl_resource_get_user_data(resource);
if (!addon) {
/* Workspace was destroyed from the compositor side */
return;
}
struct lab_cosmic_workspace *workspace = addon->data;
transaction_add_workspace_ev(workspace, resource, CW_PENDING_WS_DEACTIVATE);
}
static void
workspace_handle_remove(struct wl_client *client, struct wl_resource *resource)
{
struct wl_resource_addon *addon = wl_resource_get_user_data(resource);
if (!addon) {
/* workspace was destroyed from the compositor side */
return;
}
struct lab_cosmic_workspace *workspace = addon->data;
transaction_add_workspace_ev(workspace, resource, CW_PENDING_WS_REMOVE);
}
static const struct zcosmic_workspace_handle_v1_interface workspace_impl = {
.destroy = workspace_handle_destroy,
.activate = workspace_handle_activate,
.deactivate = workspace_handle_deactivate,
.remove = workspace_handle_remove,
};
static void
workspace_instance_resource_destroy(struct wl_resource *resource)
{
struct wl_resource_addon *addon = wl_resource_get_user_data(resource);
if (addon) {
resource_addon_destroy(addon);
wl_resource_set_user_data(resource, NULL);
}
wl_list_remove(wl_resource_get_link(resource));
}
static struct wl_resource *
workspace_resource_create(struct lab_cosmic_workspace *workspace,
struct wl_resource *group_resource, struct session_context *ctx)
{
struct wl_client *client = wl_resource_get_client(group_resource);
struct wl_resource *resource = wl_resource_create(client,
&zcosmic_workspace_handle_v1_interface,
wl_resource_get_version(group_resource), 0);
if (!resource) {
wl_client_post_no_memory(client);
return NULL;
}
struct wl_resource_addon *addon = resource_addon_create(ctx);
addon->data = workspace;
wl_resource_set_implementation(resource, &workspace_impl, addon,
workspace_instance_resource_destroy);
wl_list_insert(&workspace->resources, wl_resource_get_link(resource));
return resource;
}
/* Workspace internal helpers */
static void
workspace_send_state(struct lab_cosmic_workspace *workspace, struct wl_resource *target)
{
struct wl_array state;
wl_array_init(&state);
if (workspace->state & CW_WS_STATE_ACTIVE) {
array_add(&state, ZCOSMIC_WORKSPACE_HANDLE_V1_STATE_ACTIVE);
}
if (workspace->state & CW_WS_STATE_URGENT) {
array_add(&state, ZCOSMIC_WORKSPACE_HANDLE_V1_STATE_URGENT);
}
if (workspace->state & CW_WS_STATE_HIDDEN) {
array_add(&state, ZCOSMIC_WORKSPACE_HANDLE_V1_STATE_HIDDEN);
}
if (target) {
zcosmic_workspace_handle_v1_send_state(target, &state);
} else {
struct wl_resource *resource;
wl_resource_for_each(resource, &workspace->resources) {
zcosmic_workspace_handle_v1_send_state(resource, &state);
}
}
wl_array_release(&state);
}
static void
workspace_send_initial_state(struct lab_cosmic_workspace *workspace, struct wl_resource *resource)
{
zcosmic_workspace_handle_v1_send_capabilities(resource, &workspace->capabilities);
if (workspace->coordinates.size > 0) {
zcosmic_workspace_handle_v1_send_coordinates(resource, &workspace->coordinates);
}
if (workspace->name) {
zcosmic_workspace_handle_v1_send_name(resource, workspace->name);
}
}
static void
workspace_set_state(struct lab_cosmic_workspace *workspace,
enum workspace_state state, bool enabled)
{
if (!!(workspace->state_pending & state) == enabled) {
return;
}
if (enabled) {
workspace->state_pending |= state;
} else {
workspace->state_pending &= ~state;
}
manager_schedule_done_event(workspace->group->manager);
}
/* Group */
static void
group_handle_create_workspace(struct wl_client *client,
struct wl_resource *resource, const char *name)
{
struct wl_resource_addon *addon = wl_resource_get_user_data(resource);
if (!addon) {
return;
}
struct lab_cosmic_workspace_group *group = addon->data;
transaction_add_workspace_group_ev(group, resource, CW_PENDING_WS_CREATE, name);
}
static void
group_handle_destroy(struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy(resource);
}
static const struct zcosmic_workspace_group_handle_v1_interface group_impl = {
.create_workspace = group_handle_create_workspace,
.destroy = group_handle_destroy,
};
static void
group_instance_resource_destroy(struct wl_resource *resource)
{
struct wl_resource_addon *addon = wl_resource_get_user_data(resource);
if (addon) {
resource_addon_destroy(addon);
wl_resource_set_user_data(resource, NULL);
}
wl_list_remove(wl_resource_get_link(resource));
}
static struct wl_resource *
group_resource_create(struct lab_cosmic_workspace_group *group,
struct wl_resource *manager_resource, struct session_context *ctx)
{
struct wl_client *client = wl_resource_get_client(manager_resource);
struct wl_resource *resource = wl_resource_create(client,
&zcosmic_workspace_group_handle_v1_interface,
wl_resource_get_version(manager_resource), 0);
if (!resource) {
wl_client_post_no_memory(client);
return NULL;
}
struct wl_resource_addon *addon = resource_addon_create(ctx);
addon->data = group;
wl_resource_set_implementation(resource, &group_impl, addon,
group_instance_resource_destroy);
wl_list_insert(&group->resources, wl_resource_get_link(resource));
return resource;
}
/* Group internal helpers */
static void
group_send_state(struct lab_cosmic_workspace_group *group, struct wl_resource *resource)
{
zcosmic_workspace_group_handle_v1_send_capabilities(
resource, &group->capabilities);
group_output_send_initial_state(group, resource);
}
/* Manager itself */
static void
manager_handle_commit(struct wl_client *client, struct wl_resource *resource)
{
struct wl_resource_addon *addon = wl_resource_get_user_data(resource);
if (!addon) {
return;
}
struct transaction_group *trans_grp;
struct transaction_workspace *trans_ws;
struct transaction *trans, *trans_tmp;
wl_list_for_each_safe(trans, trans_tmp, &addon->ctx->transactions, link) {
switch (trans->change) {
case CW_PENDING_WS_CREATE:
trans_grp = wl_container_of(trans, trans_grp, base);
wl_signal_emit_mutable(
&trans_grp->group->events.create_workspace,
trans_grp->new_workspace_name);
free(trans_grp->new_workspace_name);
break;
case CW_PENDING_WS_ACTIVATE:
trans_ws = wl_container_of(trans, trans_ws, base);
wl_signal_emit_mutable(&trans_ws->workspace->events.activate, NULL);
break;
case CW_PENDING_WS_DEACTIVATE:
trans_ws = wl_container_of(trans, trans_ws, base);
wl_signal_emit_mutable(&trans_ws->workspace->events.deactivate, NULL);
break;
case CW_PENDING_WS_REMOVE:
trans_ws = wl_container_of(trans, trans_ws, base);
wl_signal_emit_mutable(&trans_ws->workspace->events.remove, NULL);
break;
default:
wlr_log(WLR_ERROR, "Invalid transaction state: %u", trans->change);
}
wl_list_remove(&trans->link);
free(trans);
}
}
static void
manager_handle_stop(struct wl_client *client, struct wl_resource *resource)
{
zcosmic_workspace_manager_v1_send_finished(resource);
wl_resource_destroy(resource);
}
static const struct zcosmic_workspace_manager_v1_interface manager_impl = {
.commit = manager_handle_commit,
.stop = manager_handle_stop,
};
static void
manager_instance_resource_destroy(struct wl_resource *resource)
{
struct wl_resource_addon *addon = wl_resource_get_user_data(resource);
if (addon) {
resource_addon_destroy(addon);
wl_resource_set_user_data(resource, NULL);
}
wl_list_remove(wl_resource_get_link(resource));
}
static void
manager_handle_bind(struct wl_client *client, void *data,
uint32_t version, uint32_t id)
{
struct lab_cosmic_workspace_manager *manager = data;
struct wl_resource *resource = wl_resource_create(client,
&zcosmic_workspace_manager_v1_interface,
version, id);
if (!resource) {
wl_client_post_no_memory(client);
return;
}
struct wl_resource_addon *addon = resource_addon_create(/* session context*/ NULL);
addon->data = manager;
wl_resource_set_implementation(resource, &manager_impl,
addon, manager_instance_resource_destroy);
wl_list_insert(&manager->resources, wl_resource_get_link(resource));
struct lab_cosmic_workspace *workspace;
struct lab_cosmic_workspace_group *group;
wl_list_for_each(group, &manager->groups, link) {
/* Create group resource */
struct wl_resource *group_resource =
group_resource_create(group, resource, addon->ctx);
zcosmic_workspace_manager_v1_send_workspace_group(resource, group_resource);
group_send_state(group, group_resource);
/* Create workspace resource */
wl_list_for_each(workspace, &group->workspaces, link) {
struct wl_resource *workspace_resource =
workspace_resource_create(workspace, group_resource, addon->ctx);
zcosmic_workspace_group_handle_v1_send_workspace(
group_resource, workspace_resource);
workspace_send_initial_state(workspace, workspace_resource);
/* Send the current workspace state manually */
workspace_send_state(workspace, workspace_resource);
}
}
zcosmic_workspace_manager_v1_send_done(resource);
}
static void
manager_handle_display_destroy(struct wl_listener *listener, void *data)
{
struct lab_cosmic_workspace_manager *manager =
wl_container_of(listener, manager, on.display_destroy);
wl_list_remove(&manager->on.display_destroy.link);
manager->event_loop = NULL;
}
/* Manager internal helpers */
static void
manager_idle_send_done(void *data)
{
struct lab_cosmic_workspace_manager *manager = data;
struct lab_cosmic_workspace *workspace;
struct lab_cosmic_workspace_group *group;
wl_list_for_each(group, &manager->groups, link) {
wl_list_for_each(workspace, &group->workspaces, link) {
if (workspace->state != workspace->state_pending) {
workspace->state = workspace->state_pending;
workspace_send_state(workspace, /*target*/ NULL);
}
}
}
struct wl_resource *resource;
wl_resource_for_each(resource, &manager->resources) {
zcosmic_workspace_manager_v1_send_done(resource);
}
manager->idle_source = NULL;
}
/* Internal API */
void
manager_schedule_done_event(struct lab_cosmic_workspace_manager *manager)
{
if (manager->idle_source) {
return;
}
if (!manager->event_loop) {
return;
}
manager->idle_source = wl_event_loop_add_idle(
manager->event_loop, manager_idle_send_done, manager);
}
/* Public API */
struct lab_cosmic_workspace_manager *
lab_cosmic_workspace_manager_create(struct wl_display *display, uint32_t caps, uint32_t version)
{
assert(version <= COSMIC_WORKSPACE_V1_VERSION);
struct lab_cosmic_workspace_manager *manager = znew(*manager);
manager->global = wl_global_create(display,
&zcosmic_workspace_manager_v1_interface,
version, manager, manager_handle_bind);
if (!manager->global) {
free(manager);
return NULL;
}
manager->caps = caps;
manager->event_loop = wl_display_get_event_loop(display);
manager->on.display_destroy.notify = manager_handle_display_destroy;
wl_display_add_destroy_listener(display, &manager->on.display_destroy);
wl_list_init(&manager->groups);
wl_list_init(&manager->resources);
return manager;
}
struct lab_cosmic_workspace_group *
lab_cosmic_workspace_group_create(struct lab_cosmic_workspace_manager *manager)
{
assert(manager);
struct lab_cosmic_workspace_group *group = znew(*group);
group->manager = manager;
wl_array_init(&group->capabilities);
add_caps(&group->capabilities, manager->caps & CW_CAP_GRP_ALL);
wl_list_init(&group->outputs);
wl_list_init(&group->resources);
wl_list_init(&group->workspaces);
wl_signal_init(&group->events.create_workspace);
wl_signal_init(&group->events.destroy);
wl_list_append(&manager->groups, &group->link);
struct wl_resource *resource, *tmp;
wl_resource_for_each_safe(resource, tmp, &manager->resources) {
struct wl_resource_addon *addon = wl_resource_get_user_data(resource);
assert(addon && addon->ctx);
struct wl_resource *group_resource =
group_resource_create(group, resource, addon->ctx);
zcosmic_workspace_manager_v1_send_workspace_group(resource, group_resource);
group_send_state(group, group_resource);
}
manager_schedule_done_event(manager);
return group;
}
void
lab_cosmic_workspace_group_destroy(struct lab_cosmic_workspace_group *group)
{
if (!group) {
return;
}
wl_signal_emit_mutable(&group->events.destroy, NULL);
struct lab_cosmic_workspace *ws, *ws_tmp;
wl_list_for_each_safe(ws, ws_tmp, &group->workspaces, link) {
lab_cosmic_workspace_destroy(ws);
}
struct wl_resource *resource, *res_tmp;
wl_resource_for_each_safe(resource, res_tmp, &group->resources) {
struct wl_resource_addon *addon = wl_resource_get_user_data(resource);
if (addon) {
resource_addon_destroy(addon);
wl_resource_set_user_data(resource, NULL);
}
zcosmic_workspace_group_handle_v1_send_remove(resource);
wl_list_remove(wl_resource_get_link(resource));
wl_list_init(wl_resource_get_link(resource));
}
wl_list_remove(&group->link);
wl_array_release(&group->capabilities);
free(group);
}
struct lab_cosmic_workspace *
lab_cosmic_workspace_create(struct lab_cosmic_workspace_group *group)
{
assert(group);
struct lab_cosmic_workspace *workspace = znew(*workspace);
workspace->group = group;
/*
* Ensures we are sending workspace->state_pending on the done event,
* regardless if the compositor has changed any state in between here
* and the scheduled done event or not.
*
* Without this we might have to send the state twice, first here and
* then again in the scheduled done event when there were any changes.
*/
workspace->state = CW_WS_STATE_INVALID;
wl_array_init(&workspace->capabilities);
add_caps(&workspace->capabilities, group->manager->caps & CW_CAP_WS_ALL);
wl_list_init(&workspace->resources);
wl_array_init(&workspace->coordinates);
wl_signal_init(&workspace->events.activate);
wl_signal_init(&workspace->events.deactivate);
wl_signal_init(&workspace->events.remove);
wl_signal_init(&workspace->events.destroy);
wl_list_append(&group->workspaces, &workspace->link);
/* Notify clients */
struct wl_resource *group_resource;
wl_resource_for_each(group_resource, &group->resources) {
struct wl_resource_addon *addon = wl_resource_get_user_data(group_resource);
assert(addon && addon->ctx);
struct wl_resource *workspace_resource =
workspace_resource_create(workspace, group_resource, addon->ctx);
zcosmic_workspace_group_handle_v1_send_workspace(
group_resource, workspace_resource);
workspace_send_initial_state(workspace, workspace_resource);
}
manager_schedule_done_event(group->manager);
return workspace;
}
void
lab_cosmic_workspace_set_name(struct lab_cosmic_workspace *workspace, const char *name)
{
assert(workspace);
assert(name);
if (!workspace->name || strcmp(workspace->name, name)) {
free(workspace->name);
workspace->name = xstrdup(name);
struct wl_resource *resource;
wl_resource_for_each(resource, &workspace->resources) {
zcosmic_workspace_handle_v1_send_name(resource, workspace->name);
}
}
manager_schedule_done_event(workspace->group->manager);
}
void
lab_cosmic_workspace_set_active(struct lab_cosmic_workspace *workspace, bool enabled)
{
workspace_set_state(workspace, CW_WS_STATE_ACTIVE, enabled);
}
void
lab_cosmic_workspace_set_urgent(struct lab_cosmic_workspace *workspace, bool enabled)
{
workspace_set_state(workspace, CW_WS_STATE_URGENT, enabled);
}
void
lab_cosmic_workspace_set_hidden(struct lab_cosmic_workspace *workspace, bool enabled)
{
workspace_set_state(workspace, CW_WS_STATE_HIDDEN, enabled);
}
void
lab_cosmic_workspace_set_coordinates(struct lab_cosmic_workspace *workspace,
struct wl_array *coordinates)
{
wl_array_release(&workspace->coordinates);
wl_array_init(&workspace->coordinates);
wl_array_copy(&workspace->coordinates, coordinates);
struct wl_resource *resource;
wl_resource_for_each(resource, &workspace->resources) {
zcosmic_workspace_handle_v1_send_coordinates(resource, &workspace->coordinates);
}
manager_schedule_done_event(workspace->group->manager);
}
void
lab_cosmic_workspace_destroy(struct lab_cosmic_workspace *workspace)
{
if (!workspace) {
return;
}
wl_signal_emit_mutable(&workspace->events.destroy, NULL);
struct wl_resource *resource, *tmp;
wl_resource_for_each_safe(resource, tmp, &workspace->resources) {
struct wl_resource_addon *addon = wl_resource_get_user_data(resource);
if (addon) {
resource_addon_destroy(addon);
wl_resource_set_user_data(resource, NULL);
}
zcosmic_workspace_handle_v1_send_remove(resource);
wl_list_remove(wl_resource_get_link(resource));
wl_list_init(wl_resource_get_link(resource));
}
manager_schedule_done_event(workspace->group->manager);
wl_list_remove(&workspace->link);
wl_array_release(&workspace->coordinates);
wl_array_release(&workspace->capabilities);
zfree(workspace->name);
free(workspace);
}

View file

@ -0,0 +1,5 @@
labwc_sources += files(
'cosmic-workspaces.c',
'transactions.c',
'output.c',
)

View file

@ -0,0 +1,174 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <wayland-server-core.h>
#include <wlr/types/wlr_output.h>
#include <wlr/util/log.h>
#include "common/mem.h"
#include "cosmic-workspace-unstable-v1-protocol.h"
#include "protocols/cosmic-workspaces.h"
#include "protocols/cosmic-workspaces-internal.h"
struct group_output {
struct wlr_output *wlr_output;
struct lab_cosmic_workspace_group *group;
struct {
struct wl_listener group_destroy;
struct wl_listener output_bind;
struct wl_listener output_destroy;
} on;
struct wl_list link;
};
/* Internal helpers */
static void
group_output_send_event(struct wl_list *group_resources, struct wl_list *output_resources,
void (*notifier)(struct wl_resource *group, struct wl_resource *output))
{
struct wl_client *client;
struct wl_resource *group_resource, *output_resource;
wl_resource_for_each(group_resource, group_resources) {
client = wl_resource_get_client(group_resource);
wl_resource_for_each(output_resource, output_resources) {
if (wl_resource_get_client(output_resource) == client) {
notifier(group_resource, output_resource);
}
}
}
}
static void
group_output_destroy(struct group_output *group_output)
{
group_output_send_event(
&group_output->group->resources,
&group_output->wlr_output->resources,
zcosmic_workspace_group_handle_v1_send_output_leave);
manager_schedule_done_event(group_output->group->manager);
wl_list_remove(&group_output->link);
wl_list_remove(&group_output->on.group_destroy.link);
wl_list_remove(&group_output->on.output_bind.link);
wl_list_remove(&group_output->on.output_destroy.link);
free(group_output);
}
/* Event handlers */
static void
handle_output_bind(struct wl_listener *listener, void *data)
{
struct group_output *group_output =
wl_container_of(listener, group_output, on.output_bind);
struct wlr_output_event_bind *event = data;
struct wl_client *client = wl_resource_get_client(event->resource);
bool sent = false;
struct wl_resource *group_resource;
wl_resource_for_each(group_resource, &group_output->group->resources) {
if (wl_resource_get_client(group_resource) == client) {
zcosmic_workspace_group_handle_v1_send_output_enter(
group_resource, event->resource);
sent = true;
}
}
if (!sent) {
return;
}
struct wl_resource *manager_resource;
struct wl_list *manager_resources = &group_output->group->manager->resources;
wl_resource_for_each(manager_resource, manager_resources) {
if (wl_resource_get_client(manager_resource) == client) {
zcosmic_workspace_manager_v1_send_done(manager_resource);
}
}
}
static void
handle_output_destroy(struct wl_listener *listener, void *data)
{
struct group_output *group_output =
wl_container_of(listener, group_output, on.output_destroy);
group_output_destroy(group_output);
}
static void
handle_group_destroy(struct wl_listener *listener, void *data)
{
struct group_output *group_output =
wl_container_of(listener, group_output, on.group_destroy);
group_output_destroy(group_output);
}
/* Internal API*/
void
group_output_send_initial_state(struct lab_cosmic_workspace_group *group,
struct wl_resource *group_resource)
{
struct group_output *group_output;
struct wl_resource *output_resource;
struct wl_client *client = wl_resource_get_client(group_resource);
wl_list_for_each(group_output, &group->outputs, link) {
wl_resource_for_each(output_resource, &group_output->wlr_output->resources) {
if (wl_resource_get_client(output_resource) == client) {
zcosmic_workspace_group_handle_v1_send_output_enter(
group_resource, output_resource);
}
}
}
}
/* Public API */
void
lab_cosmic_workspace_group_output_enter(struct lab_cosmic_workspace_group *group,
struct wlr_output *wlr_output)
{
struct group_output *group_output;
wl_list_for_each(group_output, &group->outputs, link) {
if (group_output->wlr_output == wlr_output) {
return;
}
}
group_output = znew(*group_output);
group_output->wlr_output = wlr_output;
group_output->group = group;
group_output->on.group_destroy.notify = handle_group_destroy;
wl_signal_add(&group->events.destroy, &group_output->on.group_destroy);
group_output->on.output_bind.notify = handle_output_bind;
wl_signal_add(&wlr_output->events.bind, &group_output->on.output_bind);
group_output->on.output_destroy.notify = handle_output_destroy;
wl_signal_add(&wlr_output->events.destroy, &group_output->on.output_destroy);
wl_list_insert(&group->outputs, &group_output->link);
group_output_send_event(
&group_output->group->resources,
&group_output->wlr_output->resources,
zcosmic_workspace_group_handle_v1_send_output_enter);
manager_schedule_done_event(group->manager);
}
void
lab_cosmic_workspace_group_output_leave(struct lab_cosmic_workspace_group *group,
struct wlr_output *wlr_output)
{
struct group_output *tmp;
struct group_output *group_output = NULL;
wl_list_for_each(tmp, &group->outputs, link) {
if (tmp->wlr_output == wlr_output) {
group_output = tmp;
break;
}
}
if (!group_output) {
wlr_log(WLR_ERROR, "output %s was never entered", wlr_output->name);
return;
}
group_output_destroy(group_output);
}

View file

@ -0,0 +1,93 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <assert.h>
#include <wayland-server-core.h>
#include <wlr/util/log.h>
#include "common/list.h"
#include "common/mem.h"
#include "protocols/cosmic-workspaces-internal.h"
static void
transactions_destroy(struct wl_list *list)
{
struct transaction_group *group;
struct transaction *trans, *trans_tmp;
wl_list_for_each_safe(trans, trans_tmp, list, link) {
if (trans->change == CW_PENDING_WS_CREATE) {
group = wl_container_of(trans, group, base);
free(group->new_workspace_name);
}
wl_list_remove(&trans->link);
free(trans);
}
}
void
resource_addon_destroy(struct wl_resource_addon *addon)
{
assert(addon);
assert(addon->ctx);
addon->ctx->ref_count--;
assert(addon->ctx->ref_count >= 0);
wlr_log(WLR_DEBUG, "New refcount for session %p: %d",
addon->ctx, addon->ctx->ref_count);
if (!addon->ctx->ref_count) {
wlr_log(WLR_DEBUG, "Destroying session context");
transactions_destroy(&addon->ctx->transactions);
free(addon->ctx);
}
free(addon);
}
struct wl_resource_addon *
resource_addon_create(struct session_context *ctx)
{
struct wl_resource_addon *addon = znew(*addon);
if (!ctx) {
ctx = znew(*ctx);
wl_list_init(&ctx->transactions);
}
addon->ctx = ctx;
addon->ctx->ref_count++;
return addon;
}
void
transaction_add_workspace_ev(struct lab_cosmic_workspace *ws,
struct wl_resource *resource, enum pending_change change)
{
struct wl_resource_addon *addon = wl_resource_get_user_data(resource);
if (!addon) {
wlr_log(WLR_ERROR, "Failed to find manager addon for workspace transaction");
return;
}
assert(change != CW_PENDING_WS_CREATE);
struct transaction_workspace *trans_ws = znew(*trans_ws);
trans_ws->workspace = ws;
trans_ws->base.change = change;
wl_list_append(&addon->ctx->transactions, &trans_ws->base.link);
}
void
transaction_add_workspace_group_ev(struct lab_cosmic_workspace_group *group,
struct wl_resource *resource, enum pending_change change,
const char *new_workspace_name)
{
struct wl_resource_addon *addon = wl_resource_get_user_data(resource);
if (!addon) {
wlr_log(WLR_ERROR, "Failed to find manager addon for group transaction");
return;
}
assert(change == CW_PENDING_WS_CREATE);
struct transaction_group *trans_grp = znew(*trans_grp);
trans_grp->group = group;
trans_grp->base.change = change;
trans_grp->new_workspace_name = xstrdup(new_workspace_name);
wl_list_append(&addon->ctx->transactions, &trans_grp->base.link);
}

View file

@ -0,0 +1 @@
subdir('cosmic_workspaces')

View file

@ -137,7 +137,7 @@ matches_criteria(struct view *view, enum lab_view_criteria criteria)
* special in that they live in a different tree. * special in that they live in a different tree.
*/ */
struct server *server = view->server; struct server *server = view->server;
if (view->scene_tree->node.parent != server->workspace_current->tree if (view->scene_tree->node.parent != server->workspaces.current->tree
&& !view_is_always_on_top(view)) { && !view_is_always_on_top(view)) {
return false; return false;
} }
@ -1451,7 +1451,7 @@ view_toggle_always_on_top(struct view *view)
{ {
assert(view); assert(view);
if (view_is_always_on_top(view)) { if (view_is_always_on_top(view)) {
view->workspace = view->server->workspace_current; view->workspace = view->server->workspaces.current;
wlr_scene_node_reparent(&view->scene_tree->node, wlr_scene_node_reparent(&view->scene_tree->node,
view->workspace->tree); view->workspace->tree);
} else { } else {
@ -1473,7 +1473,7 @@ view_toggle_always_on_bottom(struct view *view)
{ {
assert(view); assert(view);
if (view_is_always_on_bottom(view)) { if (view_is_always_on_bottom(view)) {
view->workspace = view->server->workspace_current; view->workspace = view->server->workspaces.current;
wlr_scene_node_reparent(&view->scene_tree->node, wlr_scene_node_reparent(&view->scene_tree->node,
view->workspace->tree); view->workspace->tree);
} else { } else {

View file

@ -15,10 +15,13 @@
#include "common/mem.h" #include "common/mem.h"
#include "input/keyboard.h" #include "input/keyboard.h"
#include "labwc.h" #include "labwc.h"
#include "protocols/cosmic-workspaces.h"
#include "view.h" #include "view.h"
#include "workspaces.h" #include "workspaces.h"
#include "xwayland.h" #include "xwayland.h"
#define COSMIC_WORKSPACES_VERSION 1
/* Internal helpers */ /* Internal helpers */
static size_t static size_t
parse_workspace_index(const char *name) parse_workspace_index(const char *name)
@ -66,7 +69,7 @@ _osd_update(struct server *server)
theme->osd_workspace_switcher_boxes_height == 0; theme->osd_workspace_switcher_boxes_height == 0;
/* Dimensions */ /* Dimensions */
size_t workspace_count = wl_list_length(&server->workspaces); size_t workspace_count = wl_list_length(&server->workspaces.all);
uint16_t marker_width = workspace_count * (rect_width + padding) - padding; uint16_t marker_width = workspace_count * (rect_width + padding) - padding;
uint16_t width = margin * 2 + (marker_width < 200 ? 200 : marker_width); uint16_t width = margin * 2 + (marker_width < 200 ? 200 : marker_width);
uint16_t height = margin * (hide_boxes ? 2 : 3) + rect_height + font_height(&rc.font_osd); uint16_t height = margin * (hide_boxes ? 2 : 3) + rect_height + font_height(&rc.font_osd);
@ -106,8 +109,8 @@ _osd_update(struct server *server)
uint16_t x; uint16_t x;
if (!hide_boxes) { if (!hide_boxes) {
x = (width - marker_width) / 2; x = (width - marker_width) / 2;
wl_list_for_each(workspace, &server->workspaces, link) { wl_list_for_each(workspace, &server->workspaces.all, link) {
bool active = workspace == server->workspace_current; bool active = workspace == server->workspaces.current;
set_cairo_color(cairo, server->theme->osd_label_text_color); set_cairo_color(cairo, server->theme->osd_label_text_color);
cairo_rectangle(cairo, x, margin, cairo_rectangle(cairo, x, margin,
rect_width - padding, rect_height); rect_width - padding, rect_height);
@ -128,7 +131,7 @@ _osd_update(struct server *server)
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
/* Center workspace indicator on the x axis */ /* Center workspace indicator on the x axis */
int req_width = font_width(&rc.font_osd, server->workspace_current->name); int req_width = font_width(&rc.font_osd, server->workspaces.current->name);
req_width = MIN(req_width, width - 2 * margin); req_width = MIN(req_width, width - 2 * margin);
x = (width - req_width) / 2; x = (width - req_width) / 2;
if (!hide_boxes) { if (!hide_boxes) {
@ -141,7 +144,7 @@ _osd_update(struct server *server)
pango_layout_set_font_description(layout, desc); pango_layout_set_font_description(layout, desc);
pango_layout_set_width(layout, req_width * PANGO_SCALE); pango_layout_set_width(layout, req_width * PANGO_SCALE);
pango_font_description_free(desc); pango_font_description_free(desc);
pango_layout_set_text(layout, server->workspace_current->name, -1); pango_layout_set_text(layout, server->workspaces.current->name, -1);
pango_cairo_show_layout(cairo, layout); pango_cairo_show_layout(cairo, layout);
g_object_unref(layout); g_object_unref(layout);
@ -172,6 +175,15 @@ _osd_update(struct server *server)
} }
} }
/* cosmic workspace handlers */
static void
handle_workspace_activate(struct wl_listener *listener, void *data)
{
struct workspace *workspace = wl_container_of(listener, workspace, on.activate);
workspaces_switch_to(workspace, /* update_focus */ true);
wlr_log(WLR_INFO, "activating workspace %s", workspace->name);
}
/* Internal API */ /* Internal API */
static void static void
add_workspace(struct server *server, const char *name) add_workspace(struct server *server, const char *name)
@ -180,12 +192,21 @@ add_workspace(struct server *server, const char *name)
workspace->server = server; workspace->server = server;
workspace->name = xstrdup(name); workspace->name = xstrdup(name);
workspace->tree = wlr_scene_tree_create(server->view_tree); workspace->tree = wlr_scene_tree_create(server->view_tree);
wl_list_append(&server->workspaces, &workspace->link); wl_list_append(&server->workspaces.all, &workspace->link);
if (!server->workspace_current) { if (!server->workspaces.current) {
server->workspace_current = workspace; server->workspaces.current = workspace;
} else { } else {
wlr_scene_node_set_enabled(&workspace->tree->node, false); wlr_scene_node_set_enabled(&workspace->tree->node, false);
} }
bool active = server->workspaces.current == workspace;
workspace->cosmic_workspace = lab_cosmic_workspace_create(server->workspaces.cosmic_group);
lab_cosmic_workspace_set_name(workspace->cosmic_workspace, name);
lab_cosmic_workspace_set_active(workspace->cosmic_workspace, active);
lab_cosmic_workspace_set_hidden(workspace->cosmic_workspace, !active);
workspace->on.activate.notify = handle_workspace_activate;
wl_signal_add(&workspace->cosmic_workspace->events.activate, &workspace->on.activate);
} }
static struct workspace * static struct workspace *
@ -260,7 +281,14 @@ _osd_show(struct server *server)
void void
workspaces_init(struct server *server) workspaces_init(struct server *server)
{ {
wl_list_init(&server->workspaces); server->workspaces.cosmic_manager = lab_cosmic_workspace_manager_create(
server->wl_display, /* capabilities */ CW_CAP_WS_ACTIVATE,
COSMIC_WORKSPACES_VERSION);
server->workspaces.cosmic_group = lab_cosmic_workspace_group_create(
server->workspaces.cosmic_manager);
wl_list_init(&server->workspaces.all);
struct workspace *conf; struct workspace *conf;
wl_list_for_each(conf, &rc.workspace_config.workspaces, link) { wl_list_for_each(conf, &rc.workspace_config.workspaces, link) {
@ -278,13 +306,18 @@ workspaces_switch_to(struct workspace *target, bool update_focus)
{ {
assert(target); assert(target);
struct server *server = target->server; struct server *server = target->server;
if (target == server->workspace_current) { if (target == server->workspaces.current) {
return; return;
} }
/* Disable the old workspace */ /* Disable the old workspace */
wlr_scene_node_set_enabled( wlr_scene_node_set_enabled(
&server->workspace_current->tree->node, false); &server->workspaces.current->tree->node, false);
lab_cosmic_workspace_set_active(
server->workspaces.current->cosmic_workspace, false);
lab_cosmic_workspace_set_hidden(
server->workspaces.current->cosmic_workspace, true);
/* Move Omnipresent views to new workspace */ /* Move Omnipresent views to new workspace */
struct view *view; struct view *view;
@ -300,10 +333,10 @@ workspaces_switch_to(struct workspace *target, bool update_focus)
wlr_scene_node_set_enabled(&target->tree->node, true); wlr_scene_node_set_enabled(&target->tree->node, true);
/* Save the last visited workspace */ /* Save the last visited workspace */
server->workspace_last = server->workspace_current; server->workspaces.last = server->workspaces.current;
/* Make sure new views will spawn on the new workspace */ /* Make sure new views will spawn on the new workspace */
server->workspace_current = target; server->workspaces.current = target;
/* /*
* Make sure we are focusing what the user sees. * Make sure we are focusing what the user sees.
@ -331,6 +364,9 @@ workspaces_switch_to(struct workspace *target, bool update_focus)
/* Ensure that only currently visible fullscreen windows hide the top layer */ /* Ensure that only currently visible fullscreen windows hide the top layer */
desktop_update_top_layer_visiblity(server); desktop_update_top_layer_visiblity(server);
lab_cosmic_workspace_set_active(target->cosmic_workspace, true);
lab_cosmic_workspace_set_hidden(target->cosmic_workspace, false);
} }
void void
@ -362,7 +398,7 @@ workspaces_find(struct workspace *anchor, const char *name, bool wrap)
size_t index = 0; size_t index = 0;
struct workspace *target; struct workspace *target;
size_t wants_index = parse_workspace_index(name); size_t wants_index = parse_workspace_index(name);
struct wl_list *workspaces = &anchor->server->workspaces; struct wl_list *workspaces = &anchor->server->workspaces.all;
if (wants_index) { if (wants_index) {
wl_list_for_each(target, workspaces, link) { wl_list_for_each(target, workspaces, link) {
@ -373,7 +409,7 @@ workspaces_find(struct workspace *anchor, const char *name, bool wrap)
} else if (!strcasecmp(name, "current")) { } else if (!strcasecmp(name, "current")) {
return anchor; return anchor;
} else if (!strcasecmp(name, "last")) { } else if (!strcasecmp(name, "last")) {
return anchor->server->workspace_last; return anchor->server->workspaces.last;
} else if (!strcasecmp(name, "left")) { } else if (!strcasecmp(name, "left")) {
return get_prev(anchor, workspaces, wrap); return get_prev(anchor, workspaces, wrap);
} else if (!strcasecmp(name, "right")) { } else if (!strcasecmp(name, "right")) {
@ -395,6 +431,8 @@ destroy_workspace(struct workspace *workspace)
wlr_scene_node_destroy(&workspace->tree->node); wlr_scene_node_destroy(&workspace->tree->node);
zfree(workspace->name); zfree(workspace->name);
wl_list_remove(&workspace->link); wl_list_remove(&workspace->link);
lab_cosmic_workspace_destroy(workspace->cosmic_workspace);
free(workspace); free(workspace);
} }
@ -408,7 +446,7 @@ workspaces_reconfigure(struct server *server)
* - Destroy workspaces if fewer workspace are desired * - Destroy workspaces if fewer workspace are desired
*/ */
struct wl_list *actual_workspace_link = server->workspaces.next; struct wl_list *actual_workspace_link = server->workspaces.all.next;
struct workspace *configured_workspace; struct workspace *configured_workspace;
wl_list_for_each(configured_workspace, wl_list_for_each(configured_workspace,
@ -416,7 +454,7 @@ workspaces_reconfigure(struct server *server)
struct workspace *actual_workspace = wl_container_of( struct workspace *actual_workspace = wl_container_of(
actual_workspace_link, actual_workspace, link); actual_workspace_link, actual_workspace, link);
if (actual_workspace_link == &server->workspaces) { if (actual_workspace_link == &server->workspaces.all) {
/* # of configured workspaces increased */ /* # of configured workspaces increased */
wlr_log(WLR_DEBUG, "Adding workspace \"%s\"", wlr_log(WLR_DEBUG, "Adding workspace \"%s\"",
configured_workspace->name); configured_workspace->name);
@ -429,20 +467,22 @@ workspaces_reconfigure(struct server *server)
actual_workspace->name, configured_workspace->name); actual_workspace->name, configured_workspace->name);
free(actual_workspace->name); free(actual_workspace->name);
actual_workspace->name = xstrdup(configured_workspace->name); actual_workspace->name = xstrdup(configured_workspace->name);
lab_cosmic_workspace_set_name(
actual_workspace->cosmic_workspace, actual_workspace->name);
} }
actual_workspace_link = actual_workspace_link->next; actual_workspace_link = actual_workspace_link->next;
} }
if (actual_workspace_link == &server->workspaces) { if (actual_workspace_link == &server->workspaces.all) {
return; return;
} }
/* # of configured workspaces decreased */ /* # of configured workspaces decreased */
overlay_hide(&server->seat); overlay_hide(&server->seat);
struct workspace *first_workspace = struct workspace *first_workspace =
wl_container_of(server->workspaces.next, first_workspace, link); wl_container_of(server->workspaces.all.next, first_workspace, link);
while (actual_workspace_link != &server->workspaces) { while (actual_workspace_link != &server->workspaces.all) {
struct workspace *actual_workspace = wl_container_of( struct workspace *actual_workspace = wl_container_of(
actual_workspace_link, actual_workspace, link); actual_workspace_link, actual_workspace, link);
@ -456,12 +496,12 @@ workspaces_reconfigure(struct server *server)
} }
} }
if (server->workspace_current == actual_workspace) { if (server->workspaces.current == actual_workspace) {
workspaces_switch_to(first_workspace, workspaces_switch_to(first_workspace,
/* update_focus */ true); /* update_focus */ true);
} }
if (server->workspace_last == actual_workspace) { if (server->workspaces.last == actual_workspace) {
server->workspace_last = first_workspace; server->workspaces.last = first_workspace;
} }
actual_workspace_link = actual_workspace_link->next; actual_workspace_link = actual_workspace_link->next;
@ -473,8 +513,8 @@ void
workspaces_destroy(struct server *server) workspaces_destroy(struct server *server)
{ {
struct workspace *workspace, *tmp; struct workspace *workspace, *tmp;
wl_list_for_each_safe(workspace, tmp, &server->workspaces, link) { wl_list_for_each_safe(workspace, tmp, &server->workspaces.all, link) {
destroy_workspace(workspace); destroy_workspace(workspace);
} }
assert(wl_list_empty(&server->workspaces)); assert(wl_list_empty(&server->workspaces.all));
} }

View file

@ -895,7 +895,7 @@ xdg_toplevel_new(struct wl_listener *listener, void *data)
view->output->wlr_output->scale); view->output->wlr_output->scale);
} }
view->workspace = server->workspace_current; view->workspace = server->workspaces.current;
view->scene_tree = wlr_scene_tree_create(view->workspace->tree); view->scene_tree = wlr_scene_tree_create(view->workspace->tree);
wlr_scene_node_set_enabled(&view->scene_tree->node, false); wlr_scene_node_set_enabled(&view->scene_tree->node, false);

View file

@ -975,7 +975,7 @@ xwayland_view_create(struct server *server,
xwayland_view->xwayland_surface = xsurface; xwayland_view->xwayland_surface = xsurface;
xsurface->data = view; xsurface->data = view;
view->workspace = server->workspace_current; view->workspace = server->workspaces.current;
view->scene_tree = wlr_scene_tree_create(view->workspace->tree); view->scene_tree = wlr_scene_tree_create(view->workspace->tree);
node_descriptor_create(&view->scene_tree->node, LAB_NODE_DESC_VIEW, view); node_descriptor_create(&view->scene_tree->node, LAB_NODE_DESC_VIEW, view);