mirror of
				https://github.com/labwc/labwc.git
				synced 2025-10-29 05:40:24 -04:00 
			
		
		
		
	cosmic-workspaces: protocol implementation
This commit is contained in:
		
							parent
							
								
									a9daaa05be
								
							
						
					
					
						commit
						31f4336ed6
					
				
					 10 changed files with 1445 additions and 0 deletions
				
			
		
							
								
								
									
										61
									
								
								include/protocols/cosmic-workspaces-internal.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								include/protocols/cosmic-workspaces-internal.h
									
										
									
									
									
										Normal 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 */ | ||||
							
								
								
									
										94
									
								
								include/protocols/cosmic-workspaces.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								include/protocols/cosmic-workspaces.h
									
										
									
									
									
										Normal 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 */ | ||||
							
								
								
									
										364
									
								
								protocols/cosmic-workspace-unstable-v1.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										364
									
								
								protocols/cosmic-workspace-unstable-v1.xml
									
										
									
									
									
										Normal 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> | ||||
|  | @ -21,6 +21,7 @@ server_protocols = [ | |||
| 	wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', | ||||
| 	wl_protocol_dir / 'staging/xwayland-shell/xwayland-shell-v1.xml', | ||||
| 	wl_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml', | ||||
| 	'cosmic-workspace-unstable-v1.xml', | ||||
| 	'wlr-layer-shell-unstable-v1.xml', | ||||
| 	'wlr-input-inhibitor-unstable-v1.xml', | ||||
| 	'wlr-output-power-management-unstable-v1.xml', | ||||
|  |  | |||
|  | @ -52,3 +52,4 @@ subdir('decorations') | |||
| subdir('input') | ||||
| subdir('menu') | ||||
| subdir('ssd') | ||||
| subdir('protocols') | ||||
|  |  | |||
							
								
								
									
										651
									
								
								src/protocols/cosmic_workspaces/cosmic-workspaces.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										651
									
								
								src/protocols/cosmic_workspaces/cosmic-workspaces.c
									
										
									
									
									
										Normal 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); | ||||
| } | ||||
							
								
								
									
										5
									
								
								src/protocols/cosmic_workspaces/meson.build
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/protocols/cosmic_workspaces/meson.build
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | |||
| labwc_sources += files( | ||||
|   'cosmic-workspaces.c', | ||||
|   'transactions.c', | ||||
|   'output.c', | ||||
| ) | ||||
							
								
								
									
										174
									
								
								src/protocols/cosmic_workspaces/output.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								src/protocols/cosmic_workspaces/output.c
									
										
									
									
									
										Normal 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); | ||||
| } | ||||
							
								
								
									
										93
									
								
								src/protocols/cosmic_workspaces/transactions.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/protocols/cosmic_workspaces/transactions.c
									
										
									
									
									
										Normal 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); | ||||
| } | ||||
							
								
								
									
										1
									
								
								src/protocols/meson.build
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/protocols/meson.build
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| subdir('cosmic_workspaces') | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Consolatis
						Consolatis