diff --git a/docs/wiki/Configuration:-Key-Bindings.md b/docs/wiki/Configuration:-Key-Bindings.md
index f60df35bb9..12e83fa81e 100644
--- a/docs/wiki/Configuration:-Key-Bindings.md
+++ b/docs/wiki/Configuration:-Key-Bindings.md
@@ -356,7 +356,7 @@ Actions for taking screenshots.
- `screenshot`: opens the built-in interactive screenshot UI.
- `screenshot-screen`, `screenshot-window`: takes a screenshot of the focused screen or window respectively.
-The screenshot is both stored to the clipboard and saved to disk, according to the [`screenshot-path` option](./Configuration:-Miscellaneous.md#screenshot-path).
+The screenshot is both stored to the clipboard and saved to disk, according to the [`screenshot { path ""; }` option](./Configuration:-Miscellaneous.md#path).
Since: 25.02 You can disable saving to disk for a specific bind with the `write-to-disk=false` property:
diff --git a/docs/wiki/Configuration:-Miscellaneous.md b/docs/wiki/Configuration:-Miscellaneous.md
index 7a2f42dd26..d0d7018a38 100644
--- a/docs/wiki/Configuration:-Miscellaneous.md
+++ b/docs/wiki/Configuration:-Miscellaneous.md
@@ -9,7 +9,13 @@ spawn-sh-at-startup "qs -c ~/source/qs/MyAwesomeShell"
prefer-no-csd
-screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
+screenshot {
+ path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
+ notification {
+ action "Open" "xdg-open" "{path}"
+ action "Edit" "swappy" "-f" "{path}"
+ }
+}
environment {
QT_QPA_PLATFORM "wayland"
@@ -116,7 +122,13 @@ With `prefer-no-csd` set, applications that negotiate server-side decorations th
prefer-no-csd
```
-### `screenshot-path`
+### `screenshot`
+
+Since: next release
+
+Settings for screenshots taken with the built-in niri screenshot tool.
+
+#### `path`
Set the path where screenshots are saved.
A `~` at the front will be expanded to the home directory.
@@ -126,15 +138,49 @@ The path is formatted with `strftime(3)` to give you the screenshot date and tim
Niri will create the last folder of the path if it doesn't exist.
```kdl
-screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
+screenshot {
+ path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
+}
```
You can also set this option to `null` to disable saving screenshots to disk.
```kdl
-screenshot-path null
+screenshot {
+ path null
+}
+```
+
+Until: 26.04 For backwards compatibility, the old top-level `screenshot-path` option is still supported. It is used if `screenshot { path ""; }` is not set.
+
+```kdl
+// Deprecated
+screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
```
+
+#### `notification`
+
+Since: next release
+
+Add action buttons to the screenshot notification. Actions are shown only for screenshots that were saved to disk.
+
+The `{path}` placeholder in command arguments is replaced with the saved screenshot path.
+
+```kdl
+screenshot {
+ notification {
+ action "Open" "xdg-open" "{path}"
+ action "Edit" "swappy" "-f" "{path}"
+ }
+}
+```
+
+The notification block accepts any number of `action` entries, which have the following format:
+
+`action `.
+
+
### `environment`
Override environment variables for processes spawned by niri.
diff --git a/docs/wiki/Development:-Design-Principles.md b/docs/wiki/Development:-Design-Principles.md
index 7a3892a841..66b3a14059 100644
--- a/docs/wiki/Development:-Design-Principles.md
+++ b/docs/wiki/Development:-Design-Principles.md
@@ -98,7 +98,7 @@ The default input settings like touchpad tap and natural-scroll are how I believ
Shadows default to off because they are a fairly performance-intensive shader, and because many clientside-decorated windows already draw their own shadows.
-The default screenshot-path matches GNOME Shell.
+The default screenshot path matches GNOME Shell.
Default window rules are limited to fixing known severe issues (WezTerm) and doing something the absolute majority likely wants (make Firefox Picture-in-Picture player floating—it can't do that on its own currently, maybe the pip protocol will change that).
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs
index 909aeb80a5..de526cb121 100644
--- a/niri-config/src/lib.rs
+++ b/niri-config/src/lib.rs
@@ -75,7 +75,7 @@ pub struct Config {
pub layout: Layout,
pub prefer_no_csd: bool,
pub cursor: Cursor,
- pub screenshot_path: ScreenshotPath,
+ pub screenshot: Screenshot,
pub clipboard: Clipboard,
pub hotkey_overlay: HotkeyOverlay,
pub config_notification: ConfigNotification,
@@ -193,6 +193,7 @@ where
match name {
"input" => m_merge!(input),
"cursor" => m_merge!(cursor),
+ "screenshot" => m_merge!(screenshot),
"clipboard" => m_merge!(clipboard),
"hotkey-overlay" => m_merge!(hotkey_overlay),
"config-notification" => m_merge!(config_notification),
@@ -240,7 +241,7 @@ where
"screenshot-path" => {
let part = knuffel::Decode::decode_node(node, ctx)?;
- config.borrow_mut().screenshot_path = part;
+ config.borrow_mut().screenshot.path = part;
}
"layout" => {
@@ -648,6 +649,12 @@ mod tests {
assert_eq!(config.input.keyboard.repeat_rate, 25);
}
+ #[test]
+ fn legacy_screenshot_path() {
+ let config = Config::parse_mem(r#"screenshot-path "~/foo.png""#).unwrap();
+ assert_eq!(config.screenshot.path.0.as_deref(), Some("~/foo.png"));
+ }
+
#[track_caller]
fn do_parse(text: &str) -> Config {
Config::parse_mem(text)
@@ -832,7 +839,14 @@ mod tests {
hide-after-inactive-ms 3000
}
- screenshot-path "~/Screenshots/screenshot.png"
+ screenshot {
+ path "~/Screenshots/screenshot.png"
+
+ notification {
+ action "Open" "xdg-open" "{path}"
+ action "Edit" "swappy" "-f" "{path}"
+ }
+ }
clipboard {
disable-primary
@@ -1483,11 +1497,32 @@ mod tests {
3000,
),
},
- screenshot_path: ScreenshotPath(
- Some(
- "~/Screenshots/screenshot.png",
+ screenshot: Screenshot {
+ path: ScreenshotPath(
+ Some(
+ "~/Screenshots/screenshot.png",
+ ),
),
- ),
+ notification: ScreenshotNotification {
+ actions: [
+ ScreenshotNotificationAction {
+ label: "Open",
+ command: [
+ "xdg-open",
+ "{path}",
+ ],
+ },
+ ScreenshotNotificationAction {
+ label: "Edit",
+ command: [
+ "swappy",
+ "-f",
+ "{path}",
+ ],
+ },
+ ],
+ },
+ },
clipboard: Clipboard {
disable_primary: true,
},
diff --git a/niri-config/src/misc.rs b/niri-config/src/misc.rs
index 66a200ca35..d3c3715721 100644
--- a/niri-config/src/misc.rs
+++ b/niri-config/src/misc.rs
@@ -53,7 +53,7 @@ impl MergeWith for Cursor {
}
}
-#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
+#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
pub struct ScreenshotPath(#[knuffel(argument)] pub Option);
impl Default for ScreenshotPath {
@@ -64,6 +64,63 @@ impl Default for ScreenshotPath {
}
}
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Screenshot {
+ pub path: ScreenshotPath,
+ pub notification: ScreenshotNotification,
+}
+
+impl Default for Screenshot {
+ fn default() -> Self {
+ Self {
+ path: ScreenshotPath::default(),
+ notification: ScreenshotNotification::default(),
+ }
+ }
+}
+
+#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq, Eq)]
+pub struct ScreenshotPart {
+ #[knuffel(child)]
+ pub path: Option,
+ #[knuffel(child)]
+ pub notification: Option,
+}
+
+impl MergeWith for Screenshot {
+ fn merge_with(&mut self, part: &ScreenshotPart) {
+ merge_clone!((self, part), path);
+ if let Some(notification) = &part.notification {
+ self.notification.merge_with(notification);
+ }
+ }
+}
+
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub struct ScreenshotNotification {
+ pub actions: Vec,
+}
+
+#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq, Eq)]
+pub struct ScreenshotNotificationPart {
+ #[knuffel(children(name = "action"))]
+ pub actions: Vec,
+}
+
+impl MergeWith for ScreenshotNotification {
+ fn merge_with(&mut self, part: &ScreenshotNotificationPart) {
+ self.actions.clone_from(&part.actions);
+ }
+}
+
+#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
+pub struct ScreenshotNotificationAction {
+ #[knuffel(argument)]
+ pub label: String,
+ #[knuffel(arguments)]
+ pub command: Vec,
+}
+
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct HotkeyOverlay {
pub skip_at_startup: bool,
diff --git a/niri-ipc/src/lib.rs b/niri-ipc/src/lib.rs
index 0aa3cb4f48..73d6988f82 100644
--- a/niri-ipc/src/lib.rs
+++ b/niri-ipc/src/lib.rs
@@ -230,7 +230,8 @@ pub enum Action {
///
/// The path must be absolute, otherwise an error is returned.
///
- /// If `None`, the screenshot is saved according to the `screenshot-path` config setting.
+ /// If `None`, the screenshot is saved according to the `screenshot { path ""; }` config
+ /// setting.
#[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set))]
path: Option,
},
@@ -238,7 +239,7 @@ pub enum Action {
ScreenshotScreen {
/// Write the screenshot to disk in addition to putting it in your clipboard.
///
- /// The screenshot is saved according to the `screenshot-path` config setting.
+ /// The screenshot is saved according to the `screenshot { path ""; }` config setting.
#[cfg_attr(feature = "clap", arg(short = 'd', long, action = clap::ArgAction::Set, default_value_t = true))]
write_to_disk: bool,
@@ -250,7 +251,8 @@ pub enum Action {
///
/// The path must be absolute, otherwise an error is returned.
///
- /// If `None`, the screenshot is saved according to the `screenshot-path` config setting.
+ /// If `None`, the screenshot is saved according to the `screenshot { path ""; }` config
+ /// setting.
#[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set))]
path: Option,
},
@@ -264,7 +266,7 @@ pub enum Action {
id: Option,
/// Write the screenshot to disk in addition to putting it in your clipboard.
///
- /// The screenshot is saved according to the `screenshot-path` config setting.
+ /// The screenshot is saved according to the `screenshot { path ""; }` config setting.
#[cfg_attr(feature = "clap", arg(short = 'd', long, action = clap::ArgAction::Set, default_value_t = true))]
write_to_disk: bool,
@@ -279,7 +281,8 @@ pub enum Action {
///
/// The path must be absolute, otherwise an error is returned.
///
- /// If `None`, the screenshot is saved according to the `screenshot-path` config setting.
+ /// If `None`, the screenshot is saved according to the `screenshot { path ""; }` config
+ /// setting.
#[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set))]
path: Option,
},
diff --git a/resources/default-config.kdl b/resources/default-config.kdl
index ccad1ac22e..064a2b505d 100644
--- a/resources/default-config.kdl
+++ b/resources/default-config.kdl
@@ -285,13 +285,23 @@ hotkey-overlay {
// After enabling or disabling this, you need to restart the apps for this to take effect.
// prefer-no-csd
-// You can change the path where screenshots are saved.
-// A ~ at the front will be expanded to the home directory.
-// The path is formatted with strftime(3) to give you the screenshot date and time.
-screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
-
-// You can also set this to null to disable saving screenshots to disk.
-// screenshot-path null
+// Screenshot settings.
+screenshot {
+ // You can change the path where screenshots are saved.
+ // A ~ at the front will be expanded to the home directory.
+ // The path is formatted with strftime(3) to give you the screenshot date and time.
+ path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
+
+ // You can also set this to null to disable saving screenshots to disk.
+ // path null
+
+ // Add buttons to screenshot notifications. The buttons only appear for screenshots saved to disk.
+ // The {path} placeholder is replaced with the saved screenshot path.
+ // notification {
+ // action "Open" "xdg-open" "{path}"
+ // action "Edit" "swappy" "-f" "{path}"
+ // }
+}
// Animation settings.
// The wiki explains how to configure individual animations:
diff --git a/src/niri.rs b/src/niri.rs
index 190ef09d55..a9e71ac6fc 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -5690,6 +5690,8 @@ impl Niri {
})
.unwrap();
+ let notification_actions = self.config.borrow().screenshot.notification.actions.clone();
+
// Encode and save the image in a thread as it's slow.
thread::spawn(move || {
let mut buf = vec![];
@@ -5732,7 +5734,10 @@ impl Niri {
}
#[cfg(feature = "dbus")]
- if let Err(err) = crate::utils::show_screenshot_notification(image_path.as_deref()) {
+ if let Err(err) = crate::utils::show_screenshot_notification(
+ image_path.as_deref(),
+ ¬ification_actions,
+ ) {
warn!("error showing screenshot notification: {err:?}");
}
diff --git a/src/protocols/foreign_toplevel.rs b/src/protocols/foreign_toplevel.rs
index 11379169de..a8eb248d75 100644
--- a/src/protocols/foreign_toplevel.rs
+++ b/src/protocols/foreign_toplevel.rs
@@ -5,11 +5,13 @@ use std::sync::Arc;
use arrayvec::ArrayVec;
use smithay::output::Output;
use smithay::reexports::wayland_protocols::ext::foreign_toplevel_list::v1::server::{
- ext_foreign_toplevel_handle_v1::{self, ExtForeignToplevelHandleV1}, ext_foreign_toplevel_list_v1::{self, ExtForeignToplevelListV1},
+ ext_foreign_toplevel_handle_v1::{self, ExtForeignToplevelHandleV1},
+ ext_foreign_toplevel_list_v1::{self, ExtForeignToplevelListV1},
};
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
use smithay::reexports::wayland_protocols_wlr::foreign_toplevel::v1::server::{
- zwlr_foreign_toplevel_handle_v1::{self, ZwlrForeignToplevelHandleV1}, zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1},
+ zwlr_foreign_toplevel_handle_v1::{self, ZwlrForeignToplevelHandleV1},
+ zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1},
};
use smithay::reexports::wayland_server::backend::ClientId;
use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
@@ -18,12 +20,12 @@ use smithay::reexports::wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
};
use smithay::wayland::shell::xdg::{
- ToplevelState, ToplevelStateSet, XdgToplevelSurfaceRoleAttributes
+ ToplevelState, ToplevelStateSet, XdgToplevelSurfaceRoleAttributes,
};
use crate::niri::State;
-use crate::window::mapped::MappedId;
use crate::utils::with_toplevel_role_and_current;
+use crate::window::mapped::MappedId;
const EXT_LIST_VERSION: u32 = 1;
const WLR_MANAGEMENT_VERSION: u32 = 3;
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index bbeac5bc6a..db2155a10a 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -1,3 +1,4 @@
+use std::borrow::Cow;
use std::cmp::{max, min};
use std::ffi::{CString, OsStr};
use std::fmt::Display;
@@ -13,7 +14,7 @@ use anyhow::{ensure, Context};
use bitflags::bitflags;
use directories::UserDirs;
use git_version::git_version;
-use niri_config::{Config, OutputName};
+use niri_config::{Config, OutputName, ScreenshotNotificationAction};
use smithay::backend::renderer::utils::{
with_renderer_surface_state, RendererSurfaceStateUserData,
};
@@ -277,7 +278,7 @@ pub fn expand_home(path: &Path) -> anyhow::Result