diff --git a/Cargo.lock b/Cargo.lock index fb41e0f..3fe1e4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4338,7 +4338,9 @@ dependencies = [ "vulkano-shaders", "winit", "wlx-capture", + "xcb", "xdg", + "xkbcommon", ] [[package]] @@ -4379,6 +4381,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02e75181b5a62b6eeaa72f303d3cef7dbb841e22885bf6d3e66fe23e88c55dc6" dependencies = [ + "as-raw-xcb-connection", "bitflags 1.3.2", "libc", "quick-xml 0.30.0", @@ -4412,6 +4415,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e" dependencies = [ + "as-raw-xcb-connection", "libc", "memmap2 0.8.0", "xkeysym", diff --git a/Cargo.toml b/Cargo.toml index aca7d9c..a2c9a48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,13 +58,22 @@ winit = { version = "0.29.15", optional = true } xdg = "2.5.2" log-panics = { version = "2.1.0", features = ["with-backtrace"] } serde_json5 = "0.1.0" +xkbcommon = { version = "0.7.0" } +xcb = { version = "1.4.0", optional = true, features = ["as-raw-xcb-connection"] } [features] default = ["openvr", "openxr", "osc", "x11", "wayland"] openvr = ["dep:ovr_overlay", "dep:json"] openxr = ["dep:openxr", "dep:sysinfo"] osc = ["dep:rosc"] -x11 = ["wlx-capture/xshm"] -wayland = ["wlx-capture/pipewire", "wlx-capture/wlr"] +x11 = [ + "dep:xcb", + + "wlx-capture/xshm", + "xkbcommon/x11", +] +wayland = ["wlx-capture/pipewire", "wlx-capture/wlr", "xkbcommon/wayland"] uidev = ["dep:winit"] no-dmabuf = [] +xcb = ["dep:xcb"] +as-raw-xcb-connection = [] diff --git a/src/backend/common.rs b/src/backend/common.rs index b52ece4..f3af3e2 100644 --- a/src/backend/common.rs +++ b/src/backend/common.rs @@ -11,6 +11,7 @@ use thiserror::Error; use crate::{ config::AStrSetExt, + hid::{get_keymap_wl, get_keymap_x11}, overlays::{ anchor::create_anchor, keyboard::create_keyboard, @@ -63,10 +64,14 @@ where let mut overlays = IdMap::new(); let mut wl = create_wl_client(); + let keymap; + app.screens.clear(); let data = if let Some(wl) = wl.as_mut() { + keymap = get_keymap_wl().ok(); crate::overlays::screen::create_screens_wayland(wl, app)? } else { + keymap = get_keymap_x11().ok(); crate::overlays::screen::create_screens_x11(app)? }; @@ -100,7 +105,7 @@ where watch.state.want_visible = true; overlays.insert(watch.state.id, watch); - let mut keyboard = create_keyboard(app)?; + let mut keyboard = create_keyboard(app, keymap)?; keyboard.state.show_hide = true; keyboard.state.want_visible = false; overlays.insert(keyboard.state.id, keyboard); diff --git a/src/gui/font.rs b/src/gui/font.rs index b320c24..73363b6 100644 --- a/src/gui/font.rs +++ b/src/gui/font.rs @@ -17,6 +17,7 @@ pub struct FontCache { struct FontCollection { fonts: Vec, cp_map: IdMap, + zero_glyph: Rc, } struct Font { @@ -96,6 +97,14 @@ impl FontCache { FontCollection { fonts: Vec::new(), cp_map: IdMap::new(), + zero_glyph: Rc::new(Glyph { + tex: None, + top: 0., + left: 0., + width: 0., + height: 0., + advance: size as f32 / 3., + }), }, ); } @@ -181,7 +190,7 @@ impl FontCache { let Some(font) = &mut self.collections[size].fonts.get_mut(key) else { log::warn!("No font found for codepoint: {}", cp); - return Ok(self.collections[size].fonts[0].glyphs[0].clone()); + return Ok(self.collections[size].zero_glyph.clone()); }; if let Some(glyph) = font.glyphs.get(cp) { @@ -189,18 +198,18 @@ impl FontCache { } if font.face.load_char(cp, LoadFlag::DEFAULT).is_err() { - return Ok(font.glyphs[0].clone()); + return Ok(self.collections[size].zero_glyph.clone()); } let glyph = font.face.glyph(); if glyph.render_glyph(freetype::RenderMode::Normal).is_err() { - return Ok(font.glyphs[0].clone()); + return Ok(self.collections[size].zero_glyph.clone()); } let bmp = glyph.bitmap(); let buf = bmp.buffer().to_vec(); if buf.is_empty() { - return Ok(font.glyphs[0].clone()); + return Ok(self.collections[size].zero_glyph.clone()); } let metrics = glyph.metrics(); @@ -209,7 +218,7 @@ impl FontCache { Ok(PixelMode::Gray) => Format::R8_UNORM, Ok(PixelMode::Gray2) => Format::R16_SFLOAT, Ok(PixelMode::Gray4) => Format::R32_SFLOAT, - _ => return Ok(font.glyphs[0].clone()), + _ => return Ok(self.collections[size].zero_glyph.clone()), }; let mut cmd_buffer = graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; diff --git a/src/gui/mod.rs b/src/gui/mod.rs index ac9ee3a..645a1e5 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -171,6 +171,7 @@ impl CanvasBuilder { y: f32, w: f32, h: f32, + cap_type: KeyCapType, label: &[String], ) -> &mut Control { let idx = self.canvas.controls.len(); @@ -184,27 +185,88 @@ impl CanvasBuilder { ..Control::new() }); - for (i, item) in label.iter().enumerate().take(label.len().min(2)) { + let renders = match cap_type { + KeyCapType::Regular => { + let render: ControlRenderer = Control::render_text_centered; + let rect = Rect { + x, + y, + w, + h: h - self.font_size as f32, + }; + vec![(render, rect, 1f32)] + } + KeyCapType::RegularAltGr => { + let render: ControlRenderer = Control::render_text; + let rect0 = Rect { + x: x + 12., + y: y + (self.font_size as f32) + 12., + w, + h, + }; + let rect1 = Rect { + x: x + w * 0.5 + 12., + y: y + h - (self.font_size as f32) + 8., + w, + h, + }; + vec![(render, rect0, 1.0), (render, rect1, 0.8)] + } + KeyCapType::Reversed => { + let render: ControlRenderer = Control::render_text_centered; + let rect0 = Rect { + x, + y: y + 2.0, + w, + h: h * 0.5, + }; + let rect1 = Rect { + x, + y: y + h * 0.5 + 2.0, + w, + h: h * 0.5, + }; + vec![(render, rect1, 1.0), (render, rect0, 0.8)] + } + KeyCapType::ReversedAltGr => { + let render: ControlRenderer = Control::render_text; + let rect0 = Rect { + x: x + 12., + y: y + (self.font_size as f32) + 8., + w, + h, + }; + let rect1 = Rect { + x: x + 12., + y: y + h - (self.font_size as f32) + 4., + w, + h, + }; + let rect2 = Rect { + x: x + w * 0.5 + 8., + y: y + h - (self.font_size as f32) + 4., + w, + h, + }; + vec![ + (render, rect1, 1.0), + (render, rect0, 0.8), + (render, rect2, 0.8), + ] + } + }; + + for (idx, (render, rect, alpha)) in renders.into_iter().enumerate() { + if idx >= label.len() { + break; + } + self.canvas.controls.push(Control { - rect: if i == 0 { - Rect { - x: x + 4., - y: y + (self.font_size as f32) + 4., - w, - h, - } - } else { - Rect { - x: x + w * 0.5, - y: y + h - (self.font_size as f32) + 4., - w, - h, - } - }, - text: Arc::from(item.as_str()), - fg_color: self.fg_color, + rect, + text: Arc::from(label[idx].as_str()), + fg_color: self.fg_color * alpha, size: self.font_size, - on_render_fg: Some(Control::render_text), + on_render_fg: Some(render), ..Control::new() }); } @@ -540,6 +602,17 @@ impl OverlayBackend for Canvas { fn set_interaction(&mut self, _interaction: Box) {} } +pub type ControlRenderer = + fn(&Control, &CanvasData, &mut AppState, &mut WlxCommandBuffer) -> anyhow::Result<()>; + +pub type ControlRendererHl = fn( + &Control, + &CanvasData, + &mut AppState, + &mut WlxCommandBuffer, + Vec4, +) -> anyhow::Result<()>; + pub struct Control { pub state: Option, rect: Rect, @@ -555,15 +628,9 @@ pub struct Control { pub on_scroll: Option, pub test_highlight: Option Option>, - on_render_bg: Option< - fn(&Self, &CanvasData, &mut AppState, &mut WlxCommandBuffer) -> anyhow::Result<()>, - >, - on_render_hl: Option< - fn(&Self, &CanvasData, &mut AppState, &mut WlxCommandBuffer, Vec4) -> anyhow::Result<()>, - >, - on_render_fg: Option< - fn(&Self, &CanvasData, &mut AppState, &mut WlxCommandBuffer) -> anyhow::Result<()>, - >, + on_render_bg: Option>, + on_render_hl: Option>, + on_render_fg: Option>, } impl Control { @@ -762,3 +829,18 @@ impl Control { Ok(()) } } + +pub enum KeyCapType { + /// Label is in center of keycap + Regular, + /// Label on the top + /// AltGr symbol on bottom + RegularAltGr, + /// Primary symbol on bottom + /// Shift symbol on top + Reversed, + /// Primary symbol on bottom-left + /// Shift symbol on top-left + /// AltGr symbol on bottom-right + ReversedAltGr, +} diff --git a/src/hid.rs b/src/hid/mod.rs similarity index 82% rename from src/hid.rs rename to src/hid/mod.rs index 65d5686..a309b81 100644 --- a/src/hid.rs +++ b/src/hid/mod.rs @@ -10,6 +10,13 @@ use once_cell::sync::Lazy; use std::mem::transmute; use std::{fs::File, sync::atomic::AtomicBool}; use strum::{EnumIter, EnumString, IntoEnumIterator}; +use xkbcommon::xkb; + +#[cfg(feature = "wayland")] +mod wayland; + +#[cfg(feature = "x11")] +mod x11; pub static USE_UINPUT: AtomicBool = AtomicBool::new(true); @@ -487,3 +494,100 @@ pub static MODS_TO_KEYS: Lazy>> = Lazy::new(| META => vec![VirtualKey::Meta], } }); + +pub enum KeyType { + Symbol, + NumPad, + Other, +} + +macro_rules! key_between { + ($key:expr, $start:expr, $end:expr) => { + $key as u32 >= $start as u32 && $key as u32 <= $end as u32 + }; +} + +macro_rules! key_is { + ($key:expr, $val:expr) => { + $key as u32 == $val as u32 + }; +} + +pub fn get_key_type(key: VirtualKey) -> KeyType { + if key_between!(key, VirtualKey::N1, VirtualKey::Plus) + || key_between!(key, VirtualKey::Q, VirtualKey::Oem6) + || key_between!(key, VirtualKey::A, VirtualKey::Oem3) + || key_between!(key, VirtualKey::Oem5, VirtualKey::Oem2) + || key_is!(key, VirtualKey::Oem102) + { + KeyType::Symbol + } else if key_between!(key, VirtualKey::KP_7, VirtualKey::KP_0) + && !key_is!(key, VirtualKey::KP_Subtract) + && !key_is!(key, VirtualKey::KP_Add) + { + KeyType::NumPad + } else { + KeyType::Other + } +} + +pub struct XkbKeymap { + pub context: xkb::Context, + pub keymap: xkb::Keymap, +} + +impl XkbKeymap { + pub fn label_for_key(&self, key: VirtualKey, modifier: KeyModifier) -> String { + let mut state = xkb::State::new(&self.keymap); + if modifier > 0 { + if let Some(mod_key) = MODS_TO_KEYS.get(modifier) { + state.update_key( + xkb::Keycode::from(mod_key[0] as u32), + xkb::KeyDirection::Down, + ); + } + } + state.key_get_utf8(xkb::Keycode::from(key as u32)) + } + + pub fn has_altgr(&self) -> bool { + let state0 = xkb::State::new(&self.keymap); + let mut state1 = xkb::State::new(&self.keymap); + state1.update_key( + xkb::Keycode::from(VirtualKey::Meta as u32), + xkb::KeyDirection::Down, + ); + + for key in [ + VirtualKey::N0, + VirtualKey::N1, + VirtualKey::N2, + VirtualKey::N3, + VirtualKey::N4, + VirtualKey::N5, + VirtualKey::N6, + VirtualKey::N7, + VirtualKey::N8, + VirtualKey::N9, + ] { + let sym0 = state0.key_get_one_sym(xkb::Keycode::from(key as u32)); + let sym1 = state1.key_get_one_sym(xkb::Keycode::from(key as u32)); + if sym0 != sym1 { + return true; + } + } + false + } +} + +#[cfg(feature = "wayland")] +pub use wayland::get_keymap_wl; + +#[cfg(not(feature = "wayland"))] +pub fn get_keymap_wl() -> anyhow::Result {} + +#[cfg(feature = "x11")] +pub use x11::get_keymap_x11; + +#[cfg(not(feature = "x11"))] +pub fn get_keymap_x11() -> anyhow::Result {} diff --git a/src/hid/wayland.rs b/src/hid/wayland.rs new file mode 100644 index 0000000..0e251a8 --- /dev/null +++ b/src/hid/wayland.rs @@ -0,0 +1,142 @@ +use wlx_capture::wayland::wayland_client::{ + globals::{registry_queue_init, GlobalListContents}, + protocol::{ + wl_keyboard::{self, WlKeyboard}, + wl_registry::WlRegistry, + wl_seat::{self, Capability, WlSeat}, + }, + Connection, Dispatch, Proxy, QueueHandle, +}; +use xkbcommon::xkb; + +use super::XkbKeymap; + +struct WlKeymapHandler { + seat: WlSeat, + keyboard: Option, + keymap: Option, +} + +impl Drop for WlKeymapHandler { + fn drop(&mut self) { + if let Some(keyboard) = &self.keyboard { + keyboard.release(); + } + self.seat.release(); + } +} + +pub fn get_keymap_wl() -> anyhow::Result { + let connection = Connection::connect_to_env()?; + let (globals, mut queue) = registry_queue_init::(&connection)?; + let qh = queue.handle(); + let seat: WlSeat = globals + .bind(&qh, 4..=9, ()) + .expect(WlSeat::interface().name); + + let mut me = WlKeymapHandler { + seat, + keyboard: None, + keymap: None, + }; + + // this gets us the wl_seat + let _ = queue.blocking_dispatch(&mut me); + + // this gets us the wl_keyboard + let _ = queue.blocking_dispatch(&mut me); + + if let Some(keymap) = me.keymap.take() { + Ok(keymap) + } else { + Err(anyhow::anyhow!("Could not load keymap")) + } +} + +impl Dispatch for WlKeymapHandler { + fn event( + _state: &mut Self, + _proxy: &WlRegistry, + _event: ::Event, + _data: &GlobalListContents, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +impl Dispatch for WlKeymapHandler { + fn event( + state: &mut Self, + proxy: &WlSeat, + event: ::Event, + _data: &(), + _conn: &Connection, + qhandle: &QueueHandle, + ) { + match event { + wl_seat::Event::Capabilities { capabilities } => { + let capability = capabilities + .into_result() + .unwrap_or(wl_seat::Capability::empty()); + if capability.contains(Capability::Keyboard) { + state.keyboard = Some(proxy.get_keyboard(qhandle, ())); + } + } + wl_seat::Event::Name { name } => { + log::debug!("Using WlSeat: {}", name); + } + _ => {} + } + } +} + +impl Dispatch for WlKeymapHandler { + fn event( + state: &mut Self, + _proxy: &WlKeyboard, + event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + match event { + wl_keyboard::Event::Keymap { format, fd, size } => { + let format = format + .into_result() + .unwrap_or(wl_keyboard::KeymapFormat::NoKeymap); + + if matches!(format, wl_keyboard::KeymapFormat::XkbV1) { + let context = xkb::Context::new(xkb::CONTEXT_NO_DEFAULT_INCLUDES); + let maybe_keymap = unsafe { + xkb::Keymap::new_from_fd( + &context, + fd, + size as _, + xkb::KEYMAP_FORMAT_TEXT_V1, + xkb::KEYMAP_COMPILE_NO_FLAGS, + ) + }; + + match maybe_keymap { + Ok(Some(keymap)) => { + state.keymap = Some(XkbKeymap { context, keymap }); + } + Ok(None) => { + log::error!("Could not load keymap: no keymap"); + log::error!("Default layout will be used."); + } + Err(err) => { + log::error!("Could not load keymap: {}", err); + log::error!("Default layout will be used."); + } + } + } + } + wl_keyboard::Event::RepeatInfo { rate, delay } => { + log::debug!("WlKeyboard RepeatInfo rate: {}, delay: {}", rate, delay); + } + _ => {} + } + } +} diff --git a/src/hid/x11.rs b/src/hid/x11.rs new file mode 100644 index 0000000..c6d9a63 --- /dev/null +++ b/src/hid/x11.rs @@ -0,0 +1,16 @@ +use xkbcommon::xkb::{ + self, + x11::{get_core_keyboard_device_id, keymap_new_from_device}, +}; + +use super::XkbKeymap; + +pub fn get_keymap_x11() -> anyhow::Result { + let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); + + let (conn, _) = xcb::Connection::connect(None)?; + let device_id = get_core_keyboard_device_id(&conn); + let keymap = keymap_new_from_device(&context, &conn, device_id, xkb::KEYMAP_COMPILE_NO_FLAGS); + + Ok(XkbKeymap { context, keymap }) +} diff --git a/src/overlays/keyboard.rs b/src/overlays/keyboard.rs index e3dfecf..5a2461e 100644 --- a/src/overlays/keyboard.rs +++ b/src/overlays/keyboard.rs @@ -10,8 +10,11 @@ use crate::{ overlay::{OverlayData, OverlayState}, }, config::{self, ConfigType}, - gui::{color_parse, CanvasBuilder, Control}, - hid::{KeyModifier, VirtualKey, ALT, CTRL, KEYS_TO_MODS, META, SHIFT, SUPER}, + gui::{color_parse, CanvasBuilder, Control, KeyCapType}, + hid::{ + get_key_type, KeyModifier, KeyType, VirtualKey, XkbKeymap, ALT, CTRL, KEYS_TO_MODS, META, + NUM_LOCK, SHIFT, SUPER, + }, state::AppState, }; use glam::{vec2, vec3a, Affine2, Vec4}; @@ -25,7 +28,10 @@ const AUTO_RELEASE_MODS: [KeyModifier; 5] = [SHIFT, CTRL, ALT, SUPER, META]; pub const KEYBOARD_NAME: &str = "kbd"; -pub fn create_keyboard(app: &AppState) -> anyhow::Result> +pub fn create_keyboard( + app: &AppState, + keymap: Option, +) -> anyhow::Result> where O: Default, { @@ -53,6 +59,8 @@ where canvas.font_size = 18; canvas.bg_color = color_parse("#202020").unwrap(); //safe + let has_altgr = keymap.as_ref().map_or(false, |k| k.has_altgr()); + let unit_size = size.x / LAYOUT.row_size; let h = unit_size - 2. * BUTTON_PADDING; @@ -66,8 +74,43 @@ where let w = unit_size * my_size - 2. * BUTTON_PADDING; if let Some(key) = LAYOUT.main_layout[row][col].as_ref() { + let mut label = Vec::with_capacity(2); let mut maybe_state: Option = None; + let mut cap_type = KeyCapType::Regular; + if let Ok(vk) = VirtualKey::from_str(key) { + if let Some(keymap) = keymap.as_ref() { + match get_key_type(vk) { + KeyType::Symbol => { + let label0 = keymap.label_for_key(vk, 0); + let label1 = keymap.label_for_key(vk, SHIFT); + + if label0.chars().next().map_or(false, |f| f.is_alphabetic()) { + label.push(label1); + if has_altgr { + cap_type = KeyCapType::RegularAltGr; + label.push(keymap.label_for_key(vk, META)); + } else { + cap_type = KeyCapType::Regular; + } + } else { + label.push(label0); + label.push(label1); + if has_altgr { + label.push(keymap.label_for_key(vk, META)); + cap_type = KeyCapType::ReversedAltGr; + } else { + cap_type = KeyCapType::Reversed; + } + } + } + KeyType::NumPad => { + label.push(keymap.label_for_key(vk, NUM_LOCK)); + } + KeyType::Other => {} + } + } + if let Some(mods) = KEYS_TO_MODS.get(vk) { maybe_state = Some(KeyButtonData::Modifier { modifier: *mods, @@ -97,8 +140,10 @@ where } if let Some(state) = maybe_state { - let label = LAYOUT.label_for_key(key); - let button = canvas.key_button(x, y, w, h, &label); + if label.is_empty() { + label = LAYOUT.label_for_key(key); + } + let button = canvas.key_button(x, y, w, h, cap_type, &label); button.state = Some(state); button.on_press = Some(key_press); button.on_release = Some(key_release); diff --git a/src/res/keyboard.yaml b/src/res/keyboard.yaml index ae4b1b8..992cca3 100644 --- a/src/res/keyboard.yaml +++ b/src/res/keyboard.yaml @@ -26,7 +26,7 @@ key_sizes: - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0.5, 1, 1, 1, 0.5, 1, 1, 1, 1] - [1.5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.5, 0.5, 1, 1, 1, 0.5, 1, 1, 1, 1] - [1.75, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.25, 4, 1, 1, 1, 1] - - [2.25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.75, 1.5, 1, 1.5, 1, 1, 1, 1] + - [1.25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2.75, 1.5, 1, 1.5, 1, 1, 1, 1] - [1.25, 1.25, 1.25, 6.25, 1.25, 1.25, 1.25, 1.25, 0.5, 1, 1, 1, 0.5, 2, 1, 1] # The main (blue) layout of the keyboard. @@ -40,7 +40,7 @@ main_layout: - ["Oem3", "N1", "N2", "N3", "N4", "N5", "N6", "N7", "N8", "N9", "N0", "Minus", "Plus", "BackSpace", ~, "Insert", "Home", "Prior", ~, "NumLock", "KP_Divide", "KP_Multiply", "KP_Subtract"] - ["Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "Oem4", "Oem6", "Oem5", ~, "Delete", "End", "Next", ~, "KP_7", "KP_8", "KP_9", "KP_Add"] - ["XF86Favorites", "A", "S", "D", "F", "G", "H", "J", "K", "L", "Oem1", "Oem7", "Return", ~, "KP_4", "KP_5", "KP_6", ~] - - ["LShift", "Z", "X", "C", "V", "B", "N", "M", "Comma", "Period", "Oem2", "RShift", ~, "Up", ~, "KP_1", "KP_2", "KP_3", "KP_Enter"] + - ["LShift", "Oem102", "Z", "X", "C", "V", "B", "N", "M", "Comma", "Period", "Oem2", "RShift", ~, "Up", ~, "KP_1", "KP_2", "KP_3", "KP_Enter"] - ["LCtrl", "LSuper", "LAlt", "Space", "Meta", "RSuper", "Menu", "RCtrl", ~, "Left", "Down", "Right", ~, "KP_0", "KP_Decimal", ~] # Shell commands to be used in a layout. @@ -62,16 +62,6 @@ macros: # Value: Array of strings. 0th element is the upper row, 1st element is lower row. # For empty labels, use [] (do not use ~) labels: - "N1": ["1", "!"] - "N2": ["2", "@"] - "N3": ["3", "#"] - "N4": ["4", "$"] - "N5": ["5", "%"] - "N6": ["6", "^"] - "N7": ["7", "&"] - "N8": ["8", "*"] - "N9": ["9", "("] - "N0": ["0", ")"] "Escape": ["Esc"] "Prior": ["PgUp"] "Next": ["PgDn"] @@ -86,24 +76,15 @@ labels: "RShift": ["Shift"] "Insert": ["Ins"] "Delete": ["Del"] - "Minus": ["-", "_"] - "Plus": ["=", "+"] "BackSpace": ["<<"] - "Comma": [" ,", "<"] - "Period": [" .", ">"] - "Oem1": [" ;", ":"] - "Oem2": [" /", "?"] - "Oem3": ["`", "~"] - "Oem4": [" [", "{"] - "Oem5": [" \\", "|"] - "Oem6": [" ]", "}"] - "Oem7": [" '", "\""] - "Oem102": [" \\", "|"] "KP_Divide": [" /"] "KP_Add": [" +"] "KP_Multiply": [" *"] "KP_Decimal": [" ."] "KP_Subtract": [" -"] "KP_Enter": ["Ent"] + "Print": ["Prn"] + "Scroll": ["Scr"] + "Pause": ["Brk"] "XF86Favorites": ["Rofi"]