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.
action: OutputAction,
},
/// Request information about workspaces.
Workspaces,
/// Respond with an error (for testing error handling).
ReturnError,
}
@@ -60,6 +62,8 @@ pub enum Response {
FocusedWindow(Option<Window>),
/// Output configuration change result.
OutputConfigChanged(OutputConfigChanged),
/// Information about workspaces.
Workspaces(Vec<Workspace>),
}
/// Actions that niri can perform.
@@ -484,6 +488,23 @@ pub enum OutputConfigChanged {
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 {
type Err = &'static str;
+2
View File
@@ -78,6 +78,8 @@ pub enum Msg {
#[command(subcommand)]
action: OutputAction,
},
/// List workspaces.
Workspaces,
/// Request an error from the running niri instance.
RequestError,
}
+49
View File
@@ -17,6 +17,7 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
output: output.clone(),
action: action.clone(),
},
Msg::Workspaces => Request::Workspaces,
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.");
}
}
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(())
+10
View File
@@ -198,6 +198,16 @@ async fn process(ctx: &ClientCtx, request: Request) -> Reply {
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)
+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> {
+5
View File
@@ -12,6 +12,7 @@ use _server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as
use anyhow::{ensure, Context};
use calloop::futures::Scheduler;
use niri_config::{Config, Key, Modifiers, PreviewRender, TrackLayout, WorkspaceReference};
use niri_ipc::Workspace;
use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::damage::OutputDamageTracker;
use smithay::backend::renderer::element::memory::MemoryRenderBufferRenderElement;
@@ -3934,6 +3935,10 @@ impl Niri {
self.queue_redraw_all();
}
}
pub fn ipc_workspaces(&self) -> Vec<Workspace> {
self.layout.ipc_workspaces()
}
}
pub struct ClientState {