aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-05-07 22:06:43 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2024-05-07 22:19:11 +0400
commit5248e534995dc21279cfd14651c994cd02353452 (patch)
treea08bd585caad1febbd5be1ba018841c4cb842993
parent9847a652af864109b3543e42d48087a38c0729ad (diff)
downloadniri-5248e534995dc21279cfd14651c994cd02353452.tar.gz
niri-5248e534995dc21279cfd14651c994cd02353452.tar.bz2
niri-5248e534995dc21279cfd14651c994cd02353452.zip
Implement do-screen-transition action
-rw-r--r--niri-config/src/lib.rs2
-rw-r--r--niri-ipc/src/lib.rs6
-rw-r--r--src/input.rs5
-rw-r--r--src/niri.rs96
-rw-r--r--src/ui/mod.rs1
-rw-r--r--src/ui/screen_transition.rs62
-rw-r--r--wiki/Configuration:-Key-Bindings.md35
7 files changed, 207 insertions, 0 deletions
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs
index d9ef55d1..c1fc4b1d 100644
--- a/niri-config/src/lib.rs
+++ b/niri-config/src/lib.rs
@@ -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,
diff --git a/niri-ipc/src/lib.rs b/niri-ipc/src/lib.rs
index bf67acd7..e7489165 100644
--- a/niri-ipc/src/lib.rs
+++ b/niri-ipc/src/lib.rs
@@ -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.
diff --git a/src/input.rs b/src/input.rs
index 63a22450..6a75960a 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -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 {
diff --git a/src/niri.rs b/src/niri.rs
index 7d90baae..1e7f0c14 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -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>>,
}
diff --git a/src/ui/mod.rs b/src/ui/mod.rs
index a63c6f03..b546bda5 100644
--- a/src/ui/mod.rs
+++ b/src/ui/mod.rs
@@ -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;
diff --git a/src/ui/screen_transition.rs b/src/ui/screen_transition.rs
new file mode 100644
index 00000000..0bc10c39
--- /dev/null
+++ b/src/ui/screen_transition.rs
@@ -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,
+ ))
+ }
+}
diff --git a/wiki/Configuration:-Key-Bindings.md b/wiki/Configuration:-Key-Bindings.md
index 550ad620..c9e1b13b 100644
--- a/wiki/Configuration:-Key-Bindings.md
+++ b/wiki/Configuration:-Key-Bindings.md
@@ -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
+```