1
0
Fork 0
forked from wry/wry

ext-workspace: implement v1

This commit is contained in:
Julian Orth 2025-01-24 16:32:59 +01:00
parent 2b76083d6e
commit a4e197d92a
22 changed files with 948 additions and 4 deletions

View file

@ -56,6 +56,7 @@ bitflags! {
CAP_SEAT_MANAGER = 1 << 8,
CAP_DRM_LEASE = 1 << 9,
CAP_INPUT_METHOD = 1 << 10,
CAP_WORKSPACE = 1 << 11,
}
pub const CAPS_DEFAULT: ClientCaps = ClientCaps(CAP_LAYER_SHELL.0 | CAP_DRM_LEASE.0);

View file

@ -27,6 +27,7 @@ use {
xdg_surface::{xdg_popup::XdgPopup, xdg_toplevel::XdgToplevel, XdgSurface},
WlSurface,
},
workspace_manager::ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1,
wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1,
wp_linux_drm_syncobj_timeline_v1::WpLinuxDrmSyncobjTimelineV1,
xdg_positioner::XdgPositioner,
@ -39,9 +40,9 @@ use {
},
wire::{
ExtDataControlSourceV1Id, ExtForeignToplevelHandleV1Id, ExtImageCaptureSourceV1Id,
ExtImageCopyCaptureSessionV1Id, JayOutputId, JayScreencastId, JayToplevelId,
JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId, WlPointerId, WlRegionId,
WlRegistryId, WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id,
ExtImageCopyCaptureSessionV1Id, ExtWorkspaceGroupHandleV1Id, JayOutputId,
JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId,
WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id,
WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId, XdgSurfaceId,
XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id,
ZwpTabletToolV2Id,
@ -82,6 +83,8 @@ pub struct Objects {
pub ext_copy_sessions:
CopyHashMap<ExtImageCopyCaptureSessionV1Id, Rc<ExtImageCopyCaptureSessionV1>>,
pub ext_data_sources: CopyHashMap<ExtDataControlSourceV1Id, Rc<ExtDataControlSourceV1>>,
pub ext_workspace_groups:
CopyHashMap<ExtWorkspaceGroupHandleV1Id, Rc<ExtWorkspaceGroupHandleV1>>,
ids: RefCell<Vec<usize>>,
}
@ -119,6 +122,7 @@ impl Objects {
foreign_toplevel_handles: Default::default(),
ext_copy_sessions: Default::default(),
ext_data_sources: Default::default(),
ext_workspace_groups: Default::default(),
ids: RefCell::new(vec![]),
}
}
@ -160,6 +164,7 @@ impl Objects {
self.foreign_toplevel_handles.clear();
self.ext_copy_sessions.clear();
self.ext_data_sources.clear();
self.ext_workspace_groups.clear();
}
pub fn id<T>(&self, client_data: &Client) -> Result<T, ClientError>

View file

