feat: expose shell workspace metadata

This commit is contained in:
Samuel Onoja committed 2026-09-25 06:50:26 +01:00
1 parent 3384cfde0f
commit 0c6fefacc1
5 files changed
+204 -27

No files matched your search

+114 -12
View File
@@ -1,3 +1,4 @@
use ferese_core::{OutputId, WorkspaceId};
use ferese_layout::WindowId;
use ferese_protocols::shell::v1::server::{
ferese_shell_manager_v1::{self, FereseShellManagerV1},
@@ -91,6 +92,20 @@ impl Dispatch<FereseShellV1, ()> for Ferese {
join_id(window_hi, window_lo),
ShellWindowAction::Close,
),
ferese_shell_v1::Request::ActivateWorkspace {
workspace_hi,
workspace_lo,
} => {
let workspace = WorkspaceId(join_u64(workspace_hi, workspace_lo));
if !state.activate_managed_workspace(workspace) {
send_request_failed(
shell,
ferese_shell_v1::FailedRequest::ActivateWorkspace,
workspace.0,
);
}
}
ferese_shell_v1::Request::EnterOverview => state.set_overview_active(true),
ferese_shell_v1::Request::ExitOverview => state.set_overview_active(false),
ferese_shell_v1::Request::SelectOverviewWindow {
@@ -103,7 +118,7 @@ impl Dispatch<FereseShellV1, ()> for Ferese {
send_request_failed(
shell,
ferese_shell_v1::FailedRequest::SelectOverviewWindow,
id,
id.0,
);
}
}
@@ -128,9 +143,40 @@ impl Ferese {
fn send_shell_snapshot(&mut self, shell: &FereseShellV1) {
self.shell_snapshot_serial = self.shell_snapshot_serial.wrapping_add(1);
let serial = self.shell_snapshot_serial;
let outputs = self.output_snapshots();
let workspaces = self.workspace_snapshots();
let snapshots = self.managed_window_snapshots();
shell.snapshot_begin(serial);
for output in outputs {
let (output_hi, output_lo) = split_id(output.id.0);
let (workspace_hi, workspace_lo) = split_id(output.active_workspace.0);
shell.output(
output_hi,
output_lo,
output.name,
workspace_hi,
workspace_lo,
u32::from(output.focused),
);
}
for workspace in workspaces {
let (workspace_hi, workspace_lo) = split_id(workspace.id.0);
let (output_hi, output_lo) = workspace
.output
.map(|output| split_id(output.0))
.unwrap_or_default();
shell.workspace(
workspace_hi,
workspace_lo,
output_hi,
output_lo,
workspace.name,
u32::from(workspace.active),
);
}
for snapshot in snapshots {
let (window_hi, window_lo) = split_id(snapshot.id.0);
let (workspace_hi, workspace_lo) = split_id(snapshot.workspace);
@@ -148,6 +194,48 @@ impl Ferese {
shell.snapshot_end(serial);
}
fn output_snapshots(&self) -> Vec<OutputSnapshot> {
let focused = self.output_workspaces.focused_output();
let mut outputs = self
.output_ids
.iter()
.filter_map(|(output, id)| {
Some(OutputSnapshot {
id: *id,
name: output.name(),
active_workspace: self.output_workspaces.active_workspace(*id)?,
focused: focused == Some(*id),
})
})
.collect::<Vec<_>>();
outputs.sort_by_key(|output| output.id.0);
outputs
}
fn workspace_snapshots(&self) -> Vec<WorkspaceSnapshot> {
let mut workspaces = self
.workspaces
.iter()
.map(|workspace| {
let output = self.output_workspaces.output_for_workspace(workspace.id);
let active = output.is_some_and(|output| {
self.output_workspaces.active_workspace(output) == Some(workspace.id)
});
WorkspaceSnapshot {
id: workspace.id,
output,
name: workspace.name.clone(),
active,
}
})
.collect::<Vec<_>>();
workspaces.sort_by_key(|workspace| workspace.id.0);
workspaces
}
fn managed_window_snapshots(&self) -> Vec<ManagedWindowSnapshot> {
let mut snapshots = self
.window_ids
@@ -212,7 +300,7 @@ impl Ferese {
};
if !succeeded {
send_request_failed(shell, request, id);
send_request_failed(shell, request, id.0);
}
}
@@ -242,26 +330,40 @@ struct ManagedWindowSnapshot {
state: ferese_shell_v1::WindowState,
}
struct OutputSnapshot {
id: OutputId,
name: String,
active_workspace: WorkspaceId,
focused: bool,
}
struct WorkspaceSnapshot {
id: WorkspaceId,
output: Option<OutputId>,
name: String,
active: bool,
}
fn split_id(id: u64) -> (u32, u32) {
((id >> 32) as u32, id as u32)
}
fn join_id(hi: u32, lo: u32) -> WindowId {
WindowId((u64::from(hi) << 32) | u64::from(lo))
WindowId(join_u64(hi, lo))
}
fn send_request_failed(
shell: &FereseShellV1,
request: ferese_shell_v1::FailedRequest,
id: WindowId,
) {
let (window_hi, window_lo) = split_id(id.0);
fn join_u64(hi: u32, lo: u32) -> u64 {
(u64::from(hi) << 32) | u64::from(lo)
}
fn send_request_failed(shell: &FereseShellV1, request: ferese_shell_v1::FailedRequest, id: u64) {
let (object_hi, object_lo) = split_id(id);
shell.request_failed(
request,
window_hi,
window_lo,
"unknown or unavailable managed window".to_owned(),
object_hi,
object_lo,
"unknown or unavailable shell object".to_owned(),
);
}
+38 -5
View File
@@ -114,7 +114,7 @@ pub struct Ferese {
pub space: Space<Window>,
pub workspaces: WorkspaceSet,
pub output_workspaces: OutputWorkspaceMap,
output_ids: HashMap<Output, OutputId>,
pub(crate) output_ids: HashMap<Output, OutputId>,
output_identity_ids: HashMap<String, OutputId>,
pub window_ids: HashMap<Window, WindowId>,
pub window_geometry: HashMap<WindowId, WindowGeometry>,
@@ -239,18 +239,20 @@ impl Ferese {
geometry.size.h,
);
match self
let registration = self
.output_workspaces
.connect(output_id, geometry, fallback_workspace)
{
.connect(output_id, geometry, fallback_workspace);
match registration {
Ok(workspace) => {
self.output_ids.insert(output.clone(), output_id);
if self.output_workspaces.focused_output() == Some(output_id) {
self.activate_output_workspace(output_id, workspace);
}
self.send_shell_snapshots();
}
Err(error) => {
tracing::error!(%error, output = %output.name(), "failed to register output")
tracing::error!(%error, output = %output.name(), "failed to register output");
}
}
}
@@ -1665,6 +1667,37 @@ impl Ferese {
true
}
pub(crate) fn activate_managed_workspace(&mut self, workspace: WorkspaceId) -> bool {
if self.workspaces.workspace(workspace).is_none() {
return false;
}
let output = self
.output_workspaces
.output_for_workspace(workspace)
.or_else(|| self.output_workspaces.focused_output());
let Some(output) = output else {
return false;
};
if self
.output_workspaces
.output_for_workspace(workspace)
.is_none()
&& self
.output_workspaces
.assign_workspace(output, workspace)
.is_err()
{
return false;
}
self.activate_output_workspace(output, workspace);
self.relayout();
self.restore_keyboard_focus();
true
}
pub(crate) fn closing_visual(&self, id: WindowId) -> (f64, f32) {
let Some(animation) = self.closing_windows.get(&id) else {
return (1.0, 1.0);