mirror of
https://github.com/YaLTeR/niri.git
synced 2025-10-06 00:23:14 +02:00
Add spawn-sh, spawn-at-startup-sh
Our top 10 most confusing config moments
This commit is contained in:
@@ -206,7 +206,8 @@ binds {
|
||||
> }
|
||||
> ```
|
||||
|
||||
Currently, niri *does not* use a shell to run commands, which means that you need to manually separate arguments.
|
||||
For `spawn`, niri *does not* use a shell to run commands, which means that you need to manually separate arguments.
|
||||
See [`spawn-sh`](#spawn-sh) below for an action that uses a shell.
|
||||
|
||||
```kdl
|
||||
binds {
|
||||
@@ -249,6 +250,37 @@ binds {
|
||||
}
|
||||
```
|
||||
|
||||
#### `spawn-sh`
|
||||
|
||||
<sup>Since: next release</sup>
|
||||
|
||||
Run a command through the shell.
|
||||
|
||||
The argument is a single string that is passed verbatim to `sh`.
|
||||
You can use shell variables, pipelines, `~` expansion, and everything else as expected.
|
||||
|
||||
```kdl
|
||||
binds {
|
||||
// Works with spawn-sh: all arguments in the same string.
|
||||
Mod+D { spawn-sh "alacritty -e /usr/bin/fish"; }
|
||||
|
||||
// Works with spawn-sh: shell variable ($MAIN_OUTPUT), ~ expansion.
|
||||
Mod+T { spawn-sh "grim -o $MAIN_OUTPUT ~/screenshot.png"; }
|
||||
|
||||
// Works with spawn-sh: process substitution.
|
||||
Mod+Q { spawn-sh "notify-send clipboard \"$(wl-paste)\""; }
|
||||
|
||||
// Works with spawn-sh: multiple commands.
|
||||
Super+Alt+S { spawn-sh "pkill orca || exec orca"; }
|
||||
}
|
||||
```
|
||||
|
||||
`spawn-sh "some command"` is equivalent to `spawn "sh" "-c" "some command"`—it's just a less confusing shorthand.
|
||||
Keep in mind that going through the shell incurs a tiny performance penalty compared to directly `spawn`ing some binary.
|
||||
|
||||
Using `sh` is hardcoded, consistent with other compositors.
|
||||
If you want a different shell, write it out using `spawn`, e.g. `spawn "fish" "-c" "some fish command"`.
|
||||
|
||||
#### `quit`
|
||||
|
||||
Exit niri after showing a confirmation dialog to avoid accidentally triggering it.
|
||||
|
@@ -5,6 +5,7 @@ Here are all of these options at a glance:
|
||||
```kdl
|
||||
spawn-at-startup "waybar"
|
||||
spawn-at-startup "alacritty"
|
||||
spawn-at-startup-sh "qs -c ~/source/qs/MyAwesomeShell"
|
||||
|
||||
prefer-no-csd
|
||||
|
||||
@@ -71,6 +72,22 @@ spawn-at-startup "alacritty"
|
||||
Note that running niri as a systemd session supports xdg-desktop-autostart out of the box, which may be more convenient to use.
|
||||
Thanks to this, apps that you configured to autostart in GNOME will also "just work" in niri, without any manual `spawn-at-startup` configuration.
|
||||
|
||||
### `spawn-at-startup-sh`
|
||||
|
||||
<sup>Since: next release</sup>
|
||||
|
||||
Add lines like this to run shell commands at niri startup.
|
||||
|
||||
The argument is a single string that is passed verbatim to `sh`.
|
||||
You can use shell variables, pipelines, `~` expansion and everything else as expected.
|
||||
|
||||
See detailed description in the docs for the [`spawn-sh` key binding action](./Configuration:-Key-Bindings.md#spawn-sh).
|
||||
|
||||
```kdl
|
||||
// Pass all arguments in the same string.
|
||||
spawn-at-startup-sh "qs -c ~/source/qs/MyAwesomeShell"
|
||||
```
|
||||
|
||||
### `prefer-no-csd`
|
||||
|
||||
This flag will make niri ask the applications to omit their client-side decorations.
|
||||
|
@@ -41,6 +41,8 @@ pub struct Config {
|
||||
pub outputs: Outputs,
|
||||
#[knuffel(children(name = "spawn-at-startup"))]
|
||||
pub spawn_at_startup: Vec<SpawnAtStartup>,
|
||||
#[knuffel(children(name = "spawn-at-startup-sh"))]
|
||||
pub spawn_at_startup_sh: Vec<SpawnAtStartupSh>,
|
||||
#[knuffel(child, default)]
|
||||
pub layout: Layout,
|
||||
#[knuffel(child, default)]
|
||||
@@ -606,6 +608,12 @@ pub struct SpawnAtStartup {
|
||||
pub command: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SpawnAtStartupSh {
|
||||
#[knuffel(argument)]
|
||||
pub command: String,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct FocusRing {
|
||||
#[knuffel(child)]
|
||||
@@ -1719,6 +1727,7 @@ pub enum Action {
|
||||
DebugToggleOpaqueRegions,
|
||||
DebugToggleDamage,
|
||||
Spawn(#[knuffel(arguments)] Vec<String>),
|
||||
SpawnSh(#[knuffel(argument)] String),
|
||||
DoScreenTransition(#[knuffel(property(name = "delay-ms"))] Option<u16>),
|
||||
#[knuffel(skip)]
|
||||
ConfirmScreenshot {
|
||||
@@ -1962,6 +1971,7 @@ impl From<niri_ipc::Action> for Action {
|
||||
niri_ipc::Action::PowerOffMonitors {} => Self::PowerOffMonitors,
|
||||
niri_ipc::Action::PowerOnMonitors {} => Self::PowerOnMonitors,
|
||||
niri_ipc::Action::Spawn { command } => Self::Spawn(command),
|
||||
niri_ipc::Action::SpawnSh { command } => Self::SpawnSh(command),
|
||||
niri_ipc::Action::DoScreenTransition { delay_ms } => Self::DoScreenTransition(delay_ms),
|
||||
niri_ipc::Action::Screenshot { show_pointer } => Self::Screenshot(show_pointer),
|
||||
niri_ipc::Action::ScreenshotScreen {
|
||||
@@ -3854,7 +3864,7 @@ where
|
||||
}
|
||||
match Action::decode_node(child, ctx) {
|
||||
Ok(action) => {
|
||||
if !matches!(action, Action::Spawn(_)) {
|
||||
if !matches!(action, Action::Spawn(_) | Action::SpawnSh(_)) {
|
||||
if let Some(node) = allow_when_locked_node {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
node,
|
||||
@@ -4453,6 +4463,7 @@ mod tests {
|
||||
}
|
||||
|
||||
spawn-at-startup "alacritty" "-e" "fish"
|
||||
spawn-at-startup-sh "qs -c ~/source/qs/MyAwesomeShell"
|
||||
|
||||
prefer-no-csd
|
||||
|
||||
@@ -4549,6 +4560,7 @@ mod tests {
|
||||
Mod+Shift+1 { focus-workspace "workspace-1"; }
|
||||
Mod+Shift+E allow-inhibiting=false { quit skip-confirmation=true; }
|
||||
Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; }
|
||||
Super+Alt+S allow-when-locked=true { spawn-sh "pkill orca || exec orca"; }
|
||||
}
|
||||
|
||||
switch-events {
|
||||
@@ -4794,6 +4806,11 @@ mod tests {
|
||||
],
|
||||
},
|
||||
],
|
||||
spawn_at_startup_sh: [
|
||||
SpawnAtStartupSh {
|
||||
command: "qs -c ~/source/qs/MyAwesomeShell",
|
||||
},
|
||||
],
|
||||
layout: Layout {
|
||||
focus_ring: FocusRing {
|
||||
off: false,
|
||||
@@ -5681,6 +5698,24 @@ mod tests {
|
||||
allow_inhibiting: true,
|
||||
hotkey_overlay_title: None,
|
||||
},
|
||||
Bind {
|
||||
key: Key {
|
||||
trigger: Keysym(
|
||||
XK_s,
|
||||
),
|
||||
modifiers: Modifiers(
|
||||
ALT | SUPER,
|
||||
),
|
||||
},
|
||||
action: SpawnSh(
|
||||
"pkill orca || exec orca",
|
||||
),
|
||||
repeat: true,
|
||||
cooldown: None,
|
||||
allow_when_locked: true,
|
||||
allow_inhibiting: true,
|
||||
hotkey_overlay_title: None,
|
||||
},
|
||||
],
|
||||
),
|
||||
switch_events: SwitchBinds {
|
||||
|
@@ -203,6 +203,12 @@ pub enum Action {
|
||||
#[cfg_attr(feature = "clap", arg(last = true, required = true))]
|
||||
command: Vec<String>,
|
||||
},
|
||||
/// Spawn a command through the shell.
|
||||
SpawnSh {
|
||||
/// Command to run.
|
||||
#[cfg_attr(feature = "clap", arg(last = true, required = true))]
|
||||
command: String,
|
||||
},
|
||||
/// Do a screen transition.
|
||||
DoScreenTransition {
|
||||
/// Delay in milliseconds for the screen to freeze before starting the transition.
|
||||
|
@@ -270,6 +270,9 @@ layout {
|
||||
// This line starts waybar, a commonly used bar for Wayland compositors.
|
||||
spawn-at-startup "waybar"
|
||||
|
||||
// To run a shell command (with variables, pipes, etc.), use spawn-at-startup-sh:
|
||||
// spawn-at-startup-sh "qs -c ~/source/qs/MyAwesomeShell"
|
||||
|
||||
hotkey-overlay {
|
||||
// Uncomment this line to disable the "Important Hotkeys" pop-up at startup.
|
||||
// skip-at-startup
|
||||
@@ -363,10 +366,10 @@ binds {
|
||||
Mod+D hotkey-overlay-title="Run an Application: fuzzel" { spawn "fuzzel"; }
|
||||
Super+Alt+L hotkey-overlay-title="Lock the Screen: swaylock" { spawn "swaylock"; }
|
||||
|
||||
// You can also use a shell. Do this if you need pipes, multiple commands, etc.
|
||||
// Note: the entire command goes as a single argument in the end.
|
||||
// Use spawn-sh to run a shell command. Do this if you need pipes, multiple commands, etc.
|
||||
// Note: the entire command goes as a single argument. It's passed verbatim to `sh -c`.
|
||||
// For example, this is a standard bind to toggle the screen reader (orca).
|
||||
Super+Alt+S hotkey-overlay-title=null { spawn "sh" "-c" "pkill orca || exec orca"; }
|
||||
Super+Alt+S hotkey-overlay-title=null { spawn-sh "pkill orca || exec orca"; }
|
||||
|
||||
// Example volume keys mappings for PipeWire & WirePlumber.
|
||||
// The allow-when-locked=true property makes them work even when the session is locked.
|
||||
|
@@ -44,7 +44,7 @@ use crate::layout::scrolling::ScrollDirection;
|
||||
use crate::layout::{ActivateWindow, LayoutElement as _};
|
||||
use crate::niri::{CastTarget, PointerVisibility, State};
|
||||
use crate::ui::screenshot_ui::ScreenshotUi;
|
||||
use crate::utils::spawning::spawn;
|
||||
use crate::utils::spawning::{spawn, spawn_sh};
|
||||
use crate::utils::{center, get_monotonic_time, ResizeEdge};
|
||||
|
||||
pub mod backend_ext;
|
||||
@@ -595,6 +595,10 @@ impl State {
|
||||
let (token, _) = self.niri.activation_state.create_external_token(None);
|
||||
spawn(command, Some(token.clone()));
|
||||
}
|
||||
Action::SpawnSh(command) => {
|
||||
let (token, _) = self.niri.activation_state.create_external_token(None);
|
||||
spawn_sh(command, Some(token.clone()));
|
||||
}
|
||||
Action::DoScreenTransition(delay_ms) => {
|
||||
self.backend.with_primary_renderer(|renderer| {
|
||||
self.niri.do_screen_transition(renderer, delay_ms);
|
||||
|
@@ -20,8 +20,8 @@ use niri::dbus;
|
||||
use niri::ipc::client::handle_msg;
|
||||
use niri::niri::State;
|
||||
use niri::utils::spawning::{
|
||||
spawn, store_and_increase_nofile_rlimit, CHILD_DISPLAY, CHILD_ENV, REMOVE_ENV_RUST_BACKTRACE,
|
||||
REMOVE_ENV_RUST_LIB_BACKTRACE,
|
||||
spawn, spawn_sh, store_and_increase_nofile_rlimit, CHILD_DISPLAY, CHILD_ENV,
|
||||
REMOVE_ENV_RUST_BACKTRACE, REMOVE_ENV_RUST_LIB_BACKTRACE,
|
||||
};
|
||||
use niri::utils::{cause_panic, version, watcher, xwayland, IS_SYSTEMD_SERVICE};
|
||||
use niri_config::ConfigPath;
|
||||
@@ -151,6 +151,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.unwrap_or_default();
|
||||
|
||||
let spawn_at_startup = mem::take(&mut config.spawn_at_startup);
|
||||
let spawn_at_startup_sh = mem::take(&mut config.spawn_at_startup_sh);
|
||||
*CHILD_ENV.write().unwrap() = mem::take(&mut config.environment);
|
||||
|
||||
store_and_increase_nofile_rlimit();
|
||||
@@ -237,6 +238,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
for elem in spawn_at_startup {
|
||||
spawn(elem.command, None);
|
||||
}
|
||||
for elem in spawn_at_startup_sh {
|
||||
spawn_sh(elem.command, None);
|
||||
}
|
||||
|
||||
// Show the config error notification right away if needed.
|
||||
if config_errored {
|
||||
|
@@ -270,7 +270,7 @@ fn render(
|
||||
|
||||
// Add the spawn actions.
|
||||
for bind in binds.iter().filter(|bind| {
|
||||
matches!(bind.action, Action::Spawn(_))
|
||||
matches!(bind.action, Action::Spawn(_) | Action::SpawnSh(_))
|
||||
// Only show binds with Mod or Super to filter out stuff like volume up/down.
|
||||
&& (bind.key.modifiers.contains(Modifiers::COMPOSITOR)
|
||||
|| bind.key.modifiers.contains(Modifiers::SUPER))
|
||||
@@ -447,6 +447,11 @@ fn action_name(action: &Action) -> String {
|
||||
"Spawn <span face='monospace' bgcolor='#000000'>{}</span>",
|
||||
args.first().unwrap_or(&String::new())
|
||||
),
|
||||
Action::SpawnSh(command) => format!(
|
||||
"Spawn <span face='monospace' bgcolor='#000000'>{}</span>",
|
||||
// Fairly crude but should get the job done in most cases.
|
||||
command.split_ascii_whitespace().next().unwrap_or("")
|
||||
),
|
||||
_ => String::from("FIXME: Unknown"),
|
||||
}
|
||||
}
|
||||
|
@@ -83,6 +83,16 @@ pub fn spawn<T: AsRef<OsStr> + Send + 'static>(command: Vec<T>, token: Option<Xd
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns the command through the shell.
|
||||
///
|
||||
/// We hardcode `sh -c`, consistent with other compositors:
|
||||
///
|
||||
/// - https://github.com/swaywm/sway/blob/b3dcde8d69c3f1304b076968a7a64f54d0c958be/sway/commands/exec_always.c#L64
|
||||
/// - https://github.com/hyprwm/Hyprland/blob/1ac1ff457ab8ef1ae6a8f2ab17ee7965adfa729f/src/managers/KeybindManager.cpp#L987
|
||||
pub fn spawn_sh(command: String, token: Option<XdgActivationToken>) {
|
||||
spawn(vec![String::from("sh"), String::from("-c"), command], token);
|
||||
}
|
||||
|
||||
fn spawn_sync(
|
||||
command: impl AsRef<OsStr>,
|
||||
args: impl IntoIterator<Item = impl AsRef<OsStr>>,
|
||||
|
Reference in New Issue
Block a user