@ -23,6 +23,7 @@ use {
jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts},
wl_output::{OutputId, PersistentOutputState, WlOutputGlobal},
wl_surface::{zwp_input_popup_surface_v2::input_popup_positioning, NoneSurfaceExt},
workspace_manager::workspace_manager_done,
},
io_uring::{IoUring, IoUringError},
leaks,
@ -278,6 +279,7 @@ fn start_compositor2(
const_40hz_latch: Default::default(),
tray_item_ids: Default::default(),
data_control_device_ids: Default::default(),
workspace_managers: Default::default(),
});
state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state);
@ -446,6 +448,10 @@ fn start_global_event_handlers(
Phase::Present,
handle_const_40hz_latch(state.clone()),
),
eng.spawn(
"workspace manager done",
workspace_manager_done(state.clone()),
),
]
}
@ -593,6 +599,7 @@ fn create_dummy_output(state: &Rc<State>) {
before_latch_event: Default::default(),
tray_start_rel: Default::default(),
tray_items: Default::default(),
ext_workspace_groups: Default::default(),
});
let dummy_workspace = Rc::new(WorkspaceNode {
id: state.node_ids.next(),
@ -615,6 +622,8 @@ fn create_dummy_output(state: &Rc<State>) {
title_texture: Default::default(),
attention_requests: Default::default(),
render_highlight: Default::default(),
ext_workspaces: Default::default(),
opt: Default::default(),
});
*dummy_workspace.output_link.borrow_mut() =
Some(dummy_output.workspaces.add_last(dummy_workspace.clone()));

View file

@ -40,6 +40,7 @@ use {
wl_shm::WlShmGlobal,
wl_subcompositor::WlSubcompositorGlobal,
wl_surface::xwayland_shell_v1::XwaylandShellV1Global,
workspace_manager::ext_workspace_manager_v1::ExtWorkspaceManagerV1Global,
wp_alpha_modifier_v1::WpAlphaModifierV1Global,
wp_commit_timing_manager_v1::WpCommitTimingManagerV1Global,
wp_content_type_manager_v1::WpContentTypeManagerV1Global,
@ -213,6 +214,7 @@ impl Globals {
add_singleton!(WpCommitTimingManagerV1Global);
add_singleton!(ExtDataControlManagerV1Global);
add_singleton!(WlFixesGlobal);
add_singleton!(ExtWorkspaceManagerV1Global);
}
pub fn add_backend_singletons(&self, backend: &Rc<dyn Backend>) {

View file

@ -46,6 +46,7 @@ pub mod wl_shm;
pub mod wl_shm_pool;
pub mod wl_subcompositor;
pub mod wl_surface;
pub mod workspace_manager;
pub mod wp_alpha_modifier_v1;
pub mod wp_commit_timing_manager_v1;
pub mod wp_content_type_manager_v1;

View file

@ -13,7 +13,7 @@ use {
state::{ConnectorData, State},
tree::{calculate_logical_size, OutputNode, TearingMode, VrrMode},
utils::{
cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap,
cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, rc_eq::rc_eq,
transform_ext::TransformExt,
},
wire::{wl_output::*, WlOutputId, ZxdgOutputV1Id},
@ -238,6 +238,11 @@ impl WlOutputGlobal {
if obj.version >= SEND_DONE_SINCE {
obj.send_done();
}
for group in client.objects.ext_workspace_groups.lock().values() {
if rc_eq(&group.output, &self.opt) {
group.handle_new_output(&obj);
}
}
Ok(())
}

View file

@ -0,0 +1,64 @@
use {
crate::{
client::Client,
ifs::workspace_manager::{
ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1,
ext_workspace_manager_v1::{
ExtWorkspaceManagerV1, WorkspaceManagerId, WorkspaceManagerIds,
},
},
state::State,
tree::{OutputNode, WorkspaceNode},
utils::{copyhashmap::CopyHashMap, opt::Opt, queue::AsyncQueue},
},
std::rc::Rc,
};
pub mod ext_workspace_group_handle_v1;
pub mod ext_workspace_handle_v1;
pub mod ext_workspace_manager_v1;
#[derive(Default)]
pub struct WorkspaceManagerState {
queue: AsyncQueue<Rc<Opt<ExtWorkspaceManagerV1>>>,
dangling_group: Rc<Opt<ExtWorkspaceGroupHandleV1>>,
ids: WorkspaceManagerIds,
managers: CopyHashMap<WorkspaceManagerId, Rc<ExtWorkspaceManagerV1>>,
}
impl WorkspaceManagerState {
pub fn clear(&self) {
self.managers.clear();
self.queue.clear();
}
pub fn announce_output(&self, on: &OutputNode) {
for manager in self.managers.lock().values() {
manager.announce_output(on);
}
}
pub fn announce_workspace(&self, output: &OutputNode, ws: &WorkspaceNode) {
for manager in self.managers.lock().values() {
manager.announce_workspace(output, ws);
}
}
}
pub async fn workspace_manager_done(state: Rc<State>) {
loop {
let manager = state.workspace_managers.queue.pop().await;
if let Some(manager) = manager.get() {
manager.send_done();
}
}
}
fn group_or_dangling(
client: &Client,
group: Option<&ExtWorkspaceGroupHandleV1>,
) -> Rc<Opt<ExtWorkspaceGroupHandleV1>> {
group
.map(|g| g.opt.clone())
.unwrap_or_else(|| client.state.workspace_managers.dangling_group.clone())
}

View file

@ -0,0 +1,164 @@
use {
crate::{
client::{Client, ClientError},
ifs::{
wl_output::{OutputGlobalOpt, WlOutput},
workspace_manager::{
ext_workspace_handle_v1::ExtWorkspaceHandleV1,
ext_workspace_manager_v1::{
ExtWorkspaceManagerV1, WorkspaceChange, WorkspaceManagerId,
},
},
},
leaks::Tracker,
object::{Object, Version},
utils::opt::Opt,
wire::{ext_workspace_group_handle_v1::*, ExtWorkspaceGroupHandleV1Id},
},
std::rc::Rc,
thiserror::Error,
};
pub struct ExtWorkspaceGroupHandleV1 {
pub(super) id: ExtWorkspaceGroupHandleV1Id,
pub(super) client: Rc<Client>,
pub(super) tracker: Tracker<Self>,
pub(super) version: Version,
pub output: Rc<OutputGlobalOpt>,
pub(super) manager_id: WorkspaceManagerId,
pub(super) manager: Rc<Opt<ExtWorkspaceManagerV1>>,
pub(super) opt: Rc<Opt<ExtWorkspaceGroupHandleV1>>,
}
const CAP_CREATE_WORKSPACE: u32 = 1;
impl ExtWorkspaceGroupHandleV1 {
fn detach(&self) {
self.opt.set(None);
if let Some(node) = self.output.node() {
node.ext_workspace_groups.remove(&self.manager_id);
}
}
pub(super) fn send_capabilities(&self) {
let capabilities = CAP_CREATE_WORKSPACE;
self.client.event(Capabilities {
self_id: self.id,
capabilities,
});
}
pub(super) fn send_output_enter(&self, output: &WlOutput) {
self.client.event(OutputEnter {
self_id: self.id,
output: output.id,
});
}
#[expect(dead_code)]
fn send_output_leave(&self, output: &WlOutput) {
self.client.event(OutputLeave {
self_id: self.id,
output: output.id,
});
}
pub(super) fn send_workspace_enter(&self, workspace: &ExtWorkspaceHandleV1) {
self.client.event(WorkspaceEnter {
self_id: self.id,
workspace: workspace.id,
});
}
pub(super) fn send_workspace_leave(&self, workspace: &ExtWorkspaceHandleV1) {
self.client.event(WorkspaceLeave {
self_id: self.id,
workspace: workspace.id,
});
}
fn send_removed(&self) {
self.client.event(Removed { self_id: self.id });
}
pub fn handle_destroyed(&self) {
self.detach();
if let Some(manager) = self.manager.get() {
self.send_removed();
manager.schedule_done();
}
}
pub fn handle_new_output(&self, output: &WlOutput) {
if let Some(manager) = self.manager.get() {
self.send_output_enter(output);
manager.schedule_done();
}
}
}
object_base! {
self = ExtWorkspaceGroupHandleV1;
version = self.version;
}
impl Object for ExtWorkspaceGroupHandleV1 {
fn break_loops(&self) {
self.detach();
}
}
dedicated_add_obj!(
ExtWorkspaceGroupHandleV1,
ExtWorkspaceGroupHandleV1Id,
ext_workspace_groups
);
impl ExtWorkspaceGroupHandleV1RequestHandler for ExtWorkspaceGroupHandleV1 {
type Error = ExtWorkspaceGroupHandleV1Error;
fn create_workspace(
&self,
req: CreateWorkspace<'_>,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
if self.opt.is_none() {
return Ok(());
}
let Some(manager) = self.manager.get() else {
return Ok(());
};
manager.pending.push(WorkspaceChange::CreateWorkspace(
req.workspace.to_string(),
self.output.clone(),
));
Ok(())
}
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(manager) = self.manager.get() {
if let Some(node) = self.output.node() {
let mut sent_any = false;
for ws in node.workspaces.iter() {
if let Some(ws) = ws.ext_workspaces.get(&self.manager_id) {
self.send_workspace_leave(&ws);
sent_any = true;
}
}
if sent_any {
manager.schedule_done();
}
}
}
self.detach();
self.client.remove_obj(self)?;
Ok(())
}
}
#[derive(Debug, Error)]
pub enum ExtWorkspaceGroupHandleV1Error {
#[error(transparent)]
ClientError(Box<ClientError>),
}
efrom!(ExtWorkspaceGroupHandleV1Error, ClientError);

View file

@ -0,0 +1,215 @@
use {
crate::{
client::{Client, ClientError},
ifs::workspace_manager::{
ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1,
ext_workspace_manager_v1::{
ExtWorkspaceManagerV1, WorkspaceChange, WorkspaceManagerId,
},
group_or_dangling,
},
leaks::Tracker,
object::{Object, Version},
tree::{OutputNode, WorkspaceNode},
utils::{clonecell::CloneCell, opt::Opt},
wire::{ext_workspace_handle_v1::*, ExtWorkspaceHandleV1Id},
},
std::{cell::Cell, rc::Rc},
thiserror::Error,
};
const STATE_ACTIVE: u32 = 1;
const STATE_URGENT: u32 = 2;
#[expect(dead_code)]
const STATE_HIDDEN: u32 = 4;
const CAP_ACTIVATE: u32 = 1;
#[expect(dead_code)]
const CAP_DEACTIVATE: u32 = 2;
#[expect(dead_code)]
const CAP_REMOVE: u32 = 4;
const CAP_ASSIGN: u32 = 8;
pub struct ExtWorkspaceHandleV1 {
pub(super) id: ExtWorkspaceHandleV1Id,
pub(super) client: Rc<Client>,
pub(super) tracker: Tracker<Self>,
pub version: Version,
pub(super) group: CloneCell<Rc<Opt<ExtWorkspaceGroupHandleV1>>>,
pub(super) workspace: Rc<Opt<WorkspaceNode>>,
pub(super) manager_id: WorkspaceManagerId,
pub(super) manager: Rc<Opt<ExtWorkspaceManagerV1>>,
pub(super) destroyed: Cell<bool>,
}
impl ExtWorkspaceHandleV1 {
fn detach(&self) {
if let Some(ws) = self.workspace.get() {
ws.ext_workspaces.remove(&self.manager_id);
}
}
pub(super) fn send_id(&self, id: &str) {
self.client.event(Id {
self_id: self.id,
id,
});
}
pub(super) fn send_name(&self, name: &str) {
self.client.event(Name {
self_id: self.id,
name,
});
}
#[expect(dead_code)]
fn send_coordinates(&self, coordinates: &[u32]) {
self.client.event(Coordinates {
self_id: self.id,
coordinates,
});
}
pub(super) fn send_current_state(&self) {
let Some(ws) = self.workspace.get() else {
return;
};
let mut state = 0;
let output = ws.output.get();
if let Some(active) = output.workspace.get() {
if active.id == ws.id {
state |= STATE_ACTIVE;
}
}
if ws.attention_requests.active() {
state |= STATE_URGENT;
}
self.send_state(state);
}
fn send_state(&self, state: u32) {
self.client.event(State {
self_id: self.id,
state,
});
}
pub(super) fn send_capabilities(&self) {
let capabilities = CAP_ACTIVATE | CAP_ASSIGN;
self.client.event(Capabilities {
self_id: self.id,
capabilities,
});
}
fn send_removed(&self) {
self.client.event(Removed { self_id: self.id });
}
pub fn handle_destroyed(&self) {
self.destroyed.set(true);
if let Some(manager) = self.manager.get() {
if let Some(group) = self.group.get().get() {
group.send_workspace_leave(self);
}
self.group
.set(self.client.state.workspace_managers.dangling_group.clone());
self.send_state(0);
self.send_removed();
manager.schedule_done();
}
}
pub fn handle_new_output(&self, output: &OutputNode) {
let new = output.ext_workspace_groups.get(&self.manager_id);
let new = group_or_dangling(&self.client, new.as_deref());
let old = self.group.set(new.clone());
if let Some(manager) = self.manager.get() {
if let Some(old) = old.get() {
old.send_workspace_leave(self);
}
if let Some(new) = new.get() {
new.send_workspace_enter(self);
}
manager.schedule_done();
}
}
pub fn handle_visibility_changed(&self) {
if let Some(manager) = self.manager.get() {
self.send_current_state();
manager.schedule_done();
}
}
pub fn handle_urgent_changed(&self) {
self.handle_visibility_changed();
}
}
object_base! {
self = ExtWorkspaceHandleV1;
version = self.version;
}
impl Object for ExtWorkspaceHandleV1 {
fn break_loops(&self) {
self.detach();
}
}
simple_add_obj!(ExtWorkspaceHandleV1);
impl ExtWorkspaceHandleV1RequestHandler for ExtWorkspaceHandleV1 {
type Error = ExtWorkspaceHandleV1Error;
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.detach();
self.client.remove_obj(self)?;
Ok(())
}
fn activate(&self, _req: Activate, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if self.destroyed.get() {
return Ok(());
}
let Some(manager) = self.manager.get() else {
return Ok(());
};
manager
.pending
.push(WorkspaceChange::ActivateWorkspace(self.workspace.clone()));
Ok(())
}
fn deactivate(&self, _req: Deactivate, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
fn assign(&self, req: Assign, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if self.destroyed.get() {
return Ok(());
}
let group = self.client.lookup(req.workspace_group)?;
let Some(manager) = self.manager.get() else {
return Ok(());
};
manager.pending.push(WorkspaceChange::AssignWorkspace(
self.workspace.clone(),
group.output.clone(),
));
Ok(())
}
fn remove(&self, _req: Remove, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
}
#[derive(Debug, Error)]
pub enum ExtWorkspaceHandleV1Error {
#[error(transparent)]
ClientError(Box<ClientError>),
}
efrom!(ExtWorkspaceHandleV1Error, ClientError);

View file

@ -0,0 +1,306 @@
use {
crate::{
client::{Client, ClientCaps, ClientError, CAP_WORKSPACE},
globals::{Global, GlobalName},
ifs::{
wl_output::OutputGlobalOpt,
workspace_manager::{
ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1,
ext_workspace_handle_v1::ExtWorkspaceHandleV1, group_or_dangling,
},
},
leaks::Tracker,
object::{Object, Version},
tree::{move_ws_to_output, OutputNode, WorkspaceNode, WsMoveConfig},
utils::{clonecell::CloneCell, opt::Opt, syncqueue::SyncQueue},
wire::{ext_workspace_manager_v1::*, ExtWorkspaceManagerV1Id},
},
std::{cell::Cell, rc::Rc},
thiserror::Error,
};
linear_ids!(WorkspaceManagerIds, WorkspaceManagerId, u64);
pub struct ExtWorkspaceManagerV1Global {
pub name: GlobalName,
}
pub struct ExtWorkspaceManagerV1 {
id: ExtWorkspaceManagerV1Id,
pub(super) manager_id: WorkspaceManagerId,
client: Rc<Client>,
tracker: Tracker<Self>,
version: Version,
pub(super) pending: SyncQueue<WorkspaceChange>,
opt: Rc<Opt<ExtWorkspaceManagerV1>>,
done_scheduled: Cell<bool>,
}
pub(super) enum WorkspaceChange {
CreateWorkspace(String, Rc<OutputGlobalOpt>),
ActivateWorkspace(Rc<Opt<WorkspaceNode>>),
AssignWorkspace(Rc<Opt<WorkspaceNode>>, Rc<OutputGlobalOpt>),
}
impl ExtWorkspaceManagerV1Global {
pub fn new(name: GlobalName) -> Self {
Self { name }
}
fn bind_(
self: Rc<Self>,
id: ExtWorkspaceManagerV1Id,
client: &Rc<Client>,
version: Version,
) -> Result<(), ExtWorkspaceManagerV1Error> {
let obj = Rc::new(ExtWorkspaceManagerV1 {
id,
manager_id: client.state.workspace_managers.ids.next(),
client: client.clone(),
tracker: Default::default(),
version,
pending: Default::default(),
opt: Default::default(),
done_scheduled: Cell::new(false),
});
track!(client, obj);
client.add_client_obj(&obj)?;
obj.opt.set(Some(obj.clone()));
client
.state
.workspace_managers
.managers
.set(obj.manager_id, obj.clone());
let dummy_output = client.state.dummy_output.get().unwrap();
for ws in dummy_output.workspaces.iter() {
if !ws.is_dummy {
obj.announce_workspace(&dummy_output, &ws);
}
}
for output in client.state.root.outputs.lock().values() {
obj.announce_output(output);
}
Ok(())
}
}
impl ExtWorkspaceManagerV1 {
pub(super) fn announce_output(&self, node: &OutputNode) {
let id = match self.client.new_id() {
Ok(id) => id,
Err(e) => {
self.client.error(e);
return;
}
};
let group = Rc::new(ExtWorkspaceGroupHandleV1 {
id,
client: self.client.clone(),
tracker: Default::default(),
version: self.version,
output: node.global.opt.clone(),
manager_id: self.manager_id,
manager: self.opt.clone(),
opt: Default::default(),
});
track!(self.client, group);
self.client.add_server_obj(&group);
group.opt.set(Some(group.clone()));
node.ext_workspace_groups
.set(self.manager_id, group.clone());
self.send_workspace_group(&group);
group.send_capabilities();
if let Some(bindings) = node.global.bindings.borrow().get(&self.client.id) {
for wl_output in bindings.values() {
group.send_output_enter(wl_output);
}
}
for ws in node.workspaces.iter() {
if let Some(ws) = ws.ext_workspaces.get(&self.manager_id) {
ws.handle_new_output(node);
} else {
self.announce_workspace(node, &ws);
}
}
self.schedule_done();
}
pub(super) fn announce_workspace(&self, output: &OutputNode, workspace: &WorkspaceNode) {
let id = match self.client.new_id() {
Ok(id) => id,
Err(e) => {
self.client.error(e);
return;
}
};
let group = output.ext_workspace_groups.get(&self.manager_id);
let ws = Rc::new(ExtWorkspaceHandleV1 {
id,
client: self.client.clone(),
tracker: Default::default(),
version: self.version,
group: CloneCell::new(group_or_dangling(&self.client, group.as_deref())),
workspace: workspace.opt.clone(),
manager_id: self.manager_id,
manager: self.opt.clone(),
destroyed: Cell::new(false),
});
track!(self.client, ws);
self.client.add_server_obj(&ws);
workspace.ext_workspaces.set(self.manager_id, ws.clone());
self.send_workspace(&ws);
ws.send_capabilities();
ws.send_id(&workspace.name);
ws.send_name(&workspace.name);
ws.send_current_state();
if let Some(group) = group {
group.send_workspace_enter(&ws);
}
self.schedule_done();
}
fn send_workspace_group(&self, workspace_group: &ExtWorkspaceGroupHandleV1) {
self.client.event(WorkspaceGroup {
self_id: self.id,
workspace_group: workspace_group.id,
});
}
fn send_workspace(&self, workspace: &ExtWorkspaceHandleV1) {
self.client.event(Workspace {
self_id: self.id,
workspace: workspace.id,
});
}
pub(super) fn send_done(&self) {
self.done_scheduled.set(false);
self.client.event(Done { self_id: self.id });
}
fn send_finished(&self) {
self.client.event(Finished { self_id: self.id });
}
fn detach(&self) {
self.opt.set(None);
self.pending.clear();
self.client
.state
.workspace_managers
.managers
.remove(&self.manager_id);
}
pub(super) fn schedule_done(&self) {
if self.done_scheduled.replace(true) {
return;
}
self.client
.state
.workspace_managers
.queue
.push(self.opt.clone());
}
}
global_base!(
ExtWorkspaceManagerV1Global,
ExtWorkspaceManagerV1,
ExtWorkspaceManagerV1Error
);
impl Global for ExtWorkspaceManagerV1Global {
fn singleton(&self) -> bool {
true
}
fn version(&self) -> u32 {
1
}
fn required_caps(&self) -> ClientCaps {
CAP_WORKSPACE
}
}
simple_add_global!(ExtWorkspaceManagerV1Global);
object_base! {
self = ExtWorkspaceManagerV1;
version = self.version;
}
impl Object for ExtWorkspaceManagerV1 {
fn break_loops(&self) {
self.detach();
}
}
simple_add_obj!(ExtWorkspaceManagerV1);
impl ExtWorkspaceManagerV1RequestHandler for ExtWorkspaceManagerV1 {
type Error = ExtWorkspaceManagerV1Error;
fn commit(&self, _req: Commit, _slf: &Rc<Self>) -> Result<(), Self::Error> {
while let Some(change) = self.pending.pop() {
match change {
WorkspaceChange::ActivateWorkspace(w) => {
let Some(ws) = w.get() else {
continue;
};
let output = ws.output.get();
output.show_workspace(&ws);
ws.flush_jay_workspaces();
output.schedule_update_render_data();
self.client.state.tree_changed();
}
WorkspaceChange::AssignWorkspace(w, o) => {
let Some(ws) = w.get() else {
continue;
};
let Some(o) = o.node() else {
continue;
};
let link = match &*ws.output_link.borrow() {
None => continue,
Some(l) => l.to_ref(),
};
let config = WsMoveConfig {
make_visible_always: false,
make_visible_if_empty: true,
source_is_destroyed: false,
before: None,
};
move_ws_to_output(&link, &o, config);
ws.desired_output.set(o.global.output_id.clone());
self.client.state.tree_changed();
}
WorkspaceChange::CreateWorkspace(name, output) => {
if self.client.state.workspaces.contains(&name) {
return Ok(());
}
let Some(output) = output.node() else {
return Ok(());
};
output.create_workspace(&name);
}
}
}
Ok(())
}
fn stop(&self, _req: Stop, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.detach();
self.send_finished();
self.client.remove_obj(self)?;
Ok(())
}
}
#[derive(Debug, Error)]
pub enum ExtWorkspaceManagerV1Error {
#[error(transparent)]
ClientError(Box<ClientError>),
}
efrom!(ExtWorkspaceManagerV1Error, ClientError);

View file

@ -57,6 +57,7 @@ use {
zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2,
NoneSurfaceExt,
},
workspace_manager::WorkspaceManagerState,
wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1,
wp_drm_lease_device_v1::WpDrmLeaseDeviceV1Global,
wp_linux_drm_syncobj_manager_v1::WpLinuxDrmSyncobjManagerV1Global,
@ -229,6 +230,7 @@ pub struct State {
pub const_40hz_latch: EventSource<dyn LatchListener>,
pub tray_item_ids: TrayItemIds,
pub data_control_device_ids: DataControlDeviceIds,
pub workspace_managers: WorkspaceManagerState,
}
// impl Drop for State {
@ -917,6 +919,7 @@ impl State {
self.ei_clients.clear();
self.slow_ei_clients.clear();
self.toplevels.clear();
self.workspace_managers.clear();
}
pub fn damage_hardware_cursors(&self, render: bool) {

View file

@ -197,6 +197,7 @@ impl ConnectorHandler {
before_latch_event: Default::default(),
tray_start_rel: Default::default(),
tray_items: Default::default(),
ext_workspace_groups: Default::default(),
});
on.update_visible();
on.update_rects();
@ -259,6 +260,7 @@ impl ConnectorHandler {
self.state.add_global(&tray);
self.state.tree_changed();
on.update_presentation_type();
self.state.workspace_managers.announce_output(&on);
'outer: loop {
while let Some(event) = self.data.connector.event() {
match event {
@ -331,6 +333,9 @@ impl ConnectorHandler {
};
move_ws_to_output(&ws, &target, config);
}
for group in on.ext_workspace_groups.lock().drain_values() {
group.handle_destroyed();
}
for seat in self.state.globals.seats.lock().values() {
seat.cursor_group().output_disconnected(&on, &target);
}

View file

@ -23,6 +23,10 @@ use {
zwlr_layer_surface_v1::{ExclusiveSize, ZwlrLayerSurfaceV1},
SurfaceSendPreferredScaleVisitor, SurfaceSendPreferredTransformVisitor,
},
workspace_manager::{
ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1,
ext_workspace_manager_v1::WorkspaceManagerId,
},
wp_content_type_v1::ContentType,
zwlr_layer_shell_v1::{BACKGROUND, BOTTOM, OVERLAY, TOP},
zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1,
@ -97,6 +101,7 @@ pub struct OutputNode {
pub before_latch_event: EventSource<dyn BeforeLatchListener>,
pub tray_start_rel: Cell<i32>,
pub tray_items: LinkedList<Rc<dyn DynTrayItem>>,
pub ext_workspace_groups: CopyHashMap<WorkspaceManagerId, Rc<ExtWorkspaceGroupHandleV1>>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
@ -414,6 +419,7 @@ impl OutputNode {
self.screencasts.clear();
self.screencopies.clear();
self.ext_copy_sessions.clear();
self.ext_workspace_groups.clear();
}
pub fn on_spaces_changed(self: &Rc<Self>) {
@ -635,6 +641,9 @@ impl OutputNode {
jw.send_destroyed();
jw.workspace.set(None);
}
for wh in old.ext_workspaces.lock().values() {
wh.handle_destroyed();
}
old.clear();
self.state.workspaces.remove(&old.name);
} else {
@ -678,7 +687,10 @@ impl OutputNode {
title_texture: Default::default(),
attention_requests: Default::default(),
render_highlight: Default::default(),
ext_workspaces: Default::default(),
opt: Default::default(),
});
ws.opt.set(Some(ws.clone()));
ws.update_has_captures();
*ws.output_link.borrow_mut() = Some(self.workspaces.add_last(ws.clone()));
self.state.workspaces.set(name.to_string(), ws.clone());
@ -694,6 +706,7 @@ impl OutputNode {
for (client, e) in clients_to_kill.values() {
client.error(e);
}
self.state.workspace_managers.announce_workspace(self, &ws);
self.schedule_update_render_data();
ws
}

View file

@ -10,6 +10,10 @@ use {
wl_surface::{
x_surface::xwindow::Xwindow, xdg_surface::xdg_toplevel::XdgToplevel, WlSurface,
},
workspace_manager::{
ext_workspace_handle_v1::ExtWorkspaceHandleV1,
ext_workspace_manager_v1::WorkspaceManagerId,
},
},
rect::Rect,
renderer::Renderer,
@ -25,6 +29,7 @@ use {
copyhashmap::CopyHashMap,
linkedlist::{LinkedList, LinkedNode, NodeRef},
numcell::NumCell,
opt::Opt,
threshold_counter::ThresholdCounter,
},
wire::JayWorkspaceId,
@ -60,6 +65,8 @@ pub struct WorkspaceNode {
pub title_texture: RefCell<Option<TextTexture>>,
pub attention_requests: ThresholdCounter,
pub render_highlight: NumCell<u32>,
pub ext_workspaces: CopyHashMap<WorkspaceManagerId, Rc<ExtWorkspaceHandleV1>>,
pub opt: Rc<Opt<WorkspaceNode>>,
}
impl WorkspaceNode {
@ -68,6 +75,8 @@ impl WorkspaceNode {
*self.output_link.borrow_mut() = None;
self.fullscreen.set(None);
self.jay_workspaces.clear();
self.ext_workspaces.clear();
self.opt.set(None);
}
pub fn update_has_captures(&self) {
@ -95,6 +104,9 @@ impl WorkspaceNode {
pub fn set_output(&self, output: &Rc<OutputNode>) {
self.output.set(output.clone());
for wh in self.ext_workspaces.lock().values() {
wh.handle_new_output(output);
}
for jw in self.jay_workspaces.lock().values() {
jw.send_output(output);
}
@ -171,6 +183,9 @@ impl WorkspaceNode {
for jw in self.jay_workspaces.lock().values() {
jw.send_visible(visible);
}
for wh in self.ext_workspaces.lock().values() {
wh.handle_visibility_changed();
}
for stacked in self.stacked.iter() {
stacked.stacked_prepare_set_visible();
}
@ -236,6 +251,9 @@ impl WorkspaceNode {
fn mod_attention_requested(&self, set: bool) {
let crossed_threshold = self.attention_requests.adj(set);
if crossed_threshold {
for wh in self.ext_workspaces.lock().values() {
wh.handle_urgent_changed();
}
self.output.get().schedule_update_render_data();
}
}

View file

@ -35,6 +35,7 @@ pub mod on_drop_event;
pub mod once;
pub mod opaque;
pub mod opaque_cell;
pub mod opt;
pub mod option_ext;
pub mod oserror;
pub mod page_size;

27
src/utils/opt.rs Normal file
View file

@ -0,0 +1,27 @@
use {crate::utils::clonecell::CloneCell, std::rc::Rc};
pub struct Opt<T> {
t: CloneCell<Option<Rc<T>>>,
}
impl<T> Default for Opt<T> {
fn default() -> Self {
Self {
t: Default::default(),
}
}
}
impl<T> Opt<T> {
pub fn set(&self, t: Option<Rc<T>>) {
self.t.set(t);
}
pub fn get(&self) -> Option<Rc<T>> {
self.t.get()
}
pub fn is_none(&self) -> bool {
self.t.is_none()
}
}

View file

@ -53,4 +53,10 @@ impl<T> SyncQueue<T> {
self.swap(&mut res);
res
}
pub fn clear(&self) {
unsafe {
self.el.get().deref_mut().clear();
}
}
}