mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
Implement do-screen-transition action
This commit is contained in:
@@ -842,6 +842,7 @@ pub enum Action {
|
||||
DebugToggleOpaqueRegions,
|
||||
DebugToggleDamage,
|
||||
Spawn(#[knuffel(arguments)] Vec<String>),
|
||||
DoScreenTransition(#[knuffel(property(name = "delay-ms"))] Option<u16>),
|
||||
#[knuffel(skip)]
|
||||
ConfirmScreenshot,
|
||||
#[knuffel(skip)]
|
||||
@@ -914,6 +915,7 @@ impl From<niri_ipc::Action> for Action {
|
||||
niri_ipc::Action::Quit { skip_confirmation } => Self::Quit(skip_confirmation),
|
||||
niri_ipc::Action::PowerOffMonitors => Self::PowerOffMonitors,
|
||||
niri_ipc::Action::Spawn { command } => Self::Spawn(command),
|
||||
niri_ipc::Action::DoScreenTransition { delay_ms } => Self::DoScreenTransition(delay_ms),
|
||||
niri_ipc::Action::Screenshot => Self::Screenshot,
|
||||
niri_ipc::Action::ScreenshotScreen => Self::ScreenshotScreen,
|
||||
niri_ipc::Action::ScreenshotWindow => Self::ScreenshotWindow,
|
||||
|
||||
@@ -84,6 +84,12 @@ pub enum Action {
|
||||
#[cfg_attr(feature = "clap", arg(last = true, required = true))]
|
||||
command: Vec<String>,
|
||||
},
|
||||
/// Do a screen transition.
|
||||
DoScreenTransition {
|
||||
/// Delay in milliseconds for the screen to freeze before starting the transition.
|
||||
#[cfg_attr(feature = "clap", arg(short, long))]
|
||||
delay_ms: Option<u16>,
|
||||
},
|
||||
/// Open the screenshot UI.
|
||||
Screenshot,
|
||||
/// Screenshot the focused screen.
|
||||
|
||||
@@ -378,6 +378,11 @@ impl State {
|
||||
Action::Spawn(command) => {
|
||||
spawn(command);
|
||||
}
|
||||
Action::DoScreenTransition(delay_ms) => {
|
||||
self.backend.with_primary_renderer(|renderer| {
|
||||
self.niri.do_screen_transition(renderer, delay_ms);
|
||||
});
|
||||
}
|
||||
Action::ScreenshotScreen => {
|
||||
let active = self.niri.layout.active_output().cloned();
|
||||
if let Some(active) = active {
|
||||
|
||||
+96
@@ -19,6 +19,7 @@ use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRen
|
||||
use smithay::backend::renderer::element::surface::{
|
||||
render_elements_from_surface_tree, WaylandSurfaceRenderElement,
|
||||
};
|
||||
use smithay::backend::renderer::element::texture::TextureBuffer;
|
||||
use smithay::backend::renderer::element::utils::{
|
||||
select_dmabuf_feedback, Relocate, RelocateRenderElement,
|
||||
};
|
||||
@@ -109,6 +110,7 @@ use crate::protocols::gamma_control::GammaControlManagerState;
|
||||
use crate::protocols::screencopy::{Screencopy, ScreencopyManagerState};
|
||||
use crate::pw_utils::{Cast, PipeWire};
|
||||
use crate::render_helpers::debug::draw_opaque_regions;
|
||||
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::{
|
||||
render_to_shm, render_to_texture, render_to_vec, shaders, RenderTarget,
|
||||
@@ -117,6 +119,7 @@ use crate::scroll_tracker::ScrollTracker;
|
||||
use crate::ui::config_error_notification::ConfigErrorNotification;
|
||||
use crate::ui::exit_confirm_dialog::ExitConfirmDialog;
|
||||
use crate::ui::hotkey_overlay::HotkeyOverlay;
|
||||
use crate::ui::screen_transition::{self, ScreenTransition};
|
||||
use crate::ui::screenshot_ui::{ScreenshotUi, ScreenshotUiRenderElement};
|
||||
use crate::utils::scale::guess_monitor_scale;
|
||||
use crate::utils::spawning::CHILD_ENV;
|
||||
@@ -296,6 +299,7 @@ pub struct OutputState {
|
||||
pub lock_render_state: LockRenderState,
|
||||
pub lock_surface: Option<LockSurface>,
|
||||
pub lock_color_buffer: SolidColorBuffer,
|
||||
screen_transition: Option<ScreenTransition>,
|
||||
/// Damage tracker used for the debug damage visualization.
|
||||
pub debug_damage_tracker: OutputDamageTracker,
|
||||
}
|
||||
@@ -1725,6 +1729,7 @@ impl Niri {
|
||||
lock_render_state,
|
||||
lock_surface: None,
|
||||
lock_color_buffer: SolidColorBuffer::new(size, CLEAR_COLOR_LOCKED),
|
||||
screen_transition: None,
|
||||
debug_damage_tracker: OutputDamageTracker::from_output(&output),
|
||||
};
|
||||
let rv = self.output_state.insert(output.clone(), state);
|
||||
@@ -2445,6 +2450,14 @@ impl Niri {
|
||||
elements = self.pointer_element(renderer, output);
|
||||
}
|
||||
|
||||
// Next, the screen transition texture.
|
||||
{
|
||||
let state = self.output_state.get(output).unwrap();
|
||||
if let Some(transition) = &state.screen_transition {
|
||||
elements.push(transition.render(target).into());
|
||||
}
|
||||
}
|
||||
|
||||
// Next, the exit confirm dialog.
|
||||
if let Some(dialog) = &self.exit_confirm_dialog {
|
||||
if let Some(element) = dialog.render(renderer, output) {
|
||||
@@ -2593,6 +2606,14 @@ impl Niri {
|
||||
if self.monitors_active {
|
||||
// Update from the config and advance the animations.
|
||||
self.layout.advance_animations(target_presentation_time);
|
||||
|
||||
if let Some(transition) = &mut state.screen_transition {
|
||||
transition.advance_animations(target_presentation_time);
|
||||
if transition.is_done() {
|
||||
state.screen_transition = None;
|
||||
}
|
||||
}
|
||||
|
||||
state.unfinished_animations_remain = self
|
||||
.layout
|
||||
.monitor_for_output(output)
|
||||
@@ -2609,6 +2630,9 @@ impl Niri {
|
||||
.cursor_manager
|
||||
.is_current_cursor_animated(output.current_scale().integer_scale());
|
||||
|
||||
// Also keep redrawing during a screen transition.
|
||||
state.unfinished_animations_remain |= state.screen_transition.is_some();
|
||||
|
||||
self.layout.update_render_elements(output);
|
||||
|
||||
// Render.
|
||||
@@ -3718,6 +3742,77 @@ impl Niri {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_screen_transition(&mut self, renderer: &mut GlesRenderer, delay_ms: Option<u16>) {
|
||||
let textures: Vec<_> = self
|
||||
.output_state
|
||||
.keys()
|
||||
.cloned()
|
||||
.filter_map(|output| {
|
||||
let size = output.current_mode().unwrap().size;
|
||||
let transform = output.current_transform();
|
||||
let size = transform.transform_size(size);
|
||||
|
||||
let scale = Scale::from(output.current_scale().fractional_scale());
|
||||
let targets = [
|
||||
RenderTarget::Output,
|
||||
RenderTarget::Screencast,
|
||||
RenderTarget::ScreenCapture,
|
||||
];
|
||||
let textures = targets.map(|target| {
|
||||
let elements = self.render::<GlesRenderer>(renderer, &output, false, target);
|
||||
let elements = elements.iter().rev();
|
||||
|
||||
let res = render_to_texture(
|
||||
renderer,
|
||||
size,
|
||||
scale,
|
||||
Transform::Normal,
|
||||
Fourcc::Abgr8888,
|
||||
elements,
|
||||
);
|
||||
|
||||
if let Err(err) = &res {
|
||||
warn!("error rendering output {}: {err:?}", output.name());
|
||||
}
|
||||
|
||||
res
|
||||
});
|
||||
|
||||
if textures.iter().any(|res| res.is_err()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let textures = textures.map(|res| {
|
||||
let texture = res.unwrap().0;
|
||||
TextureBuffer::from_texture(
|
||||
renderer,
|
||||
texture,
|
||||
output.current_scale().integer_scale(),
|
||||
Transform::Normal,
|
||||
Some(vec![Rectangle::from_loc_and_size(
|
||||
(0, 0),
|
||||
size.to_logical(1).to_buffer(1, Transform::Normal),
|
||||
)]),
|
||||
)
|
||||
});
|
||||
|
||||
Some((output, textures))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let delay = delay_ms.map_or(screen_transition::DELAY, |d| {
|
||||
Duration::from_millis(u64::from(d))
|
||||
});
|
||||
let start_at = get_monotonic_time() + delay;
|
||||
for (output, from_texture) in textures {
|
||||
let state = self.output_state.get_mut(&output).unwrap();
|
||||
state.screen_transition = Some(ScreenTransition::new(from_texture, start_at));
|
||||
}
|
||||
|
||||
// We don't actually need to queue a redraw because the point is to freeze the screen for a
|
||||
// bit, and even if the delay was zero, we're drawing the same contents anyway.
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClientState {
|
||||
@@ -3739,6 +3834,7 @@ niri_render_elements! {
|
||||
NamedPointer = MemoryRenderBufferRenderElement<R>,
|
||||
SolidColor = SolidColorRenderElement,
|
||||
ScreenshotUi = ScreenshotUiRenderElement,
|
||||
Texture = PrimaryGpuTextureRenderElement,
|
||||
// Used for the CPU-rendered panels.
|
||||
RelocatedMemoryBuffer = RelocateRenderElement<MemoryRenderBufferRenderElement<R>>,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod config_error_notification;
|
||||
pub mod exit_confirm_dialog;
|
||||
pub mod hotkey_overlay;
|
||||
pub mod screen_transition;
|
||||
pub mod screenshot_ui;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use smithay::backend::renderer::element::texture::{TextureBuffer, TextureRenderElement};
|
||||
use smithay::backend::renderer::element::Kind;
|
||||
use smithay::backend::renderer::gles::GlesTexture;
|
||||
|
||||
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
use crate::render_helpers::RenderTarget;
|
||||
|
||||
pub const DELAY: Duration = Duration::from_millis(250);
|
||||
pub const DURATION: Duration = Duration::from_millis(500);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ScreenTransition {
|
||||
/// Texture to crossfade from for each render target.
|
||||
from_texture: [TextureBuffer<GlesTexture>; 3],
|
||||
/// Monotonic time when to start the crossfade.
|
||||
start_at: Duration,
|
||||
/// Current crossfade alpha.
|
||||
alpha: f32,
|
||||
}
|
||||
|
||||
impl ScreenTransition {
|
||||
pub fn new(from_texture: [TextureBuffer<GlesTexture>; 3], start_at: Duration) -> Self {
|
||||
Self {
|
||||
from_texture,
|
||||
start_at,
|
||||
alpha: 1.,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance_animations(&mut self, current_time: Duration) {
|
||||
if self.start_at + DURATION <= current_time {
|
||||
self.alpha = 0.;
|
||||
} else if self.start_at <= current_time {
|
||||
self.alpha = 1. - (current_time - self.start_at).as_secs_f32() / DURATION.as_secs_f32();
|
||||
} else {
|
||||
self.alpha = 1.;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.alpha == 0.
|
||||
}
|
||||
|
||||
pub fn render(&self, target: RenderTarget) -> PrimaryGpuTextureRenderElement {
|
||||
let idx = match target {
|
||||
RenderTarget::Output => 0,
|
||||
RenderTarget::Screencast => 1,
|
||||
RenderTarget::ScreenCapture => 2,
|
||||
};
|
||||
|
||||
PrimaryGpuTextureRenderElement(TextureRenderElement::from_texture_buffer(
|
||||
(0., 0.),
|
||||
&self.from_texture[idx],
|
||||
Some(self.alpha),
|
||||
None,
|
||||
None,
|
||||
Kind::Unspecified,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -186,3 +186,38 @@ binds {
|
||||
Mod+Shift+E { quit skip-confirmation=true; }
|
||||
}
|
||||
```
|
||||
|
||||
#### `do-screen-transition`
|
||||
|
||||
Freeze the screen for a brief moment then crossfade to the new contents.
|
||||
|
||||
```
|
||||
binds {
|
||||
Mod+Return { do-screen-transition; }
|
||||
}
|
||||
```
|
||||
|
||||
This action is mainly useful to trigger from scripts changing the system theme or style (between light and dark for example).
|
||||
It makes transitions like this, where windows change their style one by one, look smooth and synchronized.
|
||||
|
||||
For example, using the GNOME color scheme setting:
|
||||
|
||||
```shell
|
||||
niri msg action do-screen-transition
|
||||
dconf write /org/gnome/desktop/interface/color-scheme "\"prefer-dark\""
|
||||
```
|
||||
|
||||
By default, the screen is frozen for 250 ms to give windows time to redraw, before the crossfade.
|
||||
You can set this delay like this:
|
||||
|
||||
```
|
||||
binds {
|
||||
Mod+Return { do-screen-transition delay-ms=100; }
|
||||
}
|
||||
```
|
||||
|
||||
Or, in scripts:
|
||||
|
||||
```shell
|
||||
niri msg action do-screen-transition --delay-ms 100
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user