mirror of
https://github.com/YaLTeR/niri.git
synced 2024-09-11 04:25:35 +03:00
Add an important hotkeys overlay
This commit is contained in:
parent
edafa139f6
commit
011c91c98a
@ -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) |
|
||||
|
@ -429,6 +429,7 @@ pub enum Action {
|
||||
MaximizeColumn,
|
||||
SetColumnWidth(#[knuffel(argument, str)] SizeChange),
|
||||
SwitchLayout(#[knuffel(argument)] LayoutAction),
|
||||
ShowHotkeyOverlay,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
|
@ -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; }
|
||||
|
429
src/hotkey_overlay.rs
Normal file
429
src/hotkey_overlay.rs
Normal file
@ -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()
|
||||
}
|
||||
}
|
29
src/input.rs
29
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,
|
||||
|
@ -9,6 +9,7 @@ mod cursor;
|
||||
mod dbus;
|
||||
mod frame_clock;
|
||||
mod handlers;
|
||||
mod hotkey_overlay;
|
||||
mod input;
|
||||
mod ipc;
|
||||
mod layout;
|
||||
|
13
src/niri.rs
13
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);
|
||||
|
Loading…
Reference in New Issue
Block a user