mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-24 02:01:18 +07:00
Add screenshot-window show-pointer=true
This commit is contained in:
@@ -382,6 +382,17 @@ binds {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<sup>Since: next release</sup> You can show the mouse pointer on window screenshots with the `show-pointer=true` property.
|
||||||
|
The pointer will be included only if the window is currently receiving pointer input (usually this means the pointer is on top of the window).
|
||||||
|
|
||||||
|
```kdl
|
||||||
|
binds {
|
||||||
|
// The pointer will be visible on the screenshot
|
||||||
|
// if it's on top of the window.
|
||||||
|
Alt+Print { screenshot-window show-pointer=true; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### `toggle-keyboard-shortcuts-inhibit`
|
#### `toggle-keyboard-shortcuts-inhibit`
|
||||||
|
|
||||||
<sup>Since: 25.02</sup>
|
<sup>Since: 25.02</sup>
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ pub enum Action {
|
|||||||
),
|
),
|
||||||
ScreenshotWindow(
|
ScreenshotWindow(
|
||||||
#[knuffel(property(name = "write-to-disk"), default = true)] bool,
|
#[knuffel(property(name = "write-to-disk"), default = true)] bool,
|
||||||
|
#[knuffel(property(name = "show-pointer"), default = false)] bool,
|
||||||
// Path; not settable from knuffel
|
// Path; not settable from knuffel
|
||||||
Option<String>,
|
Option<String>,
|
||||||
),
|
),
|
||||||
@@ -139,6 +140,7 @@ pub enum Action {
|
|||||||
ScreenshotWindowById {
|
ScreenshotWindowById {
|
||||||
id: u64,
|
id: u64,
|
||||||
write_to_disk: bool,
|
write_to_disk: bool,
|
||||||
|
show_pointer: bool,
|
||||||
path: Option<String>,
|
path: Option<String>,
|
||||||
},
|
},
|
||||||
ToggleKeyboardShortcutsInhibit,
|
ToggleKeyboardShortcutsInhibit,
|
||||||
@@ -407,15 +409,18 @@ impl From<niri_ipc::Action> for Action {
|
|||||||
niri_ipc::Action::ScreenshotWindow {
|
niri_ipc::Action::ScreenshotWindow {
|
||||||
id: None,
|
id: None,
|
||||||
write_to_disk,
|
write_to_disk,
|
||||||
|
show_pointer,
|
||||||
path,
|
path,
|
||||||
} => Self::ScreenshotWindow(write_to_disk, path),
|
} => Self::ScreenshotWindow(write_to_disk, show_pointer, path),
|
||||||
niri_ipc::Action::ScreenshotWindow {
|
niri_ipc::Action::ScreenshotWindow {
|
||||||
id: Some(id),
|
id: Some(id),
|
||||||
write_to_disk,
|
write_to_disk,
|
||||||
|
show_pointer,
|
||||||
path,
|
path,
|
||||||
} => Self::ScreenshotWindowById {
|
} => Self::ScreenshotWindowById {
|
||||||
id,
|
id,
|
||||||
write_to_disk,
|
write_to_disk,
|
||||||
|
show_pointer,
|
||||||
path,
|
path,
|
||||||
},
|
},
|
||||||
niri_ipc::Action::ToggleKeyboardShortcutsInhibit {} => {
|
niri_ipc::Action::ToggleKeyboardShortcutsInhibit {} => {
|
||||||
|
|||||||
@@ -264,6 +264,13 @@ pub enum Action {
|
|||||||
#[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,
|
||||||
|
|
||||||
|
/// Whether to include the mouse pointer in the screenshot.
|
||||||
|
///
|
||||||
|
/// The pointer will be included only if the window is currently receiving pointer input
|
||||||
|
/// (usually this means the pointer is on top of the window).
|
||||||
|
#[cfg_attr(feature = "clap", arg(short = 'p', long, action = clap::ArgAction::Set, default_value_t = false))]
|
||||||
|
show_pointer: bool,
|
||||||
|
|
||||||
/// Path to save the screenshot to.
|
/// Path to save the screenshot to.
|
||||||
///
|
///
|
||||||
/// The path must be absolute, otherwise an error is returned.
|
/// The path must be absolute, otherwise an error is returned.
|
||||||
|
|||||||
+4
-1
@@ -743,7 +743,7 @@ impl State {
|
|||||||
self.open_screenshot_ui(show_cursor, path);
|
self.open_screenshot_ui(show_cursor, path);
|
||||||
self.niri.cancel_mru();
|
self.niri.cancel_mru();
|
||||||
}
|
}
|
||||||
Action::ScreenshotWindow(write_to_disk, path) => {
|
Action::ScreenshotWindow(write_to_disk, show_pointer, 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| {
|
||||||
@@ -752,6 +752,7 @@ impl State {
|
|||||||
output,
|
output,
|
||||||
mapped,
|
mapped,
|
||||||
write_to_disk,
|
write_to_disk,
|
||||||
|
show_pointer,
|
||||||
path,
|
path,
|
||||||
) {
|
) {
|
||||||
warn!("error taking screenshot: {err:?}");
|
warn!("error taking screenshot: {err:?}");
|
||||||
@@ -762,6 +763,7 @@ impl State {
|
|||||||
Action::ScreenshotWindowById {
|
Action::ScreenshotWindowById {
|
||||||
id,
|
id,
|
||||||
write_to_disk,
|
write_to_disk,
|
||||||
|
show_pointer,
|
||||||
path,
|
path,
|
||||||
} => {
|
} => {
|
||||||
let mut windows = self.niri.layout.windows();
|
let mut windows = self.niri.layout.windows();
|
||||||
@@ -774,6 +776,7 @@ impl State {
|
|||||||
output,
|
output,
|
||||||
mapped,
|
mapped,
|
||||||
write_to_disk,
|
write_to_disk,
|
||||||
|
show_pointer,
|
||||||
path,
|
path,
|
||||||
) {
|
) {
|
||||||
warn!("error taking screenshot: {err:?}");
|
warn!("error taking screenshot: {err:?}");
|
||||||
|
|||||||
+71
-5
@@ -139,7 +139,9 @@ use crate::layer::mapped::LayerSurfaceRenderElement;
|
|||||||
use crate::layer::MappedLayer;
|
use crate::layer::MappedLayer;
|
||||||
use crate::layout::tile::TileRenderElement;
|
use crate::layout::tile::TileRenderElement;
|
||||||
use crate::layout::workspace::{Workspace, WorkspaceId};
|
use crate::layout::workspace::{Workspace, WorkspaceId};
|
||||||
use crate::layout::{HitType, Layout, LayoutElement as _, MonitorRenderElement};
|
use crate::layout::{
|
||||||
|
HitType, Layout, LayoutElement as _, LayoutElementRenderElement, MonitorRenderElement,
|
||||||
|
};
|
||||||
use crate::niri_render_elements;
|
use crate::niri_render_elements;
|
||||||
use crate::protocols::ext_workspace::{self, ExtWorkspaceManagerState};
|
use crate::protocols::ext_workspace::{self, ExtWorkspaceManagerState};
|
||||||
use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
|
use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
|
||||||
@@ -5665,6 +5667,7 @@ impl Niri {
|
|||||||
output: &Output,
|
output: &Output,
|
||||||
mapped: &Mapped,
|
mapped: &Mapped,
|
||||||
write_to_disk: bool,
|
write_to_disk: bool,
|
||||||
|
show_pointer: bool,
|
||||||
path: Option<String>,
|
path: Option<String>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let _span = tracy_client::span!("Niri::screenshot_window");
|
let _span = tracy_client::span!("Niri::screenshot_window");
|
||||||
@@ -5676,17 +5679,73 @@ impl Niri {
|
|||||||
} else {
|
} else {
|
||||||
mapped.rules().opacity.unwrap_or(1.).clamp(0., 1.)
|
mapped.rules().opacity.unwrap_or(1.).clamp(0., 1.)
|
||||||
};
|
};
|
||||||
// FIXME: pointer.
|
|
||||||
let mut elements = Vec::new();
|
let mut elements: Vec<WindowScreenshotRenderElement<GlesRenderer>> = Vec::new();
|
||||||
|
|
||||||
|
// Add pointer if requested and it's over this window.
|
||||||
|
if show_pointer {
|
||||||
|
if let Some((w, HitType::Input { win_pos })) = &self.pointer_contents.window {
|
||||||
|
if w == &mapped.window {
|
||||||
|
// Grabs can modify the pointer focus, making it different from
|
||||||
|
// pointer_contents. Notably, gestures like Mod+MMB will remove the pointer
|
||||||
|
// focus, and ClickGrab will keep pointer focus on the clicked window even
|
||||||
|
// while it's moving over a different window.
|
||||||
|
//
|
||||||
|
// So, double-check that current_focus() (after grabs) also matches the pointer
|
||||||
|
// contents.
|
||||||
|
let pointer = self.seat.get_pointer().unwrap();
|
||||||
|
|
||||||
|
// The DnD grab is a bit special because it has its own focus (data device)
|
||||||
|
// while the pointer focus is cleared. That focus is not currently exposed from
|
||||||
|
// Smithay, and showing DnD icons on window screenshots seems useful, so let's
|
||||||
|
// just allow it during DnD grabs.
|
||||||
|
let is_dnd_grab = pointer
|
||||||
|
.with_grab(|_, grab| State::is_dnd_grab(grab.as_any()))
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
let current_focus_matches = is_dnd_grab
|
||||||
|
|| pointer
|
||||||
|
.current_focus()
|
||||||
|
.map(|focused| self.find_root_shell_surface(&focused))
|
||||||
|
.is_some_and(|focused| mapped.is_wl_surface(&focused));
|
||||||
|
if current_focus_matches {
|
||||||
|
// win_pos is the window buffer position in output-local logical coords.
|
||||||
|
let win_pos = win_pos.to_physical_precise_round(scale);
|
||||||
|
|
||||||
|
// We don't check for pointer visibility because it can only be Visible or
|
||||||
|
// Hidden, and never Disabled (then it wouldn't have focus). Even when the
|
||||||
|
// pointer is Hidden, we want to render it, since the user explicitly
|
||||||
|
// requested show_pointer = true, and otherwise there's no easy way to
|
||||||
|
// screenshot a window with pointer with hide-when-typing because pressing
|
||||||
|
// the screenshot bind will hide the pointer.
|
||||||
|
self.render_pointer(renderer, output, &mut |elem| {
|
||||||
|
// Pointer elements are at output-local physical coords.
|
||||||
|
// Relocate by -win_pos to make them window-relative.
|
||||||
|
let elem = RelocateRenderElement::from_element(
|
||||||
|
elem,
|
||||||
|
win_pos.upscale(-1),
|
||||||
|
Relocate::Relative,
|
||||||
|
);
|
||||||
|
elements.push(elem.into());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let pointer_count = elements.len();
|
||||||
|
|
||||||
mapped.render(
|
mapped.render(
|
||||||
renderer,
|
renderer,
|
||||||
mapped.window.geometry().loc.to_f64(),
|
mapped.window.geometry().loc.to_f64(),
|
||||||
scale,
|
scale,
|
||||||
alpha,
|
alpha,
|
||||||
RenderTarget::ScreenCapture,
|
RenderTarget::ScreenCapture,
|
||||||
&mut |elem| elements.push(elem),
|
&mut |elem| elements.push(elem.into()),
|
||||||
);
|
);
|
||||||
let geo = encompassing_geo(scale, elements.iter());
|
|
||||||
|
// The pointer is not included in encompassing_geo because we don't want it to expand the
|
||||||
|
// screenshot size.
|
||||||
|
let geo = encompassing_geo(scale, elements.iter().skip(pointer_count));
|
||||||
let elements = elements.iter().rev().map(|elem| {
|
let elements = elements.iter().rev().map(|elem| {
|
||||||
RelocateRenderElement::from_element(elem, geo.loc.upscale(-1), Relocate::Relative)
|
RelocateRenderElement::from_element(elem, geo.loc.upscale(-1), Relocate::Relative)
|
||||||
});
|
});
|
||||||
@@ -6557,6 +6616,13 @@ niri_render_elements! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
niri_render_elements! {
|
||||||
|
WindowScreenshotRenderElement<R> => {
|
||||||
|
Layout = LayoutElementRenderElement<R>,
|
||||||
|
Pointer = RelocateRenderElement<PointerRenderElements<R>>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
niri_render_elements! {
|
niri_render_elements! {
|
||||||
OutputRenderElements<R> => {
|
OutputRenderElements<R> => {
|
||||||
Monitor = MonitorRenderElement<R>,
|
Monitor = MonitorRenderElement<R>,
|
||||||
|
|||||||
Reference in New Issue
Block a user