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

@ -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);