Implement niri msg workspaces

This commit is contained in:
rustysec
2024-05-16 14:30:52 -07:00
committed by Ivan Molodetskikh
parent a2f74c9bff
commit 36d3e70f11
6 changed files with 122 additions and 0 deletions
+21
View File
@@ -31,6 +31,8 @@ pub enum Request {
/// Configuration to apply. /// Configuration to apply.
action: OutputAction, action: OutputAction,
}, },
/// Request information about workspaces.
Workspaces,
/// Respond with an error (for testing error handling). /// Respond with an error (for testing error handling).
ReturnError, ReturnError,
} }
@@ -60,6 +62,8 @@ pub enum Response {
FocusedWindow(Option<Window>), FocusedWindow(Option<Window>),
/// Output configuration change result. /// Output configuration change result.
OutputConfigChanged(OutputConfigChanged), OutputConfigChanged(OutputConfigChanged),
/// Information about workspaces.
Workspaces(Vec<Workspace>),
} }
/// Actions that niri can perform. /// Actions that niri can perform.
@@ -484,6 +488,23 @@ pub enum OutputConfigChanged {
OutputWasMissing, OutputWasMissing,
} }
/// A workspace.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Workspace {
/// Index of the workspace on its monitor.
///
/// This is the same index you can use for requests like `niri msg action focus-workspace`.
pub idx: u8,
/// Optional name of the workspace.
pub name: Option<String>,
/// Name of the output that the workspace is on.
///
/// Can be `None` if no outputs are currently connected.
pub output: Option<String>,
/// Whether the workspace is currently active on its output.
pub is_active: bool,
}
impl FromStr for WorkspaceReferenceArg { impl FromStr for WorkspaceReferenceArg {
type Err = &'static str; type Err = &'static str;
+2
View File
@@ -78,6 +78,8 @@ pub enum Msg {
#[command(subcommand)] #[command(subcommand)]
action: OutputAction, action: OutputAction,
}, },
/// List workspaces.
Workspaces,
/// Request an error from the running niri instance. /// Request an error from the running niri instance.
RequestError, RequestError,
} }
+49
View File
@@ -17,6 +17,7 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
output: output.clone(), output: output.clone(),
action: action.clone(), action: action.clone(),
}, },
Msg::Workspaces => Request::Workspaces,
Msg::RequestError => Request::ReturnError, Msg::RequestError => Request::ReturnError,
}; };
@@ -260,6 +261,54 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
println!("The change will apply when it is connected."); println!("The change will apply when it is connected.");
} }
} }
Msg::Workspaces => {
let Response::Workspaces(mut response) = response else {
bail!("unexpected response: expected Workspaces, got {response:?}");
};
if json {
let response =
serde_json::to_string(&response).context("error formatting response")?;
println!("{response}");
return Ok(());
}
if response.is_empty() {
println!("No workspaces.");
return Ok(());
}
response.sort_by_key(|ws| ws.idx);
response.sort_by(|a, b| a.output.cmp(&b.output));
let mut current_output = if let Some(output) = response[0].output.as_deref() {
println!("Output \"{output}\":");
Some(output)
} else {
println!("No output:");
None
};
for ws in &response {
if ws.output.as_deref() != current_output {
let output = ws.output.as_deref().context(
"invalid response: workspace with no output \
following a workspace with an output",
)?;
current_output = Some(output);
println!("\nOutput \"{output}\":");
}
let is_active = if ws.is_active { " * " } else { " " };
let idx = ws.idx;
let name = if let Some(name) = ws.name.as_deref() {
format!(" \"{name}\"")
} else {
String::new()
};
println!("{is_active}{idx}{name}");
}
}
} }
Ok(()) Ok(())
+10
View File
@@ -198,6 +198,16 @@ async fn process(ctx: &ClientCtx, request: Request) -> Reply {
Response::OutputConfigChanged(response) Response::OutputConfigChanged(response)
} }
Request::Workspaces => {
let (tx, rx) = async_channel::bounded(1);
ctx.event_loop.insert_idle(move |state| {
let workspaces = state.niri.layout.ipc_workspaces();
let _ = tx.send_blocking(workspaces);
});
let result = rx.recv().await;
let workspaces = result.map_err(|_| String::from("error getting workspace info"))?;
Response::Workspaces(workspaces)
}
}; };
Ok(response) Ok(response)
+35
View File
@@ -2278,6 +2278,41 @@ impl<W: LayoutElement> Layout<W> {
} }
} }
} }
pub fn ipc_workspaces(&self) -> Vec<niri_ipc::Workspace> {
match &self.monitor_set {
MonitorSet::Normal {
monitors,
primary_idx: _,
active_monitor_idx: _,
} => {
let mut workspaces = Vec::new();
for monitor in monitors {
for (idx, workspace) in monitor.workspaces.iter().enumerate() {
workspaces.push(niri_ipc::Workspace {
idx: u8::try_from(idx + 1).unwrap_or(u8::MAX),
name: workspace.name.clone(),
output: Some(monitor.output.name()),
is_active: monitor.active_workspace_idx == idx,
})
}
}
workspaces
}
MonitorSet::NoOutputs { workspaces } => workspaces
.iter()
.enumerate()
.map(|(idx, ws)| niri_ipc::Workspace {
idx: u8::try_from(idx + 1).unwrap_or(u8::MAX),
name: ws.name.clone(),
output: None,
is_active: false,
})
.collect(),
}
}
} }
impl<W: LayoutElement> Default for MonitorSet<W> { impl<W: LayoutElement> Default for MonitorSet<W> {
+5
View File
@@ -12,6 +12,7 @@ use _server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as
use anyhow::{ensure, Context}; use anyhow::{ensure, Context};
use calloop::futures::Scheduler; use calloop::futures::Scheduler;
use niri_config::{Config, Key, Modifiers, PreviewRender, TrackLayout, WorkspaceReference}; use niri_config::{Config, Key, Modifiers, PreviewRender, TrackLayout, WorkspaceReference};
use niri_ipc::Workspace;
use smithay::backend::allocator::Fourcc; use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::damage::OutputDamageTracker; use smithay::backend::renderer::damage::OutputDamageTracker;
use smithay::backend::renderer::element::memory::MemoryRenderBufferRenderElement; use smithay::backend::renderer::element::memory::MemoryRenderBufferRenderElement;
@@ -3934,6 +3935,10 @@ impl Niri {
self.queue_redraw_all(); self.queue_redraw_all();
} }
} }
pub fn ipc_workspaces(&self) -> Vec<Workspace> {
self.layout.ipc_workspaces()
}
} }
pub struct ClientState { pub struct ClientState {