feat: expose shell workspace metadata
This commit is contained in:
1 parent
3384cfde0f
commit
0c6fefacc1
5 files changed
+204
-27
No files matched your search
+114
-12
@@ -1,3 +1,4 @@
|
|||||||
|
use ferese_core::{OutputId, WorkspaceId};
|
||||||
use ferese_layout::WindowId;
|
use ferese_layout::WindowId;
|
||||||
use ferese_protocols::shell::v1::server::{
|
use ferese_protocols::shell::v1::server::{
|
||||||
ferese_shell_manager_v1::{self, FereseShellManagerV1},
|
ferese_shell_manager_v1::{self, FereseShellManagerV1},
|
||||||
@@ -91,6 +92,20 @@ impl Dispatch<FereseShellV1, ()> for Ferese {
|
|||||||
join_id(window_hi, window_lo),
|
join_id(window_hi, window_lo),
|
||||||
ShellWindowAction::Close,
|
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::EnterOverview => state.set_overview_active(true),
|
||||||
ferese_shell_v1::Request::ExitOverview => state.set_overview_active(false),
|
ferese_shell_v1::Request::ExitOverview => state.set_overview_active(false),
|
||||||
ferese_shell_v1::Request::SelectOverviewWindow {
|
ferese_shell_v1::Request::SelectOverviewWindow {
|
||||||
@@ -103,7 +118,7 @@ impl Dispatch<FereseShellV1, ()> for Ferese {
|
|||||||
send_request_failed(
|
send_request_failed(
|
||||||
shell,
|
shell,
|
||||||
ferese_shell_v1::FailedRequest::SelectOverviewWindow,
|
ferese_shell_v1::FailedRequest::SelectOverviewWindow,
|
||||||
id,
|
id.0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,9 +143,40 @@ impl Ferese {
|
|||||||
fn send_shell_snapshot(&mut self, shell: &FereseShellV1) {
|
fn send_shell_snapshot(&mut self, shell: &FereseShellV1) {
|
||||||
self.shell_snapshot_serial = self.shell_snapshot_serial.wrapping_add(1);
|
self.shell_snapshot_serial = self.shell_snapshot_serial.wrapping_add(1);
|
||||||
let serial = self.shell_snapshot_serial;
|
let serial = self.shell_snapshot_serial;
|
||||||
|
let outputs = self.output_snapshots();
|
||||||
|
let workspaces = self.workspace_snapshots();
|
||||||
let snapshots = self.managed_window_snapshots();
|
let snapshots = self.managed_window_snapshots();
|
||||||
|
|
||||||
shell.snapshot_begin(serial);
|
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 {
|
for snapshot in snapshots {
|
||||||
let (window_hi, window_lo) = split_id(snapshot.id.0);
|
let (window_hi, window_lo) = split_id(snapshot.id.0);
|
||||||
let (workspace_hi, workspace_lo) = split_id(snapshot.workspace);
|
let (workspace_hi, workspace_lo) = split_id(snapshot.workspace);
|
||||||
@@ -148,6 +194,48 @@ impl Ferese {
|
|||||||
shell.snapshot_end(serial);
|
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> {
|
fn managed_window_snapshots(&self) -> Vec<ManagedWindowSnapshot> {
|
||||||
let mut snapshots = self
|
let mut snapshots = self
|
||||||
.window_ids
|
.window_ids
|
||||||
@@ -212,7 +300,7 @@ impl Ferese {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !succeeded {
|
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,
|
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) {
|
fn split_id(id: u64) -> (u32, u32) {
|
||||||
((id >> 32) as u32, id as u32)
|
((id >> 32) as u32, id as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join_id(hi: u32, lo: u32) -> WindowId {
|
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(
|
fn join_u64(hi: u32, lo: u32) -> u64 {
|
||||||
shell: &FereseShellV1,
|
(u64::from(hi) << 32) | u64::from(lo)
|
||||||
request: ferese_shell_v1::FailedRequest,
|
}
|
||||||
id: WindowId,
|
|
||||||
) {
|
fn send_request_failed(shell: &FereseShellV1, request: ferese_shell_v1::FailedRequest, id: u64) {
|
||||||
let (window_hi, window_lo) = split_id(id.0);
|
let (object_hi, object_lo) = split_id(id);
|
||||||
|
|
||||||
shell.request_failed(
|
shell.request_failed(
|
||||||
request,
|
request,
|
||||||
window_hi,
|
object_hi,
|
||||||
window_lo,
|
object_lo,
|
||||||
"unknown or unavailable managed window".to_owned(),
|
"unknown or unavailable shell object".to_owned(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+38
-5
@@ -114,7 +114,7 @@ pub struct Ferese {
|
|||||||
pub space: Space<Window>,
|
pub space: Space<Window>,
|
||||||
pub workspaces: WorkspaceSet,
|
pub workspaces: WorkspaceSet,
|
||||||
pub output_workspaces: OutputWorkspaceMap,
|
pub output_workspaces: OutputWorkspaceMap,
|
||||||
output_ids: HashMap<Output, OutputId>,
|
pub(crate) output_ids: HashMap<Output, OutputId>,
|
||||||
output_identity_ids: HashMap<String, OutputId>,
|
output_identity_ids: HashMap<String, OutputId>,
|
||||||
pub window_ids: HashMap<Window, WindowId>,
|
pub window_ids: HashMap<Window, WindowId>,
|
||||||
pub window_geometry: HashMap<WindowId, WindowGeometry>,
|
pub window_geometry: HashMap<WindowId, WindowGeometry>,
|
||||||
@@ -239,18 +239,20 @@ impl Ferese {
|
|||||||
geometry.size.h,
|
geometry.size.h,
|
||||||
);
|
);
|
||||||
|
|
||||||
match self
|
let registration = self
|
||||||
.output_workspaces
|
.output_workspaces
|
||||||
.connect(output_id, geometry, fallback_workspace)
|
.connect(output_id, geometry, fallback_workspace);
|
||||||
{
|
|
||||||
|
match registration {
|
||||||
Ok(workspace) => {
|
Ok(workspace) => {
|
||||||
self.output_ids.insert(output.clone(), output_id);
|
self.output_ids.insert(output.clone(), output_id);
|
||||||
if self.output_workspaces.focused_output() == Some(output_id) {
|
if self.output_workspaces.focused_output() == Some(output_id) {
|
||||||
self.activate_output_workspace(output_id, workspace);
|
self.activate_output_workspace(output_id, workspace);
|
||||||
}
|
}
|
||||||
|
self.send_shell_snapshots();
|
||||||
}
|
}
|
||||||
Err(error) => {
|
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
|
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) {
|
pub(crate) fn closing_visual(&self, id: WindowId) -> (f64, f32) {
|
||||||
let Some(animation) = self.closing_windows.get(&id) else {
|
let Some(animation) = self.closing_windows.get(&id) else {
|
||||||
return (1.0, 1.0);
|
return (1.0, 1.0);
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
fn main() {
|
||||||
|
println!("cargo::rerun-if-changed=../../protocols/ferese-effects-v1.xml");
|
||||||
|
println!("cargo::rerun-if-changed=../../protocols/ferese-shell-v1.xml");
|
||||||
|
}
|
||||||
@@ -37,9 +37,9 @@
|
|||||||
|
|
||||||
<interface name="ferese_shell_v1" version="1">
|
<interface name="ferese_shell_v1" version="1">
|
||||||
<description summary="managed-window metadata and shell control">
|
<description summary="managed-window metadata and shell control">
|
||||||
Window and workspace identifiers are unsigned 64-bit values encoded as
|
Window, workspace, and output identifiers are unsigned 64-bit values
|
||||||
high and low 32-bit words. Each snapshot atomically replaces the prior
|
encoded as high and low 32-bit words. Each snapshot atomically replaces
|
||||||
managed-window view after snapshot_end.
|
the prior shell view after snapshot_end.
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
<request name="activate_window">
|
<request name="activate_window">
|
||||||
@@ -52,6 +52,11 @@
|
|||||||
<arg name="window_lo" type="uint"/>
|
<arg name="window_lo" type="uint"/>
|
||||||
</request>
|
</request>
|
||||||
|
|
||||||
|
<request name="activate_workspace">
|
||||||
|
<arg name="workspace_hi" type="uint"/>
|
||||||
|
<arg name="workspace_lo" type="uint"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
<request name="enter_overview"/>
|
<request name="enter_overview"/>
|
||||||
<request name="exit_overview"/>
|
<request name="exit_overview"/>
|
||||||
|
|
||||||
@@ -73,6 +78,7 @@
|
|||||||
<entry name="activate_window" value="0"/>
|
<entry name="activate_window" value="0"/>
|
||||||
<entry name="close_window" value="1"/>
|
<entry name="close_window" value="1"/>
|
||||||
<entry name="select_overview_window" value="2"/>
|
<entry name="select_overview_window" value="2"/>
|
||||||
|
<entry name="activate_workspace" value="3"/>
|
||||||
</enum>
|
</enum>
|
||||||
|
|
||||||
<event name="snapshot_begin">
|
<event name="snapshot_begin">
|
||||||
@@ -89,6 +95,24 @@
|
|||||||
<arg name="state" type="uint" enum="window_state"/>
|
<arg name="state" type="uint" enum="window_state"/>
|
||||||
</event>
|
</event>
|
||||||
|
|
||||||
|
<event name="output">
|
||||||
|
<arg name="output_hi" type="uint"/>
|
||||||
|
<arg name="output_lo" type="uint"/>
|
||||||
|
<arg name="name" type="string"/>
|
||||||
|
<arg name="active_workspace_hi" type="uint"/>
|
||||||
|
<arg name="active_workspace_lo" type="uint"/>
|
||||||
|
<arg name="focused" type="uint" summary="one when this is the focused output"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="workspace">
|
||||||
|
<arg name="workspace_hi" type="uint"/>
|
||||||
|
<arg name="workspace_lo" type="uint"/>
|
||||||
|
<arg name="output_hi" type="uint" summary="zero when the workspace has no output"/>
|
||||||
|
<arg name="output_lo" type="uint" summary="zero when the workspace has no output"/>
|
||||||
|
<arg name="name" type="string"/>
|
||||||
|
<arg name="active" type="uint" summary="one when visible on its output"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
<event name="snapshot_end">
|
<event name="snapshot_end">
|
||||||
<arg name="serial" type="uint"/>
|
<arg name="serial" type="uint"/>
|
||||||
</event>
|
</event>
|
||||||
@@ -99,8 +123,8 @@
|
|||||||
|
|
||||||
<event name="request_failed">
|
<event name="request_failed">
|
||||||
<arg name="request" type="uint" enum="failed_request"/>
|
<arg name="request" type="uint" enum="failed_request"/>
|
||||||
<arg name="window_hi" type="uint"/>
|
<arg name="object_hi" type="uint"/>
|
||||||
<arg name="window_lo" type="uint"/>
|
<arg name="object_lo" type="uint"/>
|
||||||
<arg name="reason" type="string"/>
|
<arg name="reason" type="string"/>
|
||||||
</event>
|
</event>
|
||||||
|
|
||||||
|
|||||||
@@ -53,8 +53,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"PASS shell-control snapshot={} windows={} overview round-trip and stale-id rejection",
|
"PASS shell-control snapshot={} outputs={} workspaces={} windows={} overview round-trip and stale-id rejection",
|
||||||
state.snapshot_serial.unwrap_or_default(),
|
state.snapshot_serial.unwrap_or_default(),
|
||||||
|
state.output_count,
|
||||||
|
state.workspace_count,
|
||||||
state.window_count
|
state.window_count
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -68,6 +70,8 @@ struct ProbeState {
|
|||||||
snapshot_serial: Option<u32>,
|
snapshot_serial: Option<u32>,
|
||||||
snapshot_complete: bool,
|
snapshot_complete: bool,
|
||||||
window_count: usize,
|
window_count: usize,
|
||||||
|
output_count: usize,
|
||||||
|
workspace_count: usize,
|
||||||
overview_active: Option<bool>,
|
overview_active: Option<bool>,
|
||||||
overview_events: usize,
|
overview_events: usize,
|
||||||
invalid_window_rejected: bool,
|
invalid_window_rejected: bool,
|
||||||
@@ -77,6 +81,8 @@ impl ProbeState {
|
|||||||
fn initialized(&self) -> bool {
|
fn initialized(&self) -> bool {
|
||||||
self.capabilities == Some(1)
|
self.capabilities == Some(1)
|
||||||
&& self.snapshot_complete
|
&& self.snapshot_complete
|
||||||
|
&& self.output_count > 0
|
||||||
|
&& self.workspace_count > 0
|
||||||
&& self.overview_active == Some(false)
|
&& self.overview_active == Some(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,10 +128,18 @@ impl Dispatch<FereseShellV1, ()> for ProbeState {
|
|||||||
state.snapshot_serial = Some(serial);
|
state.snapshot_serial = Some(serial);
|
||||||
state.snapshot_complete = false;
|
state.snapshot_complete = false;
|
||||||
state.window_count = 0;
|
state.window_count = 0;
|
||||||
|
state.output_count = 0;
|
||||||
|
state.workspace_count = 0;
|
||||||
}
|
}
|
||||||
ferese_shell_v1::Event::Window { .. } => {
|
ferese_shell_v1::Event::Window { .. } => {
|
||||||
state.window_count += 1;
|
state.window_count += 1;
|
||||||
}
|
}
|
||||||
|
ferese_shell_v1::Event::Output { .. } => {
|
||||||
|
state.output_count += 1;
|
||||||
|
}
|
||||||
|
ferese_shell_v1::Event::Workspace { .. } => {
|
||||||
|
state.workspace_count += 1;
|
||||||
|
}
|
||||||
ferese_shell_v1::Event::SnapshotEnd { serial } => {
|
ferese_shell_v1::Event::SnapshotEnd { serial } => {
|
||||||
state.snapshot_complete = state.snapshot_serial == Some(serial);
|
state.snapshot_complete = state.snapshot_serial == Some(serial);
|
||||||
}
|
}
|
||||||
@@ -135,14 +149,14 @@ impl Dispatch<FereseShellV1, ()> for ProbeState {
|
|||||||
}
|
}
|
||||||
ferese_shell_v1::Event::RequestFailed {
|
ferese_shell_v1::Event::RequestFailed {
|
||||||
request,
|
request,
|
||||||
window_hi,
|
object_hi,
|
||||||
window_lo,
|
object_lo,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
state.invalid_window_rejected = request
|
state.invalid_window_rejected = request
|
||||||
== WEnum::Value(ferese_shell_v1::FailedRequest::ActivateWindow)
|
== WEnum::Value(ferese_shell_v1::FailedRequest::ActivateWindow)
|
||||||
&& window_hi == u32::MAX
|
&& object_hi == u32::MAX
|
||||||
&& window_lo == u32::MAX;
|
&& object_lo == u32::MAX;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user