mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-24 02:01:18 +07:00
Add --path argument for niri msg screenshot* commands (#2126)
* Check for empty screenshot parent before creating Avoids a warning. * Add --path argument for niri msg screenshot* commands * fix --------- Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
This commit is contained in:
@@ -118,16 +118,27 @@ pub enum Action {
|
|||||||
CancelScreenshot,
|
CancelScreenshot,
|
||||||
#[knuffel(skip)]
|
#[knuffel(skip)]
|
||||||
ScreenshotTogglePointer,
|
ScreenshotTogglePointer,
|
||||||
Screenshot(#[knuffel(property(name = "show-pointer"), default = true)] bool),
|
Screenshot(
|
||||||
|
#[knuffel(property(name = "show-pointer"), default = true)] bool,
|
||||||
|
// Path; not settable from knuffel
|
||||||
|
Option<String>,
|
||||||
|
),
|
||||||
ScreenshotScreen(
|
ScreenshotScreen(
|
||||||
#[knuffel(property(name = "write-to-disk"), default = true)] bool,
|
#[knuffel(property(name = "write-to-disk"), default = true)] bool,
|
||||||
#[knuffel(property(name = "show-pointer"), default = true)] bool,
|
#[knuffel(property(name = "show-pointer"), default = true)] bool,
|
||||||
|
// Path; not settable from knuffel
|
||||||
|
Option<String>,
|
||||||
|
),
|
||||||
|
ScreenshotWindow(
|
||||||
|
#[knuffel(property(name = "write-to-disk"), default = true)] bool,
|
||||||
|
// Path; not settable from knuffel
|
||||||
|
Option<String>,
|
||||||
),
|
),
|
||||||
ScreenshotWindow(#[knuffel(property(name = "write-to-disk"), default = true)] bool),
|
|
||||||
#[knuffel(skip)]
|
#[knuffel(skip)]
|
||||||
ScreenshotWindowById {
|
ScreenshotWindowById {
|
||||||
id: u64,
|
id: u64,
|
||||||
write_to_disk: bool,
|
write_to_disk: bool,
|
||||||
|
path: Option<String>,
|
||||||
},
|
},
|
||||||
ToggleKeyboardShortcutsInhibit,
|
ToggleKeyboardShortcutsInhibit,
|
||||||
CloseWindow,
|
CloseWindow,
|
||||||
@@ -364,19 +375,28 @@ impl From<niri_ipc::Action> for Action {
|
|||||||
niri_ipc::Action::Spawn { command } => Self::Spawn(command),
|
niri_ipc::Action::Spawn { command } => Self::Spawn(command),
|
||||||
niri_ipc::Action::SpawnSh { command } => Self::SpawnSh(command),
|
niri_ipc::Action::SpawnSh { command } => Self::SpawnSh(command),
|
||||||
niri_ipc::Action::DoScreenTransition { delay_ms } => Self::DoScreenTransition(delay_ms),
|
niri_ipc::Action::DoScreenTransition { delay_ms } => Self::DoScreenTransition(delay_ms),
|
||||||
niri_ipc::Action::Screenshot { show_pointer } => Self::Screenshot(show_pointer),
|
niri_ipc::Action::Screenshot { show_pointer, path } => {
|
||||||
|
Self::Screenshot(show_pointer, path)
|
||||||
|
}
|
||||||
niri_ipc::Action::ScreenshotScreen {
|
niri_ipc::Action::ScreenshotScreen {
|
||||||
write_to_disk,
|
write_to_disk,
|
||||||
show_pointer,
|
show_pointer,
|
||||||
} => Self::ScreenshotScreen(write_to_disk, show_pointer),
|
path,
|
||||||
|
} => Self::ScreenshotScreen(write_to_disk, show_pointer, path),
|
||||||
niri_ipc::Action::ScreenshotWindow {
|
niri_ipc::Action::ScreenshotWindow {
|
||||||
id: None,
|
id: None,
|
||||||
write_to_disk,
|
write_to_disk,
|
||||||
} => Self::ScreenshotWindow(write_to_disk),
|
path,
|
||||||
|
} => Self::ScreenshotWindow(write_to_disk, path),
|
||||||
niri_ipc::Action::ScreenshotWindow {
|
niri_ipc::Action::ScreenshotWindow {
|
||||||
id: Some(id),
|
id: Some(id),
|
||||||
write_to_disk,
|
write_to_disk,
|
||||||
} => Self::ScreenshotWindowById { id, write_to_disk },
|
path,
|
||||||
|
} => Self::ScreenshotWindowById {
|
||||||
|
id,
|
||||||
|
write_to_disk,
|
||||||
|
path,
|
||||||
|
},
|
||||||
niri_ipc::Action::ToggleKeyboardShortcutsInhibit {} => {
|
niri_ipc::Action::ToggleKeyboardShortcutsInhibit {} => {
|
||||||
Self::ToggleKeyboardShortcutsInhibit
|
Self::ToggleKeyboardShortcutsInhibit
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,6 +220,14 @@ pub enum Action {
|
|||||||
/// Whether to show the mouse pointer by default in the screenshot UI.
|
/// Whether to show the mouse pointer by default in the screenshot UI.
|
||||||
#[cfg_attr(feature = "clap", arg(short = 'p', long, action = clap::ArgAction::Set, default_value_t = true))]
|
#[cfg_attr(feature = "clap", arg(short = 'p', long, action = clap::ArgAction::Set, default_value_t = true))]
|
||||||
show_pointer: bool,
|
show_pointer: bool,
|
||||||
|
|
||||||
|
/// Path to save the screenshot to.
|
||||||
|
///
|
||||||
|
/// The path must be absolute, otherwise an error is returned.
|
||||||
|
///
|
||||||
|
/// 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<String>,
|
||||||
},
|
},
|
||||||
/// Screenshot the focused screen.
|
/// Screenshot the focused screen.
|
||||||
ScreenshotScreen {
|
ScreenshotScreen {
|
||||||
@@ -232,6 +240,14 @@ pub enum Action {
|
|||||||
/// Whether to include the mouse pointer in the screenshot.
|
/// Whether to include the mouse pointer in the screenshot.
|
||||||
#[cfg_attr(feature = "clap", arg(short = 'p', long, action = clap::ArgAction::Set, default_value_t = true))]
|
#[cfg_attr(feature = "clap", arg(short = 'p', long, action = clap::ArgAction::Set, default_value_t = true))]
|
||||||
show_pointer: bool,
|
show_pointer: bool,
|
||||||
|
|
||||||
|
/// Path to save the screenshot to.
|
||||||
|
///
|
||||||
|
/// The path must be absolute, otherwise an error is returned.
|
||||||
|
///
|
||||||
|
/// 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<String>,
|
||||||
},
|
},
|
||||||
/// Screenshot a window.
|
/// Screenshot a window.
|
||||||
#[cfg_attr(feature = "clap", clap(about = "Screenshot the focused window"))]
|
#[cfg_attr(feature = "clap", clap(about = "Screenshot the focused window"))]
|
||||||
@@ -246,6 +262,14 @@ pub enum Action {
|
|||||||
/// 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))]
|
#[cfg_attr(feature = "clap", arg(short = 'd', long, action = clap::ArgAction::Set, default_value_t = true))]
|
||||||
write_to_disk: bool,
|
write_to_disk: bool,
|
||||||
|
|
||||||
|
/// Path to save the screenshot to.
|
||||||
|
///
|
||||||
|
/// The path must be absolute, otherwise an error is returned.
|
||||||
|
///
|
||||||
|
/// 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<String>,
|
||||||
},
|
},
|
||||||
/// Enable or disable the keyboard shortcuts inhibitor (if any) for the focused surface.
|
/// Enable or disable the keyboard shortcuts inhibitor (if any) for the focused surface.
|
||||||
ToggleKeyboardShortcutsInhibit {},
|
ToggleKeyboardShortcutsInhibit {},
|
||||||
|
|||||||
+30
-17
@@ -605,14 +605,17 @@ impl State {
|
|||||||
self.niri.do_screen_transition(renderer, delay_ms);
|
self.niri.do_screen_transition(renderer, delay_ms);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Action::ScreenshotScreen(write_to_disk, show_pointer) => {
|
Action::ScreenshotScreen(write_to_disk, show_pointer, path) => {
|
||||||
let active = self.niri.layout.active_output().cloned();
|
let active = self.niri.layout.active_output().cloned();
|
||||||
if let Some(active) = active {
|
if let Some(active) = active {
|
||||||
self.backend.with_primary_renderer(|renderer| {
|
self.backend.with_primary_renderer(|renderer| {
|
||||||
if let Err(err) =
|
if let Err(err) = self.niri.screenshot(
|
||||||
self.niri
|
renderer,
|
||||||
.screenshot(renderer, &active, write_to_disk, show_pointer)
|
&active,
|
||||||
{
|
write_to_disk,
|
||||||
|
show_pointer,
|
||||||
|
path,
|
||||||
|
) {
|
||||||
warn!("error taking screenshot: {err:?}");
|
warn!("error taking screenshot: {err:?}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -636,32 +639,42 @@ impl State {
|
|||||||
self.niri.screenshot_ui.toggle_pointer();
|
self.niri.screenshot_ui.toggle_pointer();
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
}
|
}
|
||||||
Action::Screenshot(show_cursor) => {
|
Action::Screenshot(show_cursor, path) => {
|
||||||
self.open_screenshot_ui(show_cursor);
|
self.open_screenshot_ui(show_cursor, path);
|
||||||
}
|
}
|
||||||
Action::ScreenshotWindow(write_to_disk) => {
|
Action::ScreenshotWindow(write_to_disk, path) => {
|
||||||
let focus = self.niri.layout.focus_with_output();
|
let focus = self.niri.layout.focus_with_output();
|
||||||
if let Some((mapped, output)) = focus {
|
if let Some((mapped, output)) = focus {
|
||||||
self.backend.with_primary_renderer(|renderer| {
|
self.backend.with_primary_renderer(|renderer| {
|
||||||
if let Err(err) =
|
if let Err(err) = self.niri.screenshot_window(
|
||||||
self.niri
|
renderer,
|
||||||
.screenshot_window(renderer, output, mapped, write_to_disk)
|
output,
|
||||||
{
|
mapped,
|
||||||
|
write_to_disk,
|
||||||
|
path,
|
||||||
|
) {
|
||||||
warn!("error taking screenshot: {err:?}");
|
warn!("error taking screenshot: {err:?}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Action::ScreenshotWindowById { id, write_to_disk } => {
|
Action::ScreenshotWindowById {
|
||||||
|
id,
|
||||||
|
write_to_disk,
|
||||||
|
path,
|
||||||
|
} => {
|
||||||
let mut windows = self.niri.layout.windows();
|
let mut windows = self.niri.layout.windows();
|
||||||
let window = windows.find(|(_, m)| m.id().get() == id);
|
let window = windows.find(|(_, m)| m.id().get() == id);
|
||||||
if let Some((Some(monitor), mapped)) = window {
|
if let Some((Some(monitor), mapped)) = window {
|
||||||
let output = monitor.output();
|
let output = monitor.output();
|
||||||
self.backend.with_primary_renderer(|renderer| {
|
self.backend.with_primary_renderer(|renderer| {
|
||||||
if let Err(err) =
|
if let Err(err) = self.niri.screenshot_window(
|
||||||
self.niri
|
renderer,
|
||||||
.screenshot_window(renderer, output, mapped, write_to_disk)
|
output,
|
||||||
{
|
mapped,
|
||||||
|
write_to_disk,
|
||||||
|
path,
|
||||||
|
) {
|
||||||
warn!("error taking screenshot: {err:?}");
|
warn!("error taking screenshot: {err:?}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+31
-4
@@ -1,20 +1,34 @@
|
|||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
use std::slice;
|
use std::path::Path;
|
||||||
|
use std::{env, slice};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context};
|
use anyhow::{anyhow, bail, Context};
|
||||||
use niri_config::OutputName;
|
use niri_config::OutputName;
|
||||||
use niri_ipc::socket::Socket;
|
use niri_ipc::socket::Socket;
|
||||||
use niri_ipc::{
|
use niri_ipc::{
|
||||||
Event, KeyboardLayouts, LogicalOutput, Mode, Output, OutputConfigChanged, Overview, Request,
|
Action, Event, KeyboardLayouts, LogicalOutput, Mode, Output, OutputConfigChanged, Overview,
|
||||||
Response, Transform, Window, WindowLayout,
|
Request, Response, Transform, Window, WindowLayout,
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::cli::Msg;
|
use crate::cli::Msg;
|
||||||
use crate::utils::version;
|
use crate::utils::version;
|
||||||
|
|
||||||
pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
|
pub fn handle_msg(mut msg: Msg, json: bool) -> anyhow::Result<()> {
|
||||||
|
// For actions taking paths, prepend the niri CLI's working directory.
|
||||||
|
if let Msg::Action {
|
||||||
|
action:
|
||||||
|
Action::Screenshot { path, .. }
|
||||||
|
| Action::ScreenshotScreen { path, .. }
|
||||||
|
| Action::ScreenshotWindow { path, .. },
|
||||||
|
} = &mut msg
|
||||||
|
{
|
||||||
|
if let Some(path) = path {
|
||||||
|
ensure_absolute_path(path).context("error making the path absolute")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let request = match &msg {
|
let request = match &msg {
|
||||||
Msg::Version => Request::Version,
|
Msg::Version => Request::Version,
|
||||||
Msg::Outputs => Request::Outputs,
|
Msg::Outputs => Request::Outputs,
|
||||||
@@ -668,6 +682,19 @@ fn fmt_rounded(x: f64) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ensure_absolute_path(path: &mut String) -> anyhow::Result<()> {
|
||||||
|
let p = Path::new(path);
|
||||||
|
if p.is_relative() {
|
||||||
|
let mut cwd = env::current_dir().context("error getting current working directory")?;
|
||||||
|
cwd.push(p);
|
||||||
|
match cwd.into_os_string().into_string() {
|
||||||
|
Ok(absolute) => *path = absolute,
|
||||||
|
Err(cwd) => bail!("couldn't convert absolute path to string: {cwd:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use insta::assert_snapshot;
|
use insta::assert_snapshot;
|
||||||
|
|||||||
+22
-3
@@ -2,7 +2,7 @@ use std::cell::RefCell;
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::os::unix::net::{UnixListener, UnixStream};
|
use std::os::unix::net::{UnixListener, UnixStream};
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::{env, io, process};
|
use std::{env, io, process};
|
||||||
@@ -17,8 +17,8 @@ use futures_util::{select_biased, AsyncBufReadExt, AsyncWrite, AsyncWriteExt, Fu
|
|||||||
use niri_config::OutputName;
|
use niri_config::OutputName;
|
||||||
use niri_ipc::state::{EventStreamState, EventStreamStatePart as _};
|
use niri_ipc::state::{EventStreamState, EventStreamStatePart as _};
|
||||||
use niri_ipc::{
|
use niri_ipc::{
|
||||||
Event, KeyboardLayouts, OutputConfigChanged, Overview, Reply, Request, Response, WindowLayout,
|
Action, Event, KeyboardLayouts, OutputConfigChanged, Overview, Reply, Request, Response,
|
||||||
Workspace,
|
WindowLayout, Workspace,
|
||||||
};
|
};
|
||||||
use smithay::desktop::layer_map_for_output;
|
use smithay::desktop::layer_map_for_output;
|
||||||
use smithay::input::pointer::{
|
use smithay::input::pointer::{
|
||||||
@@ -379,6 +379,8 @@ async fn process(ctx: &ClientCtx, request: Request) -> Reply {
|
|||||||
Response::PickedColor(color)
|
Response::PickedColor(color)
|
||||||
}
|
}
|
||||||
Request::Action(action) => {
|
Request::Action(action) => {
|
||||||
|
validate_action(&action)?;
|
||||||
|
|
||||||
let (tx, rx) = async_channel::bounded(1);
|
let (tx, rx) = async_channel::bounded(1);
|
||||||
|
|
||||||
let action = niri_config::Action::from(action);
|
let action = niri_config::Action::from(action);
|
||||||
@@ -451,6 +453,23 @@ async fn process(ctx: &ClientCtx, request: Request) -> Reply {
|
|||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_action(action: &Action) -> Result<(), String> {
|
||||||
|
if let Action::Screenshot { path, .. }
|
||||||
|
| Action::ScreenshotScreen { path, .. }
|
||||||
|
| Action::ScreenshotWindow { path, .. } = action
|
||||||
|
{
|
||||||
|
if let Some(path) = path {
|
||||||
|
// Relative paths are resolved against the niri compositor's working directory, which
|
||||||
|
// is almost certainly not what you want.
|
||||||
|
if !Path::new(path).is_absolute() {
|
||||||
|
return Err(format!("path must be absolute: {path}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_event_stream_client(client: EventStreamClient) -> anyhow::Result<()> {
|
async fn handle_event_stream_client(client: EventStreamClient) -> anyhow::Result<()> {
|
||||||
let EventStreamClient {
|
let EventStreamClient {
|
||||||
events,
|
events,
|
||||||
|
|||||||
+24
-10
@@ -1835,7 +1835,7 @@ impl State {
|
|||||||
self.niri.output_management_state.notify_changes(new_config);
|
self.niri.output_management_state.notify_changes(new_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_screenshot_ui(&mut self, show_pointer: bool) {
|
pub fn open_screenshot_ui(&mut self, show_pointer: bool, path: Option<String>) {
|
||||||
if self.niri.is_locked() || self.niri.screenshot_ui.is_open() {
|
if self.niri.is_locked() || self.niri.screenshot_ui.is_open() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1876,7 +1876,7 @@ impl State {
|
|||||||
self.backend.with_primary_renderer(|renderer| {
|
self.backend.with_primary_renderer(|renderer| {
|
||||||
self.niri
|
self.niri
|
||||||
.screenshot_ui
|
.screenshot_ui
|
||||||
.open(renderer, screenshots, default_output, show_pointer)
|
.open(renderer, screenshots, default_output, show_pointer, path)
|
||||||
});
|
});
|
||||||
|
|
||||||
self.niri
|
self.niri
|
||||||
@@ -1902,14 +1902,15 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn confirm_screenshot(&mut self, write_to_disk: bool) {
|
pub fn confirm_screenshot(&mut self, write_to_disk: bool) {
|
||||||
if !self.niri.screenshot_ui.is_open() {
|
let ScreenshotUi::Open { path, .. } = &mut self.niri.screenshot_ui else {
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
|
let path = path.take();
|
||||||
|
|
||||||
self.backend.with_primary_renderer(|renderer| {
|
self.backend.with_primary_renderer(|renderer| {
|
||||||
match self.niri.screenshot_ui.capture(renderer) {
|
match self.niri.screenshot_ui.capture(renderer) {
|
||||||
Ok((size, pixels)) => {
|
Ok((size, pixels)) => {
|
||||||
if let Err(err) = self.niri.save_screenshot(size, pixels, write_to_disk) {
|
if let Err(err) = self.niri.save_screenshot(size, pixels, write_to_disk, path) {
|
||||||
warn!("error saving screenshot: {err:?}");
|
warn!("error saving screenshot: {err:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5533,6 +5534,7 @@ impl Niri {
|
|||||||
output: &Output,
|
output: &Output,
|
||||||
write_to_disk: bool,
|
write_to_disk: bool,
|
||||||
include_pointer: bool,
|
include_pointer: bool,
|
||||||
|
path: Option<String>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let _span = tracy_client::span!("Niri::screenshot");
|
let _span = tracy_client::span!("Niri::screenshot");
|
||||||
|
|
||||||
@@ -5559,7 +5561,7 @@ impl Niri {
|
|||||||
elements,
|
elements,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.save_screenshot(size, pixels, write_to_disk)
|
self.save_screenshot(size, pixels, write_to_disk, path)
|
||||||
.context("error saving screenshot")
|
.context("error saving screenshot")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5569,6 +5571,7 @@ impl Niri {
|
|||||||
output: &Output,
|
output: &Output,
|
||||||
mapped: &Mapped,
|
mapped: &Mapped,
|
||||||
write_to_disk: bool,
|
write_to_disk: bool,
|
||||||
|
path: Option<String>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let _span = tracy_client::span!("Niri::screenshot_window");
|
let _span = tracy_client::span!("Niri::screenshot_window");
|
||||||
|
|
||||||
@@ -5600,7 +5603,7 @@ impl Niri {
|
|||||||
elements,
|
elements,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.save_screenshot(geo.size, pixels, write_to_disk)
|
self.save_screenshot(geo.size, pixels, write_to_disk, path)
|
||||||
.context("error saving screenshot")
|
.context("error saving screenshot")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5609,14 +5612,20 @@ impl Niri {
|
|||||||
size: Size<i32, Physical>,
|
size: Size<i32, Physical>,
|
||||||
pixels: Vec<u8>,
|
pixels: Vec<u8>,
|
||||||
write_to_disk: bool,
|
write_to_disk: bool,
|
||||||
|
path_arg: Option<String>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let path = write_to_disk
|
let path = write_to_disk
|
||||||
.then(|| match make_screenshot_path(&self.config.borrow()) {
|
.then(|| {
|
||||||
Ok(path) => path,
|
// When given an explicit path, don't try to strftime it or create parents.
|
||||||
|
path_arg.map(|p| (PathBuf::from(p), false)).or_else(|| {
|
||||||
|
match make_screenshot_path(&self.config.borrow()) {
|
||||||
|
Ok(path) => path.map(|p| (p, true)),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("error making screenshot path: {err:?}");
|
warn!("error making screenshot path: {err:?}");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
@@ -5652,16 +5661,21 @@ impl Niri {
|
|||||||
|
|
||||||
let mut image_path = None;
|
let mut image_path = None;
|
||||||
|
|
||||||
if let Some(path) = path {
|
if let Some((path, create_parent)) = path {
|
||||||
debug!("saving screenshot to {path:?}");
|
debug!("saving screenshot to {path:?}");
|
||||||
|
|
||||||
|
if create_parent {
|
||||||
if let Some(parent) = path.parent() {
|
if let Some(parent) = path.parent() {
|
||||||
|
// Relative paths with one component, i.e. "test.png", have Some("") parent.
|
||||||
|
if !parent.as_os_str().is_empty() {
|
||||||
if let Err(err) = std::fs::create_dir(parent) {
|
if let Err(err) = std::fs::create_dir(parent) {
|
||||||
if err.kind() != std::io::ErrorKind::AlreadyExists {
|
if err.kind() != std::io::ErrorKind::AlreadyExists {
|
||||||
warn!("error creating screenshot directory: {err:?}");
|
warn!("error creating screenshot directory: {err:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match std::fs::write(&path, buf) {
|
match std::fs::write(&path, buf) {
|
||||||
Ok(()) => image_path = Some(path),
|
Ok(()) => image_path = Some(path),
|
||||||
|
|||||||
+10
-10
@@ -263,7 +263,7 @@ fn collect_actions(config: &Config) -> Vec<&Action> {
|
|||||||
// Screenshot is not as important, can omit if not bound.
|
// Screenshot is not as important, can omit if not bound.
|
||||||
if let Some(bind) = binds
|
if let Some(bind) = binds
|
||||||
.iter()
|
.iter()
|
||||||
.find(|bind| matches!(bind.action, Action::Screenshot(_)))
|
.find(|bind| matches!(bind.action, Action::Screenshot(_, _)))
|
||||||
{
|
{
|
||||||
actions.push(&bind.action);
|
actions.push(&bind.action);
|
||||||
}
|
}
|
||||||
@@ -479,7 +479,7 @@ fn action_name(action: &Action) -> String {
|
|||||||
String::from("Switch Focus Between Floating and Tiling")
|
String::from("Switch Focus Between Floating and Tiling")
|
||||||
}
|
}
|
||||||
Action::ToggleOverview => String::from("Open the Overview"),
|
Action::ToggleOverview => String::from("Open the Overview"),
|
||||||
Action::Screenshot(_) => String::from("Take a Screenshot"),
|
Action::Screenshot(_, _) => String::from("Take a Screenshot"),
|
||||||
Action::Spawn(args) => format!(
|
Action::Spawn(args) => format!(
|
||||||
"Spawn <span face='monospace' bgcolor='#000000'>{}</span>",
|
"Spawn <span face='monospace' bgcolor='#000000'>{}</span>",
|
||||||
args.first().unwrap_or(&String::new())
|
args.first().unwrap_or(&String::new())
|
||||||
@@ -620,7 +620,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_format_bind() {
|
fn test_format_bind() {
|
||||||
// Not bound.
|
// Not bound.
|
||||||
assert_snapshot!(check("", Action::Screenshot(true)), @" (not bound) : Take a Screenshot");
|
assert_snapshot!(check("", Action::Screenshot(true, None)), @" (not bound) : Take a Screenshot");
|
||||||
|
|
||||||
// Bound with a default title.
|
// Bound with a default title.
|
||||||
assert_snapshot!(
|
assert_snapshot!(
|
||||||
@@ -628,7 +628,7 @@ mod tests {
|
|||||||
r#"binds {
|
r#"binds {
|
||||||
Mod+P { screenshot; }
|
Mod+P { screenshot; }
|
||||||
}"#,
|
}"#,
|
||||||
Action::Screenshot(true),
|
Action::Screenshot(true, None),
|
||||||
),
|
),
|
||||||
@" Super + P : Take a Screenshot"
|
@" Super + P : Take a Screenshot"
|
||||||
);
|
);
|
||||||
@@ -639,7 +639,7 @@ mod tests {
|
|||||||
r#"binds {
|
r#"binds {
|
||||||
Mod+P hotkey-overlay-title="Hello" { screenshot; }
|
Mod+P hotkey-overlay-title="Hello" { screenshot; }
|
||||||
}"#,
|
}"#,
|
||||||
Action::Screenshot(true),
|
Action::Screenshot(true, None),
|
||||||
),
|
),
|
||||||
@" Super + P : Hello"
|
@" Super + P : Hello"
|
||||||
);
|
);
|
||||||
@@ -651,7 +651,7 @@ mod tests {
|
|||||||
Mod+P { screenshot; }
|
Mod+P { screenshot; }
|
||||||
Print { screenshot; }
|
Print { screenshot; }
|
||||||
}"#,
|
}"#,
|
||||||
Action::Screenshot(true),
|
Action::Screenshot(true, None),
|
||||||
),
|
),
|
||||||
@" Super + P : Take a Screenshot"
|
@" Super + P : Take a Screenshot"
|
||||||
);
|
);
|
||||||
@@ -663,7 +663,7 @@ mod tests {
|
|||||||
Mod+P { screenshot; }
|
Mod+P { screenshot; }
|
||||||
Print hotkey-overlay-title="My Cool Bind" { screenshot; }
|
Print hotkey-overlay-title="My Cool Bind" { screenshot; }
|
||||||
}"#,
|
}"#,
|
||||||
Action::Screenshot(true),
|
Action::Screenshot(true, None),
|
||||||
),
|
),
|
||||||
@" PrtSc : My Cool Bind"
|
@" PrtSc : My Cool Bind"
|
||||||
);
|
);
|
||||||
@@ -675,7 +675,7 @@ mod tests {
|
|||||||
Mod+P hotkey-overlay-title="First" { screenshot; }
|
Mod+P hotkey-overlay-title="First" { screenshot; }
|
||||||
Print hotkey-overlay-title="My Cool Bind" { screenshot; }
|
Print hotkey-overlay-title="My Cool Bind" { screenshot; }
|
||||||
}"#,
|
}"#,
|
||||||
Action::Screenshot(true),
|
Action::Screenshot(true, None),
|
||||||
),
|
),
|
||||||
@" Super + P : First"
|
@" Super + P : First"
|
||||||
);
|
);
|
||||||
@@ -687,7 +687,7 @@ mod tests {
|
|||||||
Mod+P { screenshot; }
|
Mod+P { screenshot; }
|
||||||
Print hotkey-overlay-title=null { screenshot; }
|
Print hotkey-overlay-title=null { screenshot; }
|
||||||
}"#,
|
}"#,
|
||||||
Action::Screenshot(true),
|
Action::Screenshot(true, None),
|
||||||
),
|
),
|
||||||
@"None"
|
@"None"
|
||||||
);
|
);
|
||||||
@@ -699,7 +699,7 @@ mod tests {
|
|||||||
Mod+P hotkey-overlay-title="Hello" { screenshot; }
|
Mod+P hotkey-overlay-title="Hello" { screenshot; }
|
||||||
Print hotkey-overlay-title=null { screenshot; }
|
Print hotkey-overlay-title=null { screenshot; }
|
||||||
}"#,
|
}"#,
|
||||||
Action::Screenshot(true),
|
Action::Screenshot(true, None),
|
||||||
),
|
),
|
||||||
@" Super + P : Hello"
|
@" Super + P : Hello"
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ pub enum ScreenshotUi {
|
|||||||
open_anim: Animation,
|
open_anim: Animation,
|
||||||
clock: Clock,
|
clock: Clock,
|
||||||
config: Rc<RefCell<Config>>,
|
config: Rc<RefCell<Config>>,
|
||||||
|
path: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +142,7 @@ impl ScreenshotUi {
|
|||||||
screenshots: HashMap<Output, [OutputScreenshot; 3]>,
|
screenshots: HashMap<Output, [OutputScreenshot; 3]>,
|
||||||
default_output: Output,
|
default_output: Output,
|
||||||
show_pointer: bool,
|
show_pointer: bool,
|
||||||
|
path: Option<String>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if screenshots.is_empty() {
|
if screenshots.is_empty() {
|
||||||
return false;
|
return false;
|
||||||
@@ -235,6 +237,7 @@ impl ScreenshotUi {
|
|||||||
open_anim,
|
open_anim,
|
||||||
clock: clock.clone(),
|
clock: clock.clone(),
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
|
path,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.update_buffers();
|
self.update_buffers();
|
||||||
|
|||||||
Reference in New Issue
Block a user