Implement do-screen-transition action

This commit is contained in:
Ivan Molodetskikh
2024-05-07 22:06:43 +04:00
parent 9847a652af
commit 5248e53499
7 changed files with 207 additions and 0 deletions
+2
View File
@@ -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,
+6
View File
@@ -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.
+5
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+62
View File
@@ -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,
))
}
}
+35
View File
@@ -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
```