From 63b252e47d7037ad079e033866786f89c73fbcdd Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 20 Jul 2022 17:25:36 +0200 Subject: [PATCH] input: Add screenshotting shortcut --- Cargo.lock | 90 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + config.ron | 1 + src/backend/kms/mod.rs | 2 +- src/backend/render/mod.rs | 26 +++++------ src/config/mod.rs | 1 + src/input/mod.rs | 29 +++++++++++++ src/state.rs | 57 ++++++++++++++++++++++++- 8 files changed, 193 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0570b6f0..1670c01f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + [[package]] name = "ahash" version = "0.7.6" @@ -160,6 +166,18 @@ version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +[[package]] +name = "bytemuck" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c53dfa917ec274df8ed3c572698f381a24eef2efba9492d797301b72b6db408a" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "calloop" version = "0.9.3" @@ -242,6 +260,12 @@ dependencies = [ "objc", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "core-foundation" version = "0.7.0" @@ -335,6 +359,7 @@ dependencies = [ "edid-rs", "egui", "id_tree", + "image", "indexmap", "lazy_static", "libsystemd", @@ -379,6 +404,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "crossbeam-channel" version = "0.5.5" @@ -450,6 +484,15 @@ dependencies = [ "syn", ] +[[package]] +name = "deflate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" +dependencies = [ + "adler32", +] + [[package]] name = "digest" version = "0.10.3" @@ -757,6 +800,20 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "image" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30ca2ecf7666107ff827a8e481de6a132a9b687ed3bb20bb1c144a36c00964" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-rational", + "num-traits", + "png", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -1081,6 +1138,27 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -1213,6 +1291,18 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "png" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide", +] + [[package]] name = "ppv-lite86" version = "0.2.16" diff --git a/Cargo.toml b/Cargo.toml index 30c1fd4b..fbef1854 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ serde_json = "1" sendfd = "0.4.1" egui = { version = "0.18.1", optional = true } edid-rs = { version = "0.1" } +image = { version = "0.24.3", default-features = false, features = ["png"] } lazy_static = "1.4.0" thiserror = "1.0.26" regex = "1" diff --git a/config.ron b/config.ron index 2eed5d0b..9969716f 100644 --- a/config.ron +++ b/config.ron @@ -37,6 +37,7 @@ (modifiers: [Logo], key: "y"): ToggleTiling, (modifiers: [Logo], key: "g"): ToggleWindowFloating, (modifiers: [Logo, Shift], key: "f"): Fullscreen, + (modifiers: [Logo, Shift], key: "s"): Screenshot, //TODO: ability to select default web browser (modifiers: [Logo], key: "b"): Spawn("firefox"), //TODO: ability to select default file browser diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 822f1e28..e752e500 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -63,7 +63,7 @@ use socket::*; pub struct KmsState { devices: HashMap, - api: GpuManager, + pub api: GpuManager, pub primary: DrmNode, session: AutoSession, signaler: Signaler, diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 78f7b5a2..5885905b 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -150,7 +150,7 @@ pub fn render_output( state: &mut Common, output: &Output, hardware_cursor: bool, - #[cfg(feature = "debug")] fps: &mut Fps, + #[cfg(feature = "debug")] fps: Option<&mut Fps>, ) -> Result>>, RenderError> where R: Renderer + ImportAll + AsGles2Renderer, @@ -198,7 +198,7 @@ fn render_desktop( state: &mut Common, output: &Output, hardware_cursor: bool, - #[cfg(feature = "debug")] fps: &mut Fps, + #[cfg(feature = "debug")] fps: Option<&mut Fps>, ) -> Result>>, RenderError> where R: Renderer + ImportAll + AsGles2Renderer, @@ -216,14 +216,16 @@ where .unwrap_or(Rectangle::from_loc_and_size((0, 0), (0, 0))); let scale = output.current_scale().fractional_scale(); - let fps_overlay = fps_ui( - _gpu, - state, - fps, - output_geo.to_f64().to_physical(scale), - scale, - ); - custom_elements.push(fps_overlay.into()); + if let Some(fps) = fps { + let fps_overlay = fps_ui( + _gpu, + state, + fps, + output_geo.to_f64().to_physical(scale), + scale, + ); + custom_elements.push(fps_overlay.into()); + } let area = Rectangle::::from_loc_and_size( state @@ -282,7 +284,7 @@ fn render_fullscreen( state: &mut Common, output: &Output, hardware_cursor: bool, - #[cfg(feature = "debug")] fps: &mut Fps, + #[cfg(feature = "debug")] fps: Option<&mut Fps>, ) -> Result>>, RenderError> where R: Renderer + ImportAll + AsGles2Renderer, @@ -296,7 +298,7 @@ where let mut custom_elements = Vec::::new(); #[cfg(feature = "debug")] - { + if let Some(fps) = fps { let output_geo = output.geometry(); let fps_overlay = fps_ui( _gpu, diff --git a/src/config/mod.rs b/src/config/mod.rs index 7bbeed5d..d3e18ffe 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -752,5 +752,6 @@ pub enum Action { ToggleTiling, ToggleWindowFloating, Fullscreen, + Screenshot, Spawn(String), } diff --git a/src/input/mod.rs b/src/input/mod.rs index b8150b15..52beaa22 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -387,6 +387,35 @@ impl State { slog_scope::warn!("Failed to spawn: {}", err); } } + Action::Screenshot => { + let home = match std::env::var("HOME") { + Ok(home) => home, + Err(err) => { + slog_scope::error!("$HOME is not set, can't save screenshots: {}", err); + break; + } + }; + let timestamp = match std::time::SystemTime::UNIX_EPOCH.elapsed() { + Ok(duration) => duration.as_secs(), + Err(err) => { + slog_scope::error!("Unable to get timestamp, can't save screenshots: {}", err); + break; + } + }; + for output in self.common.shell.outputs.clone().into_iter() { + match self.backend.offscreen_for_output(&output, &mut self.common) { + Ok(buffer) => { + let mut path = std::path::PathBuf::new(); + path.push(&home); + path.push(format!("{}_{}.png", output.name(), timestamp)); + if let Err(err) = buffer.save(&path) { + slog_scope::error!("Unable to save screenshot at {}: {}", path.display(), err); + } + }, + Err(err) => slog_scope::error!("Could not save screenshot for output {}: {}", output.name(), err), + } + } + } } } break; diff --git a/src/state.rs b/src/state.rs index 46d4b6e8..d6c9853f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,7 +8,7 @@ use crate::{ wayland::protocols::{ drm::WlDrmState, output_configuration::OutputConfigurationState, workspace::WorkspaceClientState, - }, + }, utils::prelude::OutputExt, }; use smithay::{ backend::drm::DrmNode, @@ -199,6 +199,61 @@ impl BackendData { _ => unreachable!("No backend was initialized"), } } + + pub fn offscreen_for_output( + &mut self, + output: &Output, + state: &mut Common, + ) -> anyhow::Result, Vec>> { + use anyhow::Context; + use smithay::{ + backend::{ + drm::NodeType, + renderer::{ + Bind, Offscreen, ExportMem, + gles2::Gles2Renderbuffer, + }, + }, + utils::Rectangle, + }; + use crate::backend::render::render_output; + + let mut _tmp_multirenderer = None; + let (node, renderer) = match self { + BackendData::Winit(winit) => (None, winit.backend.renderer()), + BackendData::X11(x11) => (None, &mut x11.renderer), + BackendData::Kms(kms) => { + let node = kms.target_node_for_output(output) + .unwrap_or(kms.primary) + .node_with_type(NodeType::Render) + .with_context(|| "Unable to find node")??; + _tmp_multirenderer = Some(kms.api.renderer::(&node, &node)?); + (Some(node), _tmp_multirenderer.as_mut().map(|x| x.as_mut()).unwrap()) + }, + BackendData::Unset => unreachable!(), + }; + + let size = output.geometry().size.to_f64().to_buffer( + output.current_scale().fractional_scale(), + output.current_transform().into() + ).to_i32_round(); + let buffer = Offscreen::::create_buffer(renderer, size)?; + renderer.bind(buffer)?; + render_output( + node.as_ref(), + renderer, + 0, + state, + output, + false, + #[cfg(feature = "debug")] + None, + )?; + let mapping = renderer.copy_framebuffer(Rectangle::from_loc_and_size((0, 0), size))?; + let data = Vec::from(renderer.map_texture(&mapping)?); + + Ok(image::ImageBuffer::from_raw(size.w as u32, size.h as u32, data).with_context(|| "buffer smaller then dimensions")?) + } } impl State {