aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md1
-rw-r--r--niri-config/src/lib.rs1
-rw-r--r--resources/default-config.kdl48
-rw-r--r--src/hotkey_overlay.rs429
-rw-r--r--src/input.rs29
-rw-r--r--src/main.rs1
-rw-r--r--src/niri.rs13
7 files changed, 500 insertions, 22 deletions
diff --git a/README.md b/README.md
index e9609ae9..01eab118 100644
--- a/README.md
+++ b/README.md
@@ -144,6 +144,7 @@ The general system is: if a hotkey switches somewhere, then adding <kbd>Ctrl</kb
| Hotkey | Description |
| ------ | ----------- |
+| <kbd>Mod</kbd><kbd>Shift</kbd><kbd>/</kbd> | Show a list of important niri hotkeys |
| <kbd>Mod</kbd><kbd>T</kbd> | Spawn `alacritty` (terminal) |
| <kbd>Mod</kbd><kbd>D</kbd> | Spawn `fuzzel` (application launcher) |
| <kbd>Mod</kbd><kbd>Alt</kbd><kbd>L</kbd> | Spawn `swaylock` (screen locker) |
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs
index f61180f0..f05b78b9 100644
--- a/niri-config/src/lib.rs
+++ b/niri-config/src/lib.rs
@@ -429,6 +429,7 @@ pub enum Action {
MaximizeColumn,
SetColumnWidth(#[knuffel(argument, str)] SizeChange),
SwitchLayout(#[knuffel(argument)] LayoutAction),
+ ShowHotkeyOverlay,
}
#[derive(Debug, Clone, Copy, PartialEq)]
diff --git a/resources/default-config.kdl b/resources/default-config.kdl
index c192467b..1a7d6f94 100644
--- a/resources/default-config.kdl
+++ b/resources/default-config.kdl
@@ -187,6 +187,10 @@ binds {
// "Mod" is a special modifier equal to Super when running on a TTY, and to Alt
// when running as a winit window.
+ // Mod-Shift-/, which is usually the same as Mod-?,
+ // shows a list of important hotkeys.
+ Mod+Shift+Slash { show-hotkey-overlay; }
+
// Suggested binds for running programs: terminal, app launcher, screen locker.
Mod+T { spawn "alacritty"; }
Mod+D { spawn "fuzzel"; }
@@ -201,23 +205,23 @@ binds {
Mod+Q { close-window; }
- Mod+H { focus-column-left; }
- Mod+J { focus-window-down; }
- Mod+K { focus-window-up; }
- Mod+L { focus-column-right; }
Mod+Left { focus-column-left; }
Mod+Down { focus-window-down; }
Mod+Up { focus-window-up; }
Mod+Right { focus-column-right; }
+ Mod+H { focus-column-left; }
+ Mod+J { focus-window-down; }
+ Mod+K { focus-window-up; }
+ Mod+L { focus-column-right; }
- Mod+Ctrl+H { move-column-left; }
- Mod+Ctrl+J { move-window-down; }
- Mod+Ctrl+K { move-window-up; }
- Mod+Ctrl+L { move-column-right; }
Mod+Ctrl+Left { move-column-left; }
Mod+Ctrl+Down { move-window-down; }
Mod+Ctrl+Up { move-window-up; }
Mod+Ctrl+Right { move-column-right; }
+ Mod+Ctrl+H { move-column-left; }
+ Mod+Ctrl+J { move-window-down; }
+ Mod+Ctrl+K { move-window-up; }
+ Mod+Ctrl+L { move-column-right; }
// Alternative commands that move across workspaces when reaching
// the first or last window in a column.
@@ -231,45 +235,45 @@ binds {
Mod+Ctrl+Home { move-column-to-first; }
Mod+Ctrl+End { move-column-to-last; }
- Mod+Shift+H { focus-monitor-left; }
- Mod+Shift+J { focus-monitor-down; }
- Mod+Shift+K { focus-monitor-up; }
- Mod+Shift+L { focus-monitor-right; }
Mod+Shift+Left { focus-monitor-left; }
Mod+Shift+Down { focus-monitor-down; }
Mod+Shift+Up { focus-monitor-up; }
Mod+Shift+Right { focus-monitor-right; }
+ Mod+Shift+H { focus-monitor-left; }
+ Mod+Shift+J { focus-monitor-down; }
+ Mod+Shift+K { focus-monitor-up; }
+ Mod+Shift+L { focus-monitor-right; }
- Mod+Shift+Ctrl+H { move-column-to-monitor-left; }
- Mod+Shift+Ctrl+J { move-column-to-monitor-down; }
- Mod+Shift+Ctrl+K { move-column-to-monitor-up; }
- Mod+Shift+Ctrl+L { move-column-to-monitor-right; }
Mod+Shift+Ctrl+Left { move-column-to-monitor-left; }
Mod+Shift+Ctrl+Down { move-column-to-monitor-down; }
Mod+Shift+Ctrl+Up { move-column-to-monitor-up; }
Mod+Shift+Ctrl+Right { move-column-to-monitor-right; }
+ Mod+Shift+Ctrl+H { move-column-to-monitor-left; }
+ Mod+Shift+Ctrl+J { move-column-to-monitor-down; }
+ Mod+Shift+Ctrl+K { move-column-to-monitor-up; }
+ Mod+Shift+Ctrl+L { move-column-to-monitor-right; }
// Alternatively, there are commands to move just a single window:
// Mod+Shift+Ctrl+Left { move-window-to-monitor-left; }
// ...
- Mod+U { focus-workspace-down; }
- Mod+I { focus-workspace-up; }
Mod+Page_Down { focus-workspace-down; }
Mod+Page_Up { focus-workspace-up; }
- Mod+Ctrl+U { move-column-to-workspace-down; }
- Mod+Ctrl+I { move-column-to-workspace-up; }
+ Mod+U { focus-workspace-down; }
+ Mod+I { focus-workspace-up; }
Mod+Ctrl+Page_Down { move-column-to-workspace-down; }
Mod+Ctrl+Page_Up { move-column-to-workspace-up; }
+ Mod+Ctrl+U { move-column-to-workspace-down; }
+ Mod+Ctrl+I { move-column-to-workspace-up; }
// Alternatively, there are commands to move just a single window:
// Mod+Ctrl+Page_Down { move-window-to-workspace-down; }
// ...
- Mod+Shift+U { move-workspace-down; }
- Mod+Shift+I { move-workspace-up; }
Mod+Shift+Page_Down { move-workspace-down; }
Mod+Shift+Page_Up { move-workspace-up; }
+ Mod+Shift+U { move-workspace-down; }
+ Mod+Shift+I { move-workspace-up; }
Mod+1 { focus-workspace 1; }
Mod+2 { focus-workspace 2; }
diff --git a/src/hotkey_overlay.rs b/src/hotkey_overlay.rs
new file mode 100644
index 00000000..5aa898a3
--- /dev/null
+++ b/src/hotkey_overlay.rs
@@ -0,0 +1,429 @@
+use std::cell::RefCell;
+use std::cmp::max;
+use std::collections::HashMap;
+use std::iter::zip;
+use std::rc::Rc;
+
+use niri_config::{Action, Config, Key, Modifiers};
+use pangocairo::cairo::{self, ImageSurface};
+use pangocairo::pango::{AttrColor, AttrInt, AttrList, AttrString, FontDescription, Weight};
+use smithay::backend::renderer::element::memory::{
+ MemoryRenderBuffer, MemoryRenderBufferRenderElement,
+};
+use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement};
+use smithay::backend::renderer::element::Kind;
+use smithay::input::keyboard::xkb::keysym_get_name;
+use smithay::output::{Output, WeakOutput};
+use smithay::reexports::gbm::Format as Fourcc;
+use smithay::utils::{Physical, Size, Transform};
+
+use crate::input::CompositorMod;
+use crate::render_helpers::NiriRenderer;
+
+const PADDING: i32 = 8;
+const MARGIN: i32 = PADDING * 2;
+const FONT: &str = "sans 14px";
+const BORDER: i32 = 4;
+const LINE_INTERVAL: i32 = 2;
+const TITLE: &str = "Important Hotkeys";
+
+pub struct HotkeyOverlay {
+ is_open: bool,
+ config: Rc<RefCell<Config>>,
+ comp_mod: CompositorMod,
+ buffers: RefCell<HashMap<WeakOutput, RenderedOverlay>>,
+}
+
+pub struct RenderedOverlay {
+ buffer: Option<MemoryRenderBuffer>,
+ size: Size<i32, Physical>,
+ scale: i32,
+}
+
+pub type HotkeyOverlayRenderElement<R> = RelocateRenderElement<MemoryRenderBufferRenderElement<R>>;
+
+impl HotkeyOverlay {
+ pub fn new(config: Rc<RefCell<Config>>, comp_mod: CompositorMod) -> Self {
+ Self {
+ // Start the compositor with the overlay open.
+ is_open: true,
+ config,
+ comp_mod,
+ buffers: RefCell::new(HashMap::new()),
+ }
+ }
+
+ 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 on_hotkey_config_updated(&mut self) {
+ self.buffers.borrow_mut().clear();
+ }
+
+ pub fn render<R: NiriRenderer>(
+ &self,
+ renderer: &mut R,
+ output: &Output,
+ ) -> Option<HotkeyOverlayRenderElement<R>> {
+ if !self.is_open {
+ return None;
+ }
+
+ let scale = output.current_scale().integer_scale();
+ let margin = MARGIN * scale;
+
+ let output_transform = output.current_transform();
+ let output_mode = output.current_mode().unwrap();
+ let output_size = output_transform.transform_size(output_mode.size);
+
+ let mut buffers = self.buffers.borrow_mut();
+ buffers.retain(|output, _| output.upgrade().is_some());
+
+ // FIXME: should probably use the working area rather than view size.
+ let weak = output.downgrade();
+ if let Some(rendered) = buffers.get(&weak) {
+ if rendered.scale != scale {
+ buffers.remove(&weak);
+ }
+ }
+
+ let rendered = buffers.entry(weak).or_insert_with(|| {
+ render(&self.config.borrow(), self.comp_mod, scale).unwrap_or_else(|_| {
+ // This can go negative but whatever, as long as there's no rerender loop.
+ let mut size = output_size;
+ size.w -= margin * 2;
+ size.h -= margin * 2;
+ RenderedOverlay {
+ buffer: None,
+ size,
+ scale,
+ }
+ })
+ });
+ let buffer = rendered.buffer.as_ref()?;
+
+ let elem = MemoryRenderBufferRenderElement::from_buffer(
+ renderer,
+ (0., 0.),
+ buffer,
+ Some(0.9),
+ None,
+ None,
+ Kind::Unspecified,
+ )
+ .ok()?;
+
+ let x = (output_size.w / 2 - rendered.size.w / 2).max(0);
+ let y = (output_size.h / 2 - rendered.size.h / 2).max(0);
+ let elem = RelocateRenderElement::from_element(elem, (x, y), Relocate::Absolute);
+
+ Some(elem)
+ }
+}
+
+fn render(config: &Config, comp_mod: CompositorMod, scale: i32) -> anyhow::Result<RenderedOverlay> {
+ let _span = tracy_client::span!("hotkey_overlay::render");
+
+ // let margin = MARGIN * scale;
+ let padding = PADDING * scale;
+ let line_interval = LINE_INTERVAL * scale;
+
+ // FIXME: if it doesn't fit, try splitting in two columns or something.
+ // let mut target_size = output_size;
+ // target_size.w -= margin * 2;
+ // target_size.h -= margin * 2;
+ // anyhow::ensure!(target_size.w > 0 && target_size.h > 0);
+
+ let binds = &config.binds.0;
+
+ // Collect actions that we want to show.
+ let mut actions = vec![
+ &Action::ShowHotkeyOverlay,
+ &Action::Quit,
+ &Action::CloseWindow,
+ ];
+
+ actions.extend(&[
+ &Action::FocusColumnLeft,
+ &Action::FocusColumnRight,
+ &Action::MoveColumnLeft,
+ &Action::MoveColumnRight,
+ &Action::FocusWorkspaceDown,
+ &Action::FocusWorkspaceUp,
+ ]);
+
+ // Prefer move-column-to-workspace-down, but fall back to move-window-to-workspace-down.
+ if binds
+ .iter()
+ .any(|bind| bind.actions.first() == Some(&Action::MoveColumnToWorkspaceDown))
+ {
+ actions.push(&Action::MoveColumnToWorkspaceDown);
+ } else if binds
+ .iter()
+ .any(|bind| bind.actions.first() == Some(&Action::MoveWindowToWorkspaceDown))
+ {
+ actions.push(&Action::MoveWindowToWorkspaceDown);
+ } else {
+ actions.push(&Action::MoveColumnToWorkspaceDown);
+ }
+
+ // Same for -up.
+ if binds
+ .iter()
+ .any(|bind| bind.actions.first() == Some(&Action::MoveColumnToWorkspaceUp))
+ {
+ actions.push(&Action::MoveColumnToWorkspaceUp);
+ } else if binds
+ .iter()
+ .any(|bind| bind.actions.first() == Some(&Action::MoveWindowToWorkspaceUp))
+ {
+ actions.push(&Action::MoveWindowToWorkspaceUp);
+ } else {
+ actions.push(&Action::MoveColumnToWorkspaceUp);
+ }
+
+ actions.extend(&[
+ &Action::SwitchPresetColumnWidth,
+ &Action::MaximizeColumn,
+ &Action::ConsumeWindowIntoColumn,
+ &Action::ExpelWindowFromColumn,
+ ]);
+
+ // Screenshot is not as important, can omit if not bound.
+ if binds
+ .iter()
+ .any(|bind| bind.actions.first() == Some(&Action::Screenshot))
+ {
+ actions.push(&Action::Screenshot);
+ }
+
+ // Add the spawn actions.
+ for bind in binds
+ .iter()
+ .filter(|bind| matches!(bind.actions.first(), Some(Action::Spawn(_))))
+ {
+ actions.push(bind.actions.first().unwrap());
+ }
+
+ let strings = actions
+ .into_iter()
+ .map(|action| {
+ let key = config
+ .binds
+ .0
+ .iter()
+ .find(|bind| bind.actions.first() == Some(action))
+ .map(|bind| key_name(comp_mod, &bind.key))
+ .unwrap_or_else(|| String::from("(not bound)"));
+
+ (format!(" {key} "), action_name(action))
+ })
+ .collect::<Vec<_>>();
+
+ 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));
+
+ let bold = AttrList::new();
+ bold.insert(AttrInt::new_weight(Weight::Bold));
+ layout.set_attributes(Some(&bold));
+ layout.set_text(TITLE);
+ let title_size = layout.pixel_size();
+
+ let attrs = AttrList::new();
+ attrs.insert(AttrString::new_family("Monospace"));
+ attrs.insert(AttrColor::new_background(12000, 12000, 12000));
+
+ layout.set_attributes(Some(&attrs));
+ let key_sizes = strings
+ .iter()
+ .map(|(key, _)| {
+ layout.set_text(key);
+ layout.pixel_size()
+ })
+ .collect::<Vec<_>>();
+
+ layout.set_attributes(None);
+ let action_sizes = strings
+ .iter()
+ .map(|(_, action)| {
+ layout.set_markup(action);
+ layout.pixel_size()
+ })
+ .collect::<Vec<_>>();
+
+ let key_width = key_sizes.iter().map(|(w, _)| w).max().unwrap();
+ let action_width = action_sizes.iter().map(|(w, _)| w).max().unwrap();
+ let mut width = key_width + padding + action_width;
+
+ let mut height = zip(&key_sizes, &action_sizes)
+ .map(|((_, key_h), (_, act_h))| max(key_h, act_h))
+ .sum::<i32>()
+ + (key_sizes.len() - 1) as i32 * line_interval
+ + title_size.1
+ + padding;
+
+ 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));
+
+ cr.set_source_rgb(1., 1., 1.);
+
+ cr.move_to(((width - title_size.0) / 2).into(), padding.into());
+ layout.set_attributes(Some(&bold));
+ layout.set_text(TITLE);
+ pangocairo::show_layout(&cr, &layout);
+
+ cr.move_to(padding.into(), (padding + title_size.1 + padding).into());
+
+ for ((key, action), ((_, key_h), (_, act_h))) in zip(&strings, zip(&key_sizes, &action_sizes)) {
+ layout.set_attributes(Some(&attrs));
+ layout.set_text(key);
+ pangocairo::show_layout(&cr, &layout);
+
+ cr.rel_move_to((key_width + padding).into(), 0.);
+
+ layout.set_attributes(None);
+ layout.set_markup(action);
+ pangocairo::show_layout(&cr, &layout);
+
+ cr.rel_move_to(
+ (-(key_width + padding)).into(),
+ (max(key_h, act_h) + line_interval).into(),
+ );
+ }
+
+ 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(0.5, 0.8, 1.0);
+ 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(RenderedOverlay {
+ buffer: Some(buffer),
+ size: Size::from((width, height)),
+ scale,
+ })
+}
+
+fn action_name(action: &Action) -> String {
+ match action {
+ Action::Quit => String::from("Exit niri"),
+ Action::ShowHotkeyOverlay => String::from("Show Important Hotkeys"),
+ Action::CloseWindow => String::from("Close Focused Window"),
+ Action::FocusColumnLeft => String::from("Focus Column to the Left"),
+ Action::FocusColumnRight => String::from("Focus Column to the Right"),
+ Action::MoveColumnLeft => String::from("Move Column Left"),
+ Action::MoveColumnRight => String::from("Move Column Right"),
+ Action::FocusWorkspaceDown => String::from("Switch Workspace Down"),
+ Action::FocusWorkspaceUp => String::from("Switch Workspace Up"),
+ Action::MoveColumnToWorkspaceDown => String::from("Move Column to Workspace Down"),
+ Action::MoveColumnToWorkspaceUp => String::from("Move Column to Workspace Up"),
+ Action::MoveWindowToWorkspaceDown => String::from("Move Window to Workspace Down"),
+ Action::MoveWindowToWorkspaceUp => String::from("Move Window to Workspace Up"),
+ Action::SwitchPresetColumnWidth => String::from("Switch Preset Column Widths"),
+ Action::MaximizeColumn => String::from("Maximize Column"),
+ Action::ConsumeWindowIntoColumn => String::from("Consume Window Into Column"),
+ Action::ExpelWindowFromColumn => String::from("Expel Window From Column"),
+ Action::Screenshot => String::from("Take a Screenshot"),
+ Action::Spawn(args) => format!(
+ "Spawn <span face='monospace' bgcolor='#000000'>{}</span>",
+ args.first().unwrap_or(&String::new())
+ ),
+ _ => String::from("FIXME: Unknown"),
+ }
+}
+
+fn key_name(comp_mod: CompositorMod, key: &Key) -> String {
+ let mut name = String::new();
+
+ let has_comp_mod = key.modifiers.contains(Modifiers::COMPOSITOR);
+
+ if key.modifiers.contains(Modifiers::SUPER)
+ || (has_comp_mod && comp_mod == CompositorMod::Super)
+ {
+ name.push_str("Super + ");
+ }
+ if key.modifiers.contains(Modifiers::ALT) || (has_comp_mod && comp_mod == CompositorMod::Alt) {
+ name.push_str("Alt + ");
+ }
+ if key.modifiers.contains(Modifiers::SHIFT) {
+ name.push_str("Shift + ");
+ }
+ if key.modifiers.contains(Modifiers::CTRL) {
+ name.push_str("Ctrl + ");
+ }
+ name.push_str(&prettify_keysym_name(&keysym_get_name(key.keysym)));
+
+ name
+}
+
+fn prettify_keysym_name(name: &str) -> String {
+ let name = match name {
+ "slash" => "/",
+ "comma" => ",",
+ "period" => ".",
+ "minus" => "-",
+ "equal" => "=",
+ "grave" => "`",
+ "Next" => "Page Down",
+ "Prior" => "Page Up",
+ "Print" => "PrtSc",
+ _ => name,
+ };
+
+ if name.len() == 1 && name.is_ascii() {
+ name.to_ascii_uppercase()
+ } else {
+ name.into()
+ }
+}
diff --git a/src/input.rs b/src/input.rs
index 293bda93..595be712 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -54,6 +54,9 @@ impl State {
self.niri.activate_monitors(&self.backend);
}
+ let hide_hotkey_overlay =
+ self.niri.hotkey_overlay.is_open() && should_hide_hotkey_overlay(&event);
+
use InputEvent::*;
match event {
DeviceAdded { device } => self.on_device_added(device),
@@ -82,6 +85,12 @@ impl State {
TouchFrame { .. } => (),
Special(_) => (),
}
+
+ // Do this last so that screenshot still gets it.
+ // FIXME: do this in a less cursed fashion somehow.
+ if hide_hotkey_overlay && self.niri.hotkey_overlay.hide() {
+ self.niri.queue_redraw_all();
+ }
}
pub fn process_libinput_event(&mut self, event: &mut InputEvent<LibinputInputBackend>) {
@@ -557,6 +566,11 @@ impl State {
Action::SetWindowHeight(change) => {
self.niri.layout.set_window_height(change);
}
+ Action::ShowHotkeyOverlay => {
+ if self.niri.hotkey_overlay.show() {
+ self.niri.queue_redraw_all();
+ }
+ }
}
}
@@ -1372,6 +1386,21 @@ fn should_activate_monitors<I: InputBackend>(event: &InputEvent<I>) -> bool {
}
}
+fn should_hide_hotkey_overlay<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 9fe1394a..f4c7c70c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,6 +9,7 @@ mod cursor;
mod dbus;
mod frame_clock;
mod handlers;
+mod hotkey_overlay;
mod input;
mod ipc;
mod layout;
diff --git a/src/niri.rs b/src/niri.rs
index 8109f2eb..16e0f2d6 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -100,6 +100,7 @@ use crate::dbus::gnome_shell_screenshot::{NiriToScreenshot, ScreenshotToNiri};
use crate::dbus::mutter_screen_cast::{self, ScreenCastToNiri};
use crate::frame_clock::FrameClock;
use crate::handlers::configure_lock_surface;
+use crate::hotkey_overlay::HotkeyOverlay;
use crate::input::{apply_libinput_settings, TabletData};
use crate::ipc::server::IpcServer;
use crate::layout::{Layout, MonitorRenderElement};
@@ -189,6 +190,7 @@ pub struct Niri {
pub screenshot_ui: ScreenshotUi,
pub config_error_notification: ConfigErrorNotification,
+ pub hotkey_overlay: HotkeyOverlay,
#[cfg(feature = "dbus")]
pub dbus: Option<crate::dbus::DBusServers>,
@@ -594,6 +596,10 @@ impl State {
output_config_changed = true;
}
+ if config.binds != old_config.binds {
+ self.niri.hotkey_overlay.on_hotkey_config_updated();
+ }
+
*old_config = config;
// Release the borrow.
@@ -859,6 +865,7 @@ impl Niri {
let screenshot_ui = ScreenshotUi::new();
let config_error_notification = ConfigErrorNotification::new();
+ let hotkey_overlay = HotkeyOverlay::new(config.clone(), backend.mod_key());
let socket_source = ListeningSocketSource::new_auto().unwrap();
let socket_name = socket_source.socket_name().to_os_string();
@@ -964,6 +971,7 @@ impl Niri {
screenshot_ui,
config_error_notification,
+ hotkey_overlay,
#[cfg(feature = "dbus")]
dbus: None,
@@ -1858,6 +1866,11 @@ impl Niri {
return elements;
}
+ // Draw the hotkey overlay on top.
+ if let Some(element) = self.hotkey_overlay.render(renderer, output) {
+ elements.push(element.into());
+ }
+
// Get monitor elements.
let mon = self.layout.monitor_for_output(output).unwrap();
let monitor_elements = mon.render_elements(renderer);