🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
//! `ext_workspace_v1` — lista i przełączanie przestrzeni roboczych dla powłoki.
//!
//! Kompozytor wystawia globalny menedżer; każdy klient (pasek `luna-shell`) dostaje
//! jedną **grupę** i po jednym **uchwycie** na przestrzeń roboczą. Uchwyt niesie
//! `id`/`name`/`state` (m.in. `active`), a jego żądanie `activate` przełącza przestrzeń
//! w kompozytorze (patrz [`crate::seat::switch_workspace`]) — to czyni kropki w pasku
//! w pełni funkcjonalnymi.
use wayland_protocols::ext::workspace::v1::server::{
ext_workspace_group_handle_v1 as group, ext_workspace_handle_v1 as workspace,
ext_workspace_manager_v1 as manager,
};
use wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum,
};
use wayland_protocols::ext::workspace::v1::server::ext_workspace_group_handle_v1::GroupCapabilities;
use wayland_protocols::ext::workspace::v1::server::ext_workspace_handle_v1::{
State as WsState, WorkspaceCapabilities,
};
use crate::state::State;
use crate::sync::LockExt;
impl GlobalDispatch<manager::ExtWorkspaceManagerV1, ()> for State {
fn bind(
state: &mut Self,
handle: &DisplayHandle,
_client: &Client,
resource: New<manager::ExtWorkspaceManagerV1>,
_data: &(),
data_init: &mut DataInit<'_, Self>,
) {
let mgr = data_init.init(resource, ());
announce(state, handle, &mgr);
state.ws_managers.push(mgr);
}
}
impl Dispatch<manager::ExtWorkspaceManagerV1, ()> for State {
fn request(
state: &mut Self,
_client: &Client,
resource: &manager::ExtWorkspaceManagerV1,
request: manager::Request,
_data: &(),
_handle: &DisplayHandle,
_data_init: &mut DataInit<'_, Self>,
) {
match request {
// Zmiany wysyłamy od razu, więc `commit` nic nie robi.
manager::Request::Commit => {}
manager::Request::Stop => {
let mgr_id = resource.id();
let client_id = resource.client().map(|c| c.id());
state.ws_managers.retain(|m| m.id() != mgr_id);
if let Some(client_id) = client_id {
state
.ws_groups
.retain(|g| g.client().map(|c| c.id() != client_id).unwrap_or(false));
state
.ws_handles
.retain(|h| h.client().map(|c| c.id() != client_id).unwrap_or(false));
}
}
_ => {}
}
}
}
impl Dispatch<group::ExtWorkspaceGroupHandleV1, ()> for State {
fn request(
state: &mut Self,
_client: &Client,
resource: &group::ExtWorkspaceGroupHandleV1,
request: group::Request,
_data: &(),
_handle: &DisplayHandle,
_data_init: &mut DataInit<'_, Self>,
) {
match request {
// Tworzenie przestrzeni przez klienta nie jest wspierane (stały zestaw).
group::Request::CreateWorkspace { .. } => {}
group::Request::Destroy => {
let id = resource.id();
state.ws_groups.retain(|g| g.id() != id);
}
_ => {}
}
}
}
impl Dispatch<workspace::ExtWorkspaceHandleV1, usize> for State {
fn request(
state: &mut Self,
_client: &Client,
resource: &workspace::ExtWorkspaceHandleV1,
request: workspace::Request,
index: &usize,
_handle: &DisplayHandle,
_data_init: &mut DataInit<'_, Self>,
) {
match request {
workspace::Request::Activate => crate::seat::switch_workspace(state, *index),
// Dezaktywacja/przypisanie/usuwanie: poza zakresem (stały zestaw przestrzeni).
workspace::Request::Deactivate
| workspace::Request::Assign { .. }
| workspace::Request::Remove => {}
workspace::Request::Destroy => {
let id = Resource::id(resource);
state.ws_handles.retain(|h| Resource::id(h) != id);
}
_ => {}
}
}
}
/// Zgłasza nowo podłączonemu menedżerowi grupę i wszystkie przestrzenie.
fn announce(state: &mut State, display: &DisplayHandle, mgr: &manager::ExtWorkspaceManagerV1) {
let Some(client) = mgr.client() else {
return;
};
let Ok(group) =
client.create_resource::<group::ExtWorkspaceGroupHandleV1, (), State>(display, 1, ())
else {
return;
};
let _ = mgr.send_event(manager::Event::WorkspaceGroup {
workspace_group: group.clone(),
});
let _ = group.send_event(group::Event::Capabilities {
capabilities: WEnum::<GroupCapabilities>::Unknown(0),
});
let cur = *state.ws_current.lock_or_recover();
for index in 0..state.ws_count {
let Ok(ws) = client
.create_resource::<workspace::ExtWorkspaceHandleV1, usize, State>(display, 1, index)
else {
continue;
};
let _ = mgr.send_event(manager::Event::Workspace {
workspace: ws.clone(),
});
let _ = ws.send_event(workspace::Event::Id {
id: format!("ws{}", index + 1),
});
let _ = ws.send_event(workspace::Event::Name {
name: format!("{}", index + 1),
});
let _ = ws.send_event(workspace::Event::Coordinates {
coordinates: Vec::new(),
});
let _ = ws.send_event(workspace::Event::Capabilities {
capabilities: WEnum::Value(WorkspaceCapabilities::Activate),
});
let _ = ws.send_event(workspace::Event::State {
state: ws_state(index, cur),
});
let _ = group.send_event(group::Event::WorkspaceEnter {
workspace: ws.clone(),
});
state.ws_handles.push(ws);
}
state.ws_groups.push(group);
let _ = mgr.send_event(manager::Event::Done);
}
/// Rozgłasza stan wszystkich przestrzeni (po przełączeniu) do podłączonych klientów.
pub fn broadcast_workspace_states(state: &State) {
let cur = *state.ws_current.lock_or_recover();
for h in &state.ws_handles {
if let Some(&index) = h.data::<usize>() {
let _ = h.send_event(workspace::Event::State {
state: ws_state(index, cur),
});
}
}
}
/// Stan przestrzeni `index`, gdy bieżąca to `cur`.
fn ws_state(index: usize, cur: usize) -> WEnum<WsState> {
if index == cur {
WEnum::Value(WsState::Active)
} else {
WEnum::Unknown(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_the_current_workspace_is_active() {
assert!(matches!(ws_state(0, 0), WEnum::Value(WsState::Active)));
assert!(matches!(ws_state(1, 0), WEnum::Unknown(0)));
assert!(matches!(ws_state(3, 3), WEnum::Value(WsState::Active)));
}
}