diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2024-01-19 08:33:54 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2024-01-19 08:33:54 +0400 |
| commit | 4b9cb2f0d3d9b1b2e730844be7d9fe3fb44a5ee1 (patch) | |
| tree | b8c40c33b6cb181ff4cbb3883924a4d9e7e16f6f /src | |
| parent | 3461c66d2ce8fb53e2a709b27868229786604256 (diff) | |
| download | niri-4b9cb2f0d3d9b1b2e730844be7d9fe3fb44a5ee1.tar.gz niri-4b9cb2f0d3d9b1b2e730844be7d9fe3fb44a5ee1.tar.bz2 niri-4b9cb2f0d3d9b1b2e730844be7d9fe3fb44a5ee1.zip | |
Add exit confirmation dialog
Diffstat (limited to 'src')
| -rw-r--r-- | src/exit_confirm_dialog.rs | 162 | ||||
| -rw-r--r-- | src/input.rs | 45 | ||||
| -rw-r--r-- | src/main.rs | 1 | ||||
| -rw-r--r-- | src/niri.rs | 19 |
4 files changed, 224 insertions, 3 deletions
diff --git a/src/exit_confirm_dialog.rs b/src/exit_confirm_dialog.rs new file mode 100644 index 00000000..d3464e04 --- /dev/null +++ b/src/exit_confirm_dialog.rs @@ -0,0 +1,162 @@ +use std::cell::RefCell; +use std::collections::HashMap; + +use pangocairo::cairo::{self, ImageSurface}; +use pangocairo::pango::{Alignment, FontDescription}; +use smithay::backend::renderer::element::memory::{ + MemoryRenderBuffer, MemoryRenderBufferRenderElement, +}; +use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement}; +use smithay::backend::renderer::element::{Element, Kind}; +use smithay::output::Output; +use smithay::reexports::gbm::Format as Fourcc; +use smithay::utils::Transform; + +use crate::render_helpers::NiriRenderer; + +const TEXT: &str = "Are you sure you want to exit niri?\n\n\ + Press <span face='mono' bgcolor='#2C2C2C'> Enter </span> to confirm."; +const PADDING: i32 = 16; +const FONT: &str = "sans 14px"; +const BORDER: i32 = 8; + +pub struct ExitConfirmDialog { + is_open: bool, + buffers: RefCell<HashMap<i32, Option<MemoryRenderBuffer>>>, +} + +pub type ExitConfirmDialogRenderElement<R> = + RelocateRenderElement<MemoryRenderBufferRenderElement<R>>; + +impl ExitConfirmDialog { + pub fn new() -> anyhow::Result<Self> { + Ok(Self { + is_open: false, + buffers: RefCell::new(HashMap::from([(1, Some(render(1)?))])), + }) + } + + pub fn show(&mut self) -> bool { + if !self.is_open { + self.is_open = true; + true + } else { + false + } + } + + pub fn hide(&mut self) -> bool { + if self.is_open { + self.is_open = false; + true + } else { + false + } + } + + pub fn is_open(&self) -> bool { + self.is_open + } + + pub fn render<R: NiriRenderer>( + &self, + renderer: &mut R, + output: &Output, + ) -> Option<ExitConfirmDialogRenderElement<R>> { + if !self.is_open { + return None; + } + + let scale = output.current_scale().integer_scale(); + + let mut buffers = self.buffers.borrow_mut(); + let fallback = buffers[&1].clone().unwrap(); + let buffer = buffers.entry(scale).or_insert_with(|| render(scale).ok()); + let buffer = buffer.as_ref().unwrap_or(&fallback); + + let elem = MemoryRenderBufferRenderElement::from_buffer( + renderer, + (0., 0.), + buffer, + None, + None, + None, + Kind::Unspecified, + ) + .ok()?; + + let output_transform = output.current_transform(); + let output_mode = output.current_mode().unwrap(); + let output_size = output_transform.transform_size(output_mode.size); + + let buffer_size = elem + .geometry(output.current_scale().fractional_scale().into()) + .size; + + let x = (output_size.w / 2 - buffer_size.w / 2).max(0); + let y = (output_size.h / 2 - buffer_size.h / 2).max(0); + let elem = RelocateRenderElement::from_element(elem, (x, y), Relocate::Absolute); + + Some(elem) + } +} + +fn render(scale: i32) -> anyhow::Result<MemoryRenderBuffer> { + let _span = tracy_client::span!("exit_confirm_dialog::render"); + + let padding = PADDING * scale; + + let mut font = FontDescription::from_string(FONT); + font.set_absolute_size((font.size() * scale).into()); + + let surface = ImageSurface::create(cairo::Format::ARgb32, 0, 0)?; + let cr = cairo::Context::new(&surface)?; + let layout = pangocairo::create_layout(&cr); + layout.set_font_description(Some(&font)); + layout.set_alignment(Alignment::Center); + layout.set_markup(TEXT); + + let (mut width, mut height) = layout.pixel_size(); + width += padding * 2; + height += padding * 2; + + // FIXME: fix bug in Smithay that rounds pixel sizes down to scale. + width = (width + scale - 1) / scale * scale; + height = (height + scale - 1) / scale * scale; + + let surface = ImageSurface::create(cairo::Format::ARgb32, width, height)?; + let cr = cairo::Context::new(&surface)?; + cr.set_source_rgb(0.1, 0.1, 0.1); + cr.paint()?; + + cr.move_to(padding.into(), padding.into()); + let layout = pangocairo::create_layout(&cr); + layout.set_font_description(Some(&font)); + layout.set_alignment(Alignment::Center); + layout.set_markup(TEXT); + + cr.set_source_rgb(1., 1., 1.); + pangocairo::show_layout(&cr, &layout); + + cr.move_to(0., 0.); + cr.line_to(width.into(), 0.); + cr.line_to(width.into(), height.into()); + cr.line_to(0., height.into()); + cr.line_to(0., 0.); + cr.set_source_rgb(1., 0.3, 0.3); + cr.set_line_width((BORDER * scale).into()); + cr.stroke()?; + drop(cr); + + let data = surface.take_data().unwrap(); + let buffer = MemoryRenderBuffer::from_memory( + &data, + Fourcc::Argb8888, + (width, height), + scale, + Transform::Normal, + None, + ); + + Ok(buffer) +} diff --git a/src/input.rs b/src/input.rs index 595be712..f82de8a0 100644 --- a/src/input.rs +++ b/src/input.rs @@ -57,6 +57,13 @@ impl State { let hide_hotkey_overlay = self.niri.hotkey_overlay.is_open() && should_hide_hotkey_overlay(&event); + let hide_exit_confirm_dialog = self + .niri + .exit_confirm_dialog + .as_ref() + .map_or(false, |d| d.is_open()) + && should_hide_exit_confirm_dialog(&event); + use InputEvent::*; match event { DeviceAdded { device } => self.on_device_added(device), @@ -91,6 +98,12 @@ impl State { if hide_hotkey_overlay && self.niri.hotkey_overlay.hide() { self.niri.queue_redraw_all(); } + + if let Some(dialog) = &mut self.niri.exit_confirm_dialog { + if hide_exit_confirm_dialog && dialog.hide() { + self.niri.queue_redraw_all(); + } + } } pub fn process_libinput_event(&mut self, event: &mut InputEvent<LibinputInputBackend>) { @@ -204,6 +217,13 @@ impl State { let key_code = event.key_code(); let modified = keysym.modified_sym(); let raw = keysym.raw_latin_sym_or_raw_current_sym(); + + if let Some(dialog) = &this.niri.exit_confirm_dialog { + if dialog.is_open() && pressed && raw == Some(Keysym::Return) { + this.niri.stop_signal.stop(); + } + } + should_intercept_key( &mut this.niri.suppressed_keys, bindings, @@ -232,8 +252,14 @@ impl State { match action { Action::Quit => { - info!("quitting because quit bind was pressed"); - self.niri.stop_signal.stop() + if let Some(dialog) = &mut self.niri.exit_confirm_dialog { + if dialog.show() { + self.niri.queue_redraw_all(); + } + } else { + info!("quitting because quit bind was pressed"); + self.niri.stop_signal.stop() + } } Action::ChangeVt(vt) => { self.backend.change_vt(vt); @@ -1401,6 +1427,21 @@ fn should_hide_hotkey_overlay<I: InputBackend>(event: &InputEvent<I>) -> bool { } } +fn should_hide_exit_confirm_dialog<I: InputBackend>(event: &InputEvent<I>) -> bool { + match event { + InputEvent::Keyboard { event } if event.state() == KeyState::Pressed => true, + InputEvent::PointerButton { .. } + | InputEvent::PointerAxis { .. } + | InputEvent::GestureSwipeBegin { .. } + | InputEvent::GesturePinchBegin { .. } + | InputEvent::TouchDown { .. } + | InputEvent::TouchMotion { .. } + | InputEvent::TabletToolTip { .. } + | InputEvent::TabletToolButton { .. } => true, + _ => false, + } +} + fn allowed_when_locked(action: &Action) -> bool { matches!( action, diff --git a/src/main.rs b/src/main.rs index f4c7c70c..af2346e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ mod config_error_notification; mod cursor; #[cfg(feature = "dbus")] mod dbus; +mod exit_confirm_dialog; mod frame_clock; mod handlers; mod hotkey_overlay; diff --git a/src/niri.rs b/src/niri.rs index 93b50be3..94a2056f 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -98,6 +98,7 @@ use crate::cursor::{CursorManager, CursorTextureCache, RenderCursor, XCursor}; use crate::dbus::gnome_shell_screenshot::{NiriToScreenshot, ScreenshotToNiri}; #[cfg(feature = "xdp-gnome-screencast")] use crate::dbus::mutter_screen_cast::{self, ScreenCastToNiri}; +use crate::exit_confirm_dialog::ExitConfirmDialog; use crate::frame_clock::FrameClock; use crate::handlers::configure_lock_surface; use crate::hotkey_overlay::HotkeyOverlay; @@ -191,6 +192,7 @@ pub struct Niri { pub screenshot_ui: ScreenshotUi, pub config_error_notification: ConfigErrorNotification, pub hotkey_overlay: HotkeyOverlay, + pub exit_confirm_dialog: Option<ExitConfirmDialog>, #[cfg(feature = "dbus")] pub dbus: Option<crate::dbus::DBusServers>, @@ -867,6 +869,13 @@ impl Niri { let screenshot_ui = ScreenshotUi::new(); let config_error_notification = ConfigErrorNotification::new(); let hotkey_overlay = HotkeyOverlay::new(config.clone(), backend.mod_key()); + let exit_confirm_dialog = match ExitConfirmDialog::new() { + Ok(x) => Some(x), + Err(err) => { + warn!("error creating the exit confirm dialog: {err:?}"); + None + } + }; let socket_source = ListeningSocketSource::new_auto().unwrap(); let socket_name = socket_source.socket_name().to_os_string(); @@ -973,6 +982,7 @@ impl Niri { screenshot_ui, config_error_notification, hotkey_overlay, + exit_confirm_dialog, #[cfg(feature = "dbus")] dbus: None, @@ -1807,7 +1817,14 @@ impl Niri { elements = self.pointer_element(renderer, output); } - // The config error notification too. + // Next, the exit confirm dialog. + if let Some(dialog) = &self.exit_confirm_dialog { + if let Some(element) = dialog.render(renderer, output) { + elements.push(element.into()); + } + } + + // Next, the config error notification too. if let Some(element) = self.config_error_notification.render(renderer, output) { elements.push(element.into()); } |
