aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-02-10 09:33:32 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2024-02-10 09:40:32 +0400
commit93e16a6582f8e80ac4079ac527637861696ed7b1 (patch)
treeec557f5d2d8816516a0b4925ad41df1ed139007c
parent3486fa5536e0c69213b9e6fb4b58a1cf225d5959 (diff)
downloadniri-93e16a6582f8e80ac4079ac527637861696ed7b1.tar.gz
niri-93e16a6582f8e80ac4079ac527637861696ed7b1.tar.bz2
niri-93e16a6582f8e80ac4079ac527637861696ed7b1.zip
Implement niri msg action
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml5
-rw-r--r--niri-config/Cargo.toml1
-rw-r--r--niri-config/src/lib.rs138
-rw-r--r--niri-ipc/Cargo.toml4
-rw-r--r--niri-ipc/src/lib.rs241
-rw-r--r--resources/default-config.kdl3
-rw-r--r--src/cli.rs6
-rw-r--r--src/input.rs11
-rw-r--r--src/ipc/client.rs12
-rw-r--r--src/ipc/server.rs9
-rw-r--r--src/layout/mod.rs3
-rw-r--r--src/layout/monitor.rs2
-rw-r--r--src/layout/workspace.rs3
14 files changed, 366 insertions, 74 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 817c41bf..d00655e4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2093,6 +2093,7 @@ dependencies = [
"bitflags 2.4.2",
"knuffel",
"miette",
+ "niri-ipc",
"smithay",
"tracing",
"tracy-client",
@@ -2102,6 +2103,7 @@ dependencies = [
name = "niri-ipc"
version = "0.1.1"
dependencies = [
+ "clap",
"serde",
]
diff --git a/Cargo.toml b/Cargo.toml
index 9ebd4b01..a3fcbda4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,6 +12,7 @@ repository = "https://github.com/YaLTeR/niri"
[workspace.dependencies]
anyhow = "1.0.79"
bitflags = "2.4.2"
+clap = { version = "4.4.18", features = ["derive"] }
serde = { version = "1.0.196", features = ["derive"] }
tracing = { version = "0.1.40", features = ["max_level_trace", "release_max_level_debug"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
@@ -45,7 +46,7 @@ async-channel = { version = "2.1.1", optional = true }
async-io = { version = "1.13.0", optional = true }
bitflags = "2.4.2"
calloop = { version = "0.12.4", features = ["executor", "futures-io"] }
-clap = { version = "4.4.18", features = ["derive", "string"] }
+clap = { workspace = true, features = ["string"] }
directories = "5.0.1"
futures-util = { version = "0.3.30", default-features = false, features = ["std", "io"] }
git-version = "0.3.9"
@@ -55,7 +56,7 @@ libc = "0.2.153"
log = { version = "0.4.20", features = ["max_level_trace", "release_max_level_debug"] }
logind-zbus = { version = "3.1.2", optional = true }
niri-config = { version = "0.1.1", path = "niri-config" }
-niri-ipc = { version = "0.1.1", path = "niri-ipc" }
+niri-ipc = { version = "0.1.1", path = "niri-ipc", features = ["clap"] }
notify-rust = { version = "4.10.0", optional = true }
pangocairo = "0.19.1"
pipewire = { version = "0.7.2", optional = true }
diff --git a/niri-config/Cargo.toml b/niri-config/Cargo.toml
index 4123cb07..8bff3a30 100644
--- a/niri-config/Cargo.toml
+++ b/niri-config/Cargo.toml
@@ -11,6 +11,7 @@ repository.workspace = true
bitflags.workspace = true
knuffel = "3.2.0"
miette = "5.10.0"
+niri-ipc = { version = "0.1.1", path = "../niri-ipc" }
smithay = { workspace = true, features = ["backend_libinput"] }
tracing.workspace = true
tracy-client.workspace = true
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs
index 333a018a..f32ca741 100644
--- a/niri-config/src/lib.rs
+++ b/niri-config/src/lib.rs
@@ -6,6 +6,7 @@ use std::str::FromStr;
use bitflags::bitflags;
use miette::{miette, Context, IntoDiagnostic, NarratableReportHandler};
+use niri_ipc::{LayoutSwitchTarget, SizeChange};
use smithay::input::keyboard::keysyms::KEY_NoSymbol;
use smithay::input::keyboard::xkb::{keysym_from_name, KEYSYM_CASE_INSENSITIVE};
use smithay::input::keyboard::{Keysym, XkbConfig};
@@ -512,6 +513,7 @@ bitflags! {
}
}
+// Remember to add new actions to the CLI enum too.
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
pub enum Action {
Quit,
@@ -578,7 +580,7 @@ pub enum Action {
SwitchPresetColumnWidth,
MaximizeColumn,
SetColumnWidth(#[knuffel(argument, str)] SizeChange),
- SwitchLayout(#[knuffel(argument)] LayoutAction),
+ SwitchLayout(#[knuffel(argument, str)] LayoutSwitchTarget),
ShowHotkeyOverlay,
MoveWorkspaceToMonitorLeft,
MoveWorkspaceToMonitorRight,
@@ -586,18 +588,76 @@ pub enum Action {
MoveWorkspaceToMonitorUp,
}
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum SizeChange {
- SetFixed(i32),
- SetProportion(f64),
- AdjustFixed(i32),
- AdjustProportion(f64),
-}
-
-#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq)]
-pub enum LayoutAction {
- Next,
- Prev,
+impl From<niri_ipc::Action> for Action {
+ fn from(value: niri_ipc::Action) -> Self {
+ match value {
+ niri_ipc::Action::Quit => Self::Quit,
+ niri_ipc::Action::PowerOffMonitors => Self::PowerOffMonitors,
+ niri_ipc::Action::Spawn { command } => Self::Spawn(command),
+ niri_ipc::Action::Screenshot => Self::Screenshot,
+ niri_ipc::Action::ScreenshotScreen => Self::ScreenshotScreen,
+ niri_ipc::Action::ScreenshotWindow => Self::ScreenshotWindow,
+ niri_ipc::Action::CloseWindow => Self::CloseWindow,
+ niri_ipc::Action::FullscreenWindow => Self::FullscreenWindow,
+ niri_ipc::Action::FocusColumnLeft => Self::FocusColumnLeft,
+ niri_ipc::Action::FocusColumnRight => Self::FocusColumnRight,
+ niri_ipc::Action::FocusColumnFirst => Self::FocusColumnFirst,
+ niri_ipc::Action::FocusColumnLast => Self::FocusColumnLast,
+ niri_ipc::Action::FocusWindowDown => Self::FocusWindowDown,
+ niri_ipc::Action::FocusWindowUp => Self::FocusWindowUp,
+ niri_ipc::Action::FocusWindowOrWorkspaceDown => Self::FocusWindowOrWorkspaceDown,
+ niri_ipc::Action::FocusWindowOrWorkspaceUp => Self::FocusWindowOrWorkspaceUp,
+ niri_ipc::Action::MoveColumnLeft => Self::MoveColumnLeft,
+ niri_ipc::Action::MoveColumnRight => Self::MoveColumnRight,
+ niri_ipc::Action::MoveColumnToFirst => Self::MoveColumnToFirst,
+ niri_ipc::Action::MoveColumnToLast => Self::MoveColumnToLast,
+ niri_ipc::Action::MoveWindowDown => Self::MoveWindowDown,
+ niri_ipc::Action::MoveWindowUp => Self::MoveWindowUp,
+ niri_ipc::Action::MoveWindowDownOrToWorkspaceDown => {
+ Self::MoveWindowDownOrToWorkspaceDown
+ }
+ niri_ipc::Action::MoveWindowUpOrToWorkspaceUp => Self::MoveWindowUpOrToWorkspaceUp,
+ niri_ipc::Action::ConsumeOrExpelWindowLeft => Self::ConsumeOrExpelWindowLeft,
+ niri_ipc::Action::ConsumeOrExpelWindowRight => Self::ConsumeOrExpelWindowRight,
+ niri_ipc::Action::ConsumeWindowIntoColumn => Self::ConsumeWindowIntoColumn,
+ niri_ipc::Action::ExpelWindowFromColumn => Self::ExpelWindowFromColumn,
+ niri_ipc::Action::CenterColumn => Self::CenterColumn,
+ niri_ipc::Action::FocusWorkspaceDown => Self::FocusWorkspaceDown,
+ niri_ipc::Action::FocusWorkspaceUp => Self::FocusWorkspaceUp,
+ niri_ipc::Action::FocusWorkspace { index } => Self::FocusWorkspace(index),
+ niri_ipc::Action::MoveWindowToWorkspaceDown => Self::MoveWindowToWorkspaceDown,
+ niri_ipc::Action::MoveWindowToWorkspaceUp => Self::MoveWindowToWorkspaceUp,
+ niri_ipc::Action::MoveWindowToWorkspace { index } => Self::MoveWindowToWorkspace(index),
+ niri_ipc::Action::MoveColumnToWorkspaceDown => Self::MoveColumnToWorkspaceDown,
+ niri_ipc::Action::MoveColumnToWorkspaceUp => Self::MoveColumnToWorkspaceUp,
+ niri_ipc::Action::MoveColumnToWorkspace { index } => Self::MoveColumnToWorkspace(index),
+ niri_ipc::Action::MoveWorkspaceDown => Self::MoveWorkspaceDown,
+ niri_ipc::Action::MoveWorkspaceUp => Self::MoveWorkspaceUp,
+ niri_ipc::Action::FocusMonitorLeft => Self::FocusMonitorLeft,
+ niri_ipc::Action::FocusMonitorRight => Self::FocusMonitorRight,
+ niri_ipc::Action::FocusMonitorDown => Self::FocusMonitorDown,
+ niri_ipc::Action::FocusMonitorUp => Self::FocusMonitorUp,
+ niri_ipc::Action::MoveWindowToMonitorLeft => Self::MoveWindowToMonitorLeft,
+ niri_ipc::Action::MoveWindowToMonitorRight => Self::MoveWindowToMonitorRight,
+ niri_ipc::Action::MoveWindowToMonitorDown => Self::MoveWindowToMonitorDown,
+ niri_ipc::Action::MoveWindowToMonitorUp => Self::MoveWindowToMonitorUp,
+ niri_ipc::Action::MoveColumnToMonitorLeft => Self::MoveColumnToMonitorLeft,
+ niri_ipc::Action::MoveColumnToMonitorRight => Self::MoveColumnToMonitorRight,
+ niri_ipc::Action::MoveColumnToMonitorDown => Self::MoveColumnToMonitorDown,
+ niri_ipc::Action::MoveColumnToMonitorUp => Self::MoveColumnToMonitorUp,
+ niri_ipc::Action::SetWindowHeight { change } => Self::SetWindowHeight(change),
+ niri_ipc::Action::SwitchPresetColumnWidth => Self::SwitchPresetColumnWidth,
+ niri_ipc::Action::MaximizeColumn => Self::MaximizeColumn,
+ niri_ipc::Action::SetColumnWidth { change } => Self::SetColumnWidth(change),
+ niri_ipc::Action::SwitchLayout { layout } => Self::SwitchLayout(layout),
+ niri_ipc::Action::ShowHotkeyOverlay => Self::ShowHotkeyOverlay,
+ niri_ipc::Action::MoveWorkspaceToMonitorLeft => Self::MoveWorkspaceToMonitorLeft,
+ niri_ipc::Action::MoveWorkspaceToMonitorRight => Self::MoveWorkspaceToMonitorRight,
+ niri_ipc::Action::MoveWorkspaceToMonitorDown => Self::MoveWorkspaceToMonitorDown,
+ niri_ipc::Action::MoveWorkspaceToMonitorUp => Self::MoveWorkspaceToMonitorUp,
+ niri_ipc::Action::ToggleDebugTint => Self::ToggleDebugTint,
+ }
+ }
}
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
@@ -718,58 +778,6 @@ impl FromStr for Key {
}
}
-impl FromStr for SizeChange {
- type Err = miette::Error;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- match s.split_once('%') {
- Some((value, empty)) => {
- if !empty.is_empty() {
- return Err(miette!("trailing characters after '%' are not allowed"));
- }
-
- match value.bytes().next() {
- Some(b'-' | b'+') => {
- let value = value
- .parse()
- .into_diagnostic()
- .context("error parsing value")?;
- Ok(Self::AdjustProportion(value))
- }
- Some(_) => {
- let value = value
- .parse()
- .into_diagnostic()
- .context("error parsing value")?;
- Ok(Self::SetProportion(value))
- }
- None => Err(miette!("value is missing")),
- }
- }
- None => {
- let value = s;
- match value.bytes().next() {
- Some(b'-' | b'+') => {
- let value = value
- .parse()
- .into_diagnostic()
- .context("error parsing value")?;
- Ok(Self::AdjustFixed(value))
- }
- Some(_) => {
- let value = value
- .parse()
- .into_diagnostic()
- .context("error parsing value")?;
- Ok(Self::SetFixed(value))
- }
- None => Err(miette!("value is missing")),
- }
- }
- }
- }
-}
-
impl FromStr for AccelProfile {
type Err = miette::Error;
diff --git a/niri-ipc/Cargo.toml b/niri-ipc/Cargo.toml
index 21207ac8..9bd2e833 100644
--- a/niri-ipc/Cargo.toml
+++ b/niri-ipc/Cargo.toml
@@ -8,4 +8,8 @@ edition.workspace = true
repository.workspace = true
[dependencies]
+clap = { workspace = true, optional = true }
serde.workspace = true
+
+[features]
+clap = ["dep:clap"]
diff --git a/niri-ipc/src/lib.rs b/niri-ipc/src/lib.rs
index 129e8f57..5131388f 100644
--- a/niri-ipc/src/lib.rs
+++ b/niri-ipc/src/lib.rs
@@ -2,6 +2,7 @@
#![warn(missing_docs)]
use std::collections::HashMap;
+use std::str::FromStr;
use serde::{Deserialize, Serialize};
@@ -13,6 +14,8 @@ pub const SOCKET_PATH_ENV: &str = "NIRI_SOCKET";
pub enum Request {
/// Request information about connected outputs.
Outputs,
+ /// Perform an action.
+ Action(Action),
}
/// Response from niri to client.
@@ -24,6 +27,192 @@ pub enum Response {
Outputs(HashMap<String, Output>),
}
+/// Actions that niri can perform.
+// Variants in this enum should match the spelling of the ones in niri-config. Most, but not all,
+// variants from niri-config should be present here.
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "clap", derive(clap::Parser))]
+#[cfg_attr(feature = "clap", command(subcommand_value_name = "ACTION"))]
+#[cfg_attr(feature = "clap", command(subcommand_help_heading = "Actions"))]
+pub enum Action {
+ /// Exit niri.
+ Quit,
+ /// Power off all monitors via DPMS.
+ PowerOffMonitors,
+ /// Spawn a command.
+ Spawn {
+ /// Command to spawn.
+ #[cfg_attr(feature = "clap", arg(last = true, required = true))]
+ command: Vec<String>,
+ },
+ /// Open the screenshot UI.
+ Screenshot,
+ /// Screenshot the focused screen.
+ ScreenshotScreen,
+ /// Screenshot the focused window.
+ ScreenshotWindow,
+ /// Close the focused window.
+ CloseWindow,
+ /// Toggle fullscreen on the focused window.
+ FullscreenWindow,
+ /// Focus the column to the left.
+ FocusColumnLeft,
+ /// Focus the column to the right.
+ FocusColumnRight,
+ /// Focus the first column.
+ FocusColumnFirst,
+ /// Focus the last column.
+ FocusColumnLast,
+ /// Focus the window below.
+ FocusWindowDown,
+ /// Focus the window above.
+ FocusWindowUp,
+ /// Focus the window or the workspace above.
+ FocusWindowOrWorkspaceDown,
+ /// Focus the window or the workspace above.
+ FocusWindowOrWorkspaceUp,
+ /// Move the focused column to the left.
+ MoveColumnLeft,
+ /// Move the focused column to the right.
+ MoveColumnRight,
+ /// Move the focused column to the start of the workspace.
+ MoveColumnToFirst,
+ /// Move the focused column to the end of the workspace.
+ MoveColumnToLast,
+ /// Move the focused window down in a column.
+ MoveWindowDown,
+ /// Move the focused window up in a column.
+ MoveWindowUp,
+ /// Move the focused window down in a column or to the workspace below.
+ MoveWindowDownOrToWorkspaceDown,
+ /// Move the focused window up in a column or to the workspace above.
+ MoveWindowUpOrToWorkspaceUp,
+ /// Consume or expel the focused window left.
+ ConsumeOrExpelWindowLeft,
+ /// Consume or expel the focused window right.
+ ConsumeOrExpelWindowRight,
+ /// Consume the window to the right into the focused column.
+ ConsumeWindowIntoColumn,
+ /// Expel the focused window from the column.
+ ExpelWindowFromColumn,
+ /// Center the focused column on the screen.
+ CenterColumn,
+ /// Focus the workspace below.
+ FocusWorkspaceDown,
+ /// Focus the workspace above.
+ FocusWorkspaceUp,
+ /// Focus a workspace by index.
+ FocusWorkspace {
+ /// Index of the workspace to focus.
+ #[cfg_attr(feature = "clap", arg())]
+ index: u8,
+ },
+ /// Move the focused window to the workspace below.
+ MoveWindowToWorkspaceDown,
+ /// Move the focused window to the workspace above.
+ MoveWindowToWorkspaceUp,
+ /// Move the focused window to a workspace by index.
+ MoveWindowToWorkspace {
+ /// Index of the target workspace.
+ #[cfg_attr(feature = "clap", arg())]
+ index: u8,
+ },
+ /// Move the focused column to the workspace below.
+ MoveColumnToWorkspaceDown,
+ /// Move the focused column to the workspace above.
+ MoveColumnToWorkspaceUp,
+ /// Move the focused column to a workspace by index.
+ MoveColumnToWorkspace {
+ /// Index of the target workspace.
+ #[cfg_attr(feature = "clap", arg())]
+ index: u8,
+ },
+ /// Move the focused workspace down.
+ MoveWorkspaceDown,
+ /// Move the focused workspace up.
+ MoveWorkspaceUp,
+ /// Focus the monitor to the left.
+ FocusMonitorLeft,
+ /// Focus the monitor to the right.
+ FocusMonitorRight,
+ /// Focus the monitor below.
+ FocusMonitorDown,
+ /// Focus the monitor above.
+ FocusMonitorUp,
+ /// Move the focused window to the monitor to the left.
+ MoveWindowToMonitorLeft,
+ /// Move the focused window to the monitor to the right.
+ MoveWindowToMonitorRight,
+ /// Move the focused window to the monitor below.
+ MoveWindowToMonitorDown,
+ /// Move the focused window to the monitor above.
+ MoveWindowToMonitorUp,
+ /// Move the focused column to the monitor to the left.
+ MoveColumnToMonitorLeft,
+ /// Move the focused column to the monitor to the right.
+ MoveColumnToMonitorRight,
+ /// Move the focused column to the monitor below.
+ MoveColumnToMonitorDown,
+ /// Move the focused column to the monitor above.
+ MoveColumnToMonitorUp,
+ /// Change the height of the focused window.
+ SetWindowHeight {
+ /// How to change the height.
+ #[cfg_attr(feature = "clap", arg())]
+ change: SizeChange,
+ },
+ /// Switch between preset column widths.
+ SwitchPresetColumnWidth,
+ /// Toggle the maximized state of the focused column.
+ MaximizeColumn,
+ /// Change the width of the focused column.
+ SetColumnWidth {
+ /// How to change the width.
+ #[cfg_attr(feature = "clap", arg())]
+ change: SizeChange,
+ },
+ /// Switch between keyboard layouts.
+ SwitchLayout {
+ /// Layout to switch to.
+ #[cfg_attr(feature = "clap", arg())]
+ layout: LayoutSwitchTarget,
+ },
+ /// Show the hotkey overlay.
+ ShowHotkeyOverlay,
+ /// Move the focused workspace to the monitor to the left.
+ MoveWorkspaceToMonitorLeft,
+ /// Move the focused workspace to the monitor to the right.
+ MoveWorkspaceToMonitorRight,
+ /// Move the focused workspace to the monitor below.
+ MoveWorkspaceToMonitorDown,
+ /// Move the focused workspace to the monitor above.
+ MoveWorkspaceToMonitorUp,
+ /// Toggle a debug tint on windows.
+ ToggleDebugTint,
+}
+
+/// Change in window or column size.
+#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
+pub enum SizeChange {
+ /// Set the size in logical pixels.
+ SetFixed(i32),
+ /// Set the size as a proportion of the working area.
+ SetProportion(f64),
+ /// Add or subtract to the current size in logical pixels.
+ AdjustFixed(i32),
+ /// Add or subtract to the current size as a proportion of the working area.
+ AdjustProportion(f64),
+}
+
+/// Layout to switch to.
+#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
+pub enum LayoutSwitchTarget {
+ /// The next configured layout.
+ Next,
+ /// The previous configured layout.
+ Prev,
+}
+
/// Connected output.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Output {
@@ -53,3 +242,55 @@ pub struct Mode {
/// Refresh rate in millihertz.
pub refresh_rate: u32,
}
+
+impl FromStr for SizeChange {
+ type Err = &'static str;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s.split_once('%') {
+ Some((value, empty)) => {
+ if !empty.is_empty() {
+ return Err("trailing characters after '%' are not allowed");
+ }
+
+ match value.bytes().next() {
+ Some(b'-' | b'+') => {
+ let value = value.parse().map_err(|_| "error parsing value")?;
+ Ok(Self::AdjustProportion(value))
+ }
+ Some(_) => {
+ let value = value.parse().map_err(|_| "error parsing value")?;
+ Ok(Self::SetProportion(value))
+ }
+ None => Err("value is missing"),
+ }
+ }
+ None => {
+ let value = s;
+ match value.bytes().next() {
+ Some(b'-' | b'+') => {
+ let value = value.parse().map_err(|_| "error parsing value")?;
+ Ok(Self::AdjustFixed(value))
+ }
+ Some(_) => {
+ let value = value.parse().map_err(|_| "error parsing value")?;
+ Ok(Self::SetFixed(value))
+ }
+ None => Err("value is missing"),
+ }
+ }
+ }
+ }
+}
+
+impl FromStr for LayoutSwitchTarget {
+ type Err = &'static str;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "next" => Ok(Self::Next),
+ "prev" => Ok(Self::Prev),
+ _ => Err(r#"invalid layout action, can be "next" or "prev""#),
+ }
+ }
+}
diff --git a/resources/default-config.kdl b/resources/default-config.kdl
index 7f89bec0..2cee1c62 100644
--- a/resources/default-config.kdl
+++ b/resources/default-config.kdl
@@ -251,6 +251,9 @@ binds {
//
// "Mod" is a special modifier equal to Super when running on a TTY, and to Alt
// when running as a winit window.
+ //
+ // Most actions that you can bind here can also be invoked programmatically with
+ // `niri msg action do-something`.
// Mod-Shift-/, which is usually the same as Mod-?,
// shows a list of important hotkeys.
diff --git a/src/cli.rs b/src/cli.rs
index 8dd8927d..2632f1ec 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -2,6 +2,7 @@ use std::ffi::OsString;
use std::path::PathBuf;
use clap::{Parser, Subcommand};
+use niri_ipc::Action;
use crate::utils::version;
@@ -46,4 +47,9 @@ pub enum Sub {
pub enum Msg {
/// List connected outputs.
Outputs,
+ /// Perform an action.
+ Action {
+ #[command(subcommand)]
+ action: Action,
+ },
}
diff --git a/src/input.rs b/src/input.rs
index 507e4479..0f586aba 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -1,7 +1,8 @@
use std::any::Any;
use std::collections::HashSet;
-use niri_config::{Action, Binds, LayoutAction, Modifiers};
+use niri_config::{Action, Binds, Modifiers};
+use niri_ipc::LayoutSwitchTarget;
use smithay::backend::input::{
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Device, DeviceCapability, Event,
GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _, GestureSwipeUpdateEvent as _,
@@ -273,6 +274,10 @@ impl State {
return;
}
+ self.do_action(action);
+ }
+
+ pub fn do_action(&mut self, action: Action) {
if self.niri.is_locked() && !allowed_when_locked(&action) {
return;
}
@@ -377,8 +382,8 @@ impl State {
self.niri.seat.get_keyboard().unwrap().with_xkb_state(
self,
|mut state| match action {
- LayoutAction::Next => state.cycle_next_layout(),
- LayoutAction::Prev => state.cycle_prev_layout(),
+ LayoutSwitchTarget::Next => state.cycle_next_layout(),
+ LayoutSwitchTarget::Prev => state.cycle_prev_layout(),
},
);
}
diff --git a/src/ipc/client.rs b/src/ipc/client.rs
index 7754187a..b2004a7b 100644
--- a/src/ipc/client.rs
+++ b/src/ipc/client.rs
@@ -19,8 +19,9 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
let mut stream =
UnixStream::connect(socket_path).context("error connecting to {socket_path}")?;
- let request = match msg {
+ let request = match &msg {
Msg::Outputs => Request::Outputs,
+ Msg::Action { action } => Request::Action(action.clone()),
};
let mut buf = serde_json::to_vec(&request).unwrap();
stream
@@ -35,6 +36,14 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
.read_to_end(&mut buf)
.context("error reading IPC response")?;
+ if matches!(msg, Msg::Action { .. }) {
+ if buf.is_empty() {
+ return Ok(());
+ } else {
+ bail!("unexpected response: expected no response, got {buf:?}");
+ }
+ }
+
let response = serde_json::from_slice(&buf).context("error parsing IPC response")?;
match msg {
Msg::Outputs => {
@@ -100,6 +109,7 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
println!();
}
}
+ Msg::Action { .. } => unreachable!(),
}
Ok(())
diff --git a/src/ipc/server.rs b/src/ipc/server.rs
index d493e861..bedfe48f 100644
--- a/src/ipc/server.rs
+++ b/src/ipc/server.rs
@@ -22,6 +22,7 @@ pub struct IpcServer {
}
struct ClientCtx {
+ event_loop: LoopHandle<'static, State>,
ipc_outputs: Rc<RefCell<HashMap<String, niri_ipc::Output>>>,
}
@@ -85,6 +86,7 @@ fn on_new_ipc_client(state: &mut State, stream: UnixStream) {
};
let ctx = ClientCtx {
+ event_loop: state.niri.event_loop.clone(),
ipc_outputs: state.backend.ipc_outputs(),
};
@@ -115,6 +117,13 @@ async fn handle_client(ctx: ClientCtx, stream: Async<'_, UnixStream>) -> anyhow:
let ipc_outputs = ctx.ipc_outputs.borrow().clone();
Response::Outputs(ipc_outputs)
}
+ Request::Action(action) => {
+ let action = niri_config::Action::from(action);
+ ctx.event_loop.insert_idle(move |state| {
+ state.do_action(action);
+ });
+ return Ok(());
+ }
};
let buf = serde_json::to_vec(&response).context("error formatting response")?;
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index c4bcc61f..93b1fe13 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -34,7 +34,8 @@ use std::mem;
use std::rc::Rc;
use std::time::Duration;
-use niri_config::{self, CenterFocusedColumn, Config, SizeChange, Struts};
+use niri_config::{self, CenterFocusedColumn, Config, Struts};
+use niri_ipc::SizeChange;
use smithay::backend::renderer::element::solid::SolidColorRenderElement;
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
use smithay::backend::renderer::element::{AsRenderElements, Id};
diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs
index e67f5bf2..cb99279a 100644
--- a/src/layout/monitor.rs
+++ b/src/layout/monitor.rs
@@ -2,7 +2,7 @@ use std::cmp::min;
use std::rc::Rc;
use std::time::Duration;
-use niri_config::SizeChange;
+use niri_ipc::SizeChange;
use smithay::backend::renderer::element::utils::{
CropRenderElement, Relocate, RelocateRenderElement,
};
diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs
index 0e576b7a..64bc4bc7 100644
--- a/src/layout/workspace.rs
+++ b/src/layout/workspace.rs
@@ -3,7 +3,8 @@ use std::iter::{self, zip};
use std::rc::Rc;
use std::time::Duration;
-use niri_config::{CenterFocusedColumn, PresetWidth, SizeChange, Struts};
+use niri_config::{CenterFocusedColumn, PresetWidth, Struts};
+use niri_ipc::SizeChange;
use smithay::desktop::space::SpaceElement;
use smithay::desktop::{layer_map_for_output, Window};
use smithay::output::Output;