diff --git a/deploy-notes.md b/deploy-notes.md index 96c414fb..edd35a59 100644 --- a/deploy-notes.md +++ b/deploy-notes.md @@ -1,5 +1,9 @@ # Unreleased +- Needs jay-config release. +- Needs jay-toml-config release. +- Needs jay-compositor release. + # 1.0.3 - Needs jay-compositor release. diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index eb2d061f..462b48b4 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -818,6 +818,10 @@ impl Client { self.send(&ClientMessage::SetSeat { device, seat }) } + pub fn set_device_keymap(&self, device: InputDevice, keymap: Keymap) { + self.send(&ClientMessage::DeviceSetKeymap { device, keymap }) + } + pub fn set_left_handed(&self, device: InputDevice, left_handed: bool) { self.send(&ClientMessage::SetLeftHanded { device, diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 702c9428..844f9a47 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -432,6 +432,10 @@ pub enum ClientMessage<'a> { enabled: bool, }, GetSocketPath, + DeviceSetKeymap { + device: InputDevice, + keymap: Keymap, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index b0db1cdd..08322e6d 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -25,6 +25,16 @@ impl InputDevice { get!().set_seat(self, seat) } + /// Sets the keymap of the device. + /// + /// This overrides the keymap set for the seat. The keymap becomes active when a key + /// on the device is pressed. + /// + /// Setting the invalid keymap reverts to the seat keymap. + pub fn set_keymap(self, keymap: Keymap) { + get!().set_device_keymap(self, keymap) + } + /// Returns whether the device has the specified capability. pub fn has_capability(self, cap: Capability) -> bool { get!(false).has_capability(self, cap) diff --git a/release-notes.md b/release-notes.md index e3a4d288..7e88133b 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,6 +1,7 @@ # Unreleased - Add support for wp-alpha-modifier. +- Add support for per-device keymaps. # 1.0.3 (2024-04-11) diff --git a/src/cli/input.rs b/src/cli/input.rs index 6b55249b..a67dde78 100644 --- a/src/cli/input.rs +++ b/src/cli/input.rs @@ -19,7 +19,7 @@ use { ops::DerefMut, rc::Rc, }, - uapi::c, + uapi::{c, OwnedFd}, }; #[derive(Args, Debug)] @@ -119,6 +119,10 @@ pub enum DeviceCommand { SetPxPerWheelScroll(SetPxPerWheelScrollArgs), /// Set the transformation matrix. SetTransformMatrix(SetTransformMatrixArgs), + /// Set the keymap of this device. + SetKeymap(SetKeymapArgs), + /// Retrieve the keymap of this device. + Keymap, /// Attach the device to a seat. Attach(AttachArgs), /// Detach the device from its seat. @@ -292,6 +296,47 @@ impl Input { }); } + fn prepare_keymap(&self, a: &SetKeymapArgs) -> (Rc, usize) { + let map = match &a.file { + None => { + let mut map = vec![]; + if let Err(e) = stdin().read_to_end(&mut map) { + eprintln!("Could not read from stdin: {}", ErrorFmt(e)); + std::process::exit(1); + } + map + } + Some(f) => match std::fs::read(f) { + Ok(m) => m, + Err(e) => { + eprintln!("Could not read {}: {}", f, ErrorFmt(e)); + std::process::exit(1); + } + }, + }; + let mut memfd = + uapi::memfd_create("keymap", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING).unwrap(); + memfd.write_all(&map).unwrap(); + uapi::lseek(memfd.raw(), 0, c::SEEK_SET).unwrap(); + uapi::fcntl_add_seals( + memfd.raw(), + c::F_SEAL_SEAL | c::F_SEAL_GROW | c::F_SEAL_SHRINK | c::F_SEAL_WRITE, + ) + .unwrap(); + (Rc::new(memfd), map.len()) + } + + async fn handle_keymap(&self, input: JayInputId) -> Vec { + let data = Rc::new(RefCell::new(Vec::new())); + jay_input::Keymap::handle(&self.tc, input, data.clone(), |d, map| { + let mem = Rc::new(ClientMem::new(map.keymap.raw(), map.keymap_len as _, true).unwrap()) + .offset(0); + mem.read(d.borrow_mut().deref_mut()).unwrap(); + }); + self.tc.round_trip().await; + data.take() + } + async fn seat(self: &Rc, input: JayInputId, args: SeatArgs) { let tc = &self.tc; match args.command.unwrap_or_default() { @@ -318,40 +363,15 @@ impl Input { }); } SeatCommand::SetKeymap(a) => { + let (memfd, len) = self.prepare_keymap(&a); self.handle_error(input, |e| { eprintln!("Could not set keymap: {}", e); }); - let map = match &a.file { - None => { - let mut map = vec![]; - if let Err(e) = stdin().read_to_end(&mut map) { - eprintln!("Could not read from stdin: {}", ErrorFmt(e)); - std::process::exit(1); - } - map - } - Some(f) => match std::fs::read(f) { - Ok(m) => m, - Err(e) => { - eprintln!("Could not read {}: {}", f, ErrorFmt(e)); - std::process::exit(1); - } - }, - }; - let mut memfd = - uapi::memfd_create("keymap", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING).unwrap(); - memfd.write_all(&map).unwrap(); - uapi::lseek(memfd.raw(), 0, c::SEEK_SET).unwrap(); - uapi::fcntl_add_seals( - memfd.raw(), - c::F_SEAL_SEAL | c::F_SEAL_GROW | c::F_SEAL_SHRINK | c::F_SEAL_WRITE, - ) - .unwrap(); tc.send(jay_input::SetKeymap { self_id: input, seat: &args.seat, - keymap: Rc::new(memfd), - keymap_len: map.len() as _, + keymap: memfd, + keymap_len: len as _, }); } SeatCommand::UseHardwareCursor(a) => { @@ -368,20 +388,11 @@ impl Input { self.handle_error(input, |e| { eprintln!("Could not retrieve the keymap: {}", e); }); - let data = Rc::new(RefCell::new(Vec::new())); - jay_input::Keymap::handle(tc, input, data.clone(), |d, map| { - let mem = Rc::new( - ClientMem::new(map.keymap.raw(), map.keymap_len as _, true).unwrap(), - ) - .offset(0); - mem.read(d.borrow_mut().deref_mut()).unwrap(); - }); tc.send(jay_input::GetKeymap { self_id: input, seat: &args.seat, }); - tc.round_trip().await; - let map = data.take(); + let map = self.handle_keymap(input).await; stdout().write_all(&map).unwrap(); } SeatCommand::SetCursorSize(a) => { @@ -530,6 +541,29 @@ impl Input { id: args.device, }); } + DeviceCommand::SetKeymap(a) => { + let (memfd, len) = self.prepare_keymap(&a); + self.handle_error(input, |e| { + eprintln!("Could not set keymap: {}", e); + }); + tc.send(jay_input::SetDeviceKeymap { + self_id: input, + id: args.device, + keymap: memfd, + keymap_len: len as _, + }); + } + DeviceCommand::Keymap => { + self.handle_error(input, |e| { + eprintln!("Could not retrieve the keymap: {}", e); + }); + tc.send(jay_input::GetDeviceKeymap { + self_id: input, + id: args.device, + }); + let map = self.handle_keymap(input).await; + stdout().write_all(&map).unwrap(); + } } tc.round_trip().await; } diff --git a/src/config/handler.rs b/src/config/handler.rs index a2aa0014..ad39226d 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -299,7 +299,23 @@ impl ConfigProxyHandler { } else { self.get_keymap(keymap)? }; - seat.set_keymap(&keymap); + seat.set_seat_keymap(&keymap); + Ok(()) + } + + fn handle_set_device_keymap( + &self, + device: InputDevice, + keymap: Keymap, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + if keymap.is_invalid() { + dev.keymap_id.set(None); + dev.keymap.set(None); + } else { + let map = self.get_keymap(keymap)?; + dev.set_keymap(&map); + }; Ok(()) } @@ -1746,6 +1762,9 @@ impl ConfigProxyHandler { self.handle_set_explicit_sync_enabled(enabled) } ClientMessage::GetSocketPath => self.handle_get_socket_path(), + ClientMessage::DeviceSetKeymap { device, keymap } => self + .handle_set_device_keymap(device, keymap) + .wrn("set_device_keymap")?, } Ok(()) } diff --git a/src/ifs/jay_input.rs b/src/ifs/jay_input.rs index 0f483617..28617ee2 100644 --- a/src/ifs/jay_input.rs +++ b/src/ifs/jay_input.rs @@ -13,10 +13,11 @@ use { state::{DeviceHandlerData, InputDeviceData}, utils::errorfmt::ErrorFmt, wire::{jay_input::*, JayInputId}, - xkbcommon::XkbCommonError, + xkbcommon::{XkbCommonError, XkbKeymap}, }, std::rc::Rc, thiserror::Error, + uapi::OwnedFd, }; pub struct JayInput { @@ -67,8 +68,7 @@ impl JayInput { }); } - fn send_keymap(&self, data: &WlSeatGlobal) { - let map = data.keymap(); + fn send_keymap(&self, map: &XkbKeymap) { self.client.event(Keymap { self_id: self.id, keymap: map.map.clone(), @@ -138,6 +138,20 @@ impl JayInput { Some(d) => Ok(d.data.clone()), } } + + fn set_keymap_impl(&self, keymap: &OwnedFd, len: u32, f: F) -> Result<(), JayInputError> + where + F: FnOnce(&Rc) -> Result<(), JayInputError>, + { + let cm = Rc::new(ClientMem::new(keymap.raw(), len as _, true)?).offset(0); + let mut map = vec![]; + cm.read(&mut map)?; + self.or_error(|| { + let map = self.client.state.xkb_ctx.keymap_from_str(&map)?; + f(&map)?; + Ok(()) + }) + } } impl JayInputRequestHandler for JayInput { @@ -174,13 +188,9 @@ impl JayInputRequestHandler for JayInput { } fn set_keymap(&self, req: SetKeymap, _slf: &Rc) -> Result<(), Self::Error> { - let cm = Rc::new(ClientMem::new(req.keymap.raw(), req.keymap_len as _, true)?).offset(0); - let mut map = vec![]; - cm.read(&mut map)?; - self.or_error(|| { - let map = self.client.state.xkb_ctx.keymap_from_str(&map)?; + self.set_keymap_impl(&req.keymap, req.keymap_len, |map| { let seat = self.seat(req.seat)?; - seat.set_keymap(&map); + seat.set_seat_keymap(&map); Ok(()) }) } @@ -200,7 +210,7 @@ impl JayInputRequestHandler for JayInput { fn get_keymap(&self, req: GetKeymap, _slf: &Rc) -> Result<(), Self::Error> { self.or_error(|| { let seat = self.seat(req.seat)?; - self.send_keymap(&seat); + self.send_keymap(&seat.keymap()); Ok(()) }) } @@ -360,6 +370,24 @@ impl JayInputRequestHandler for JayInput { } }) } + + fn set_device_keymap(&self, req: SetDeviceKeymap, _slf: &Rc) -> Result<(), Self::Error> { + self.set_keymap_impl(&req.keymap, req.keymap_len, |map| { + let dev = self.device(req.id)?; + dev.set_keymap(&map); + Ok(()) + }) + } + + fn get_device_keymap(&self, req: GetDeviceKeymap, _slf: &Rc) -> Result<(), Self::Error> { + self.or_error(|| { + let dev = self.device(req.id)?; + if let Some(map) = dev.keymap.get() { + self.send_keymap(&map); + } + Ok(()) + }) + } } object_base! { diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 0cfb87a7..7b6dced8 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -56,16 +56,16 @@ use { utils::{ asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, linkedlist::LinkedNode, numcell::NumCell, rc_eq::rc_eq, - smallmap::SmallMap, transform_ext::TransformExt, + smallmap::SmallMap, transform_ext::TransformExt, vecset::VecSet, }, wire::{ wl_seat::*, ExtIdleNotificationV1Id, WlDataDeviceId, WlKeyboardId, WlPointerId, WlSeatId, ZwlrDataControlDeviceV1Id, ZwpPrimarySelectionDeviceV1Id, ZwpRelativePointerV1Id, }, - xkbcommon::{XkbKeymap, XkbState}, + xkbcommon::{KeymapId, XkbKeymap, XkbState}, }, - ahash::{AHashMap, AHashSet}, + ahash::AHashMap, jay_config::keyboard::mods::Modifiers, smallvec::SmallVec, std::{ @@ -128,7 +128,7 @@ pub struct WlSeatGlobal { pointer_stack_modified: Cell, found_tree: RefCell>, keyboard_node: CloneCell>, - pressed_keys: RefCell>, + pressed_keys: RefCell>, bindings: RefCell>>>, x_data_devices: SmallMap, 1>, data_devices: RefCell>>>, @@ -141,7 +141,10 @@ pub struct WlSeatGlobal { wlr_data_devices: CopyHashMap<(ClientId, ZwlrDataControlDeviceV1Id), Rc>, repeat_rate: Cell<(i32, i32)>, - kb_map: CloneCell>, + seat_kb_map: CloneCell>, + seat_kb_map_id: Cell, + effective_kb_map: CloneCell>, + effective_kb_map_id: Cell, kb_state: RefCell, cursor: CloneCell>>, tree_changed: Rc, @@ -163,6 +166,7 @@ pub struct WlSeatGlobal { constraint: CloneCell>>, idle_notifications: CopyHashMap<(ClientId, ExtIdleNotificationV1Id), Rc>, last_input_usec: Cell, + keymap_version: NumCell, } const CHANGE_CURSOR_MOVED: u32 = 1 << 0; @@ -196,7 +200,10 @@ impl WlSeatGlobal { data_devices: RefCell::new(Default::default()), primary_selection_devices: RefCell::new(Default::default()), repeat_rate: Cell::new((25, 250)), - kb_map: CloneCell::new(state.default_keymap.clone()), + seat_kb_map: CloneCell::new(state.default_keymap.clone()), + seat_kb_map_id: Cell::new(state.default_keymap.id), + effective_kb_map: CloneCell::new(state.default_keymap.clone()), + effective_kb_map_id: Cell::new(state.default_keymap.id), kb_state: RefCell::new(state.default_keymap.state().unwrap()), cursor: Default::default(), tree_changed: Default::default(), @@ -219,6 +226,7 @@ impl WlSeatGlobal { idle_notifications: Default::default(), last_input_usec: Cell::new(now_usec()), wlr_data_devices: Default::default(), + keymap_version: NumCell::new(1), }); state.add_cursor_size(*DEFAULT_CURSOR_SIZE); let seat = slf.clone(); @@ -236,7 +244,7 @@ impl WlSeatGlobal { } pub fn keymap(&self) -> Rc { - self.kb_map.get() + self.seat_kb_map.get() } pub fn toplevel_drag(&self) -> Option> { @@ -505,7 +513,12 @@ impl WlSeatGlobal { false } - pub fn set_keymap(&self, keymap: &Rc) { + pub fn set_seat_keymap(&self, keymap: &Rc) { + self.seat_kb_map.set(keymap.clone()); + self.seat_kb_map_id.set(keymap.id); + } + + fn set_effective_keymap(&self, keymap: &Rc) { let state = match keymap.state() { Ok(s) => s, Err(e) => { @@ -513,24 +526,13 @@ impl WlSeatGlobal { return; } }; - self.kb_map.set(keymap.clone()); + self.keyboard_node.get().node_on_unfocus(self); + self.effective_kb_map.set(keymap.clone()); + self.effective_kb_map_id.set(keymap.id); *self.kb_state.borrow_mut() = state; - let bindings = self.bindings.borrow_mut(); - for (id, client) in bindings.iter() { - for seat in client.values() { - let kbs = seat.keyboards.lock(); - for kb in kbs.values() { - let fd = match seat.keymap_fd(keymap) { - Ok(fd) => fd, - Err(e) => { - log::error!("Could not creat a file descriptor to transfer the keymap to client {}: {}", id, ErrorFmt(e)); - continue; - } - }; - kb.send_keymap(wl_keyboard::XKB_V1, fd, keymap.map_len as _); - } - } - } + self.keymap_version.fetch_add(1); + self.pressed_keys.borrow_mut().clear(); + self.keyboard_node.get().node_on_focus(self); } pub fn prepare_for_lock(self: &Rc) { @@ -1180,12 +1182,13 @@ impl WlSeatRequestHandler for WlSeat { track!(self.client, p); self.client.add_client_obj(&p)?; self.keyboards.set(req.id, p.clone()); - let keymap = self.global.kb_map.get(); - p.send_keymap( - wl_keyboard::XKB_V1, - self.keymap_fd(&keymap)?, - keymap.map_len as _, - ); + p.send_keymap(); + if let Some(surface) = self.global.keyboard_node.get().node_into_surface() { + if surface.client.id == self.client.id { + let serial = self.client.next_serial(); + p.send_enter(serial, surface.id, &self.global.pressed_keys.borrow()) + } + } if self.version >= REPEAT_INFO_SINCE { let (rate, delay) = self.global.repeat_rate.get(); p.send_repeat_info(rate, delay); diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index aab12a45..440e706f 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -196,7 +196,20 @@ impl WlSeatGlobal { time_usec, key, state, - } => self.key_event(time_usec, key, state), + } => { + let desired_kb_map_id = match dev.keymap_id.get() { + Some(id) => id, + None => self.seat_kb_map_id.get(), + }; + if desired_kb_map_id != self.effective_kb_map_id.get() { + let map = match dev.keymap.get() { + Some(map) => map, + None => self.seat_kb_map.get(), + }; + self.set_effective_keymap(&map); + } + self.key_event(time_usec, key, state) + } InputEvent::ConnectorPosition { time_usec, connector, @@ -742,10 +755,10 @@ impl WlSeatGlobal { // Focus callbacks impl WlSeatGlobal { pub fn focus_surface(&self, surface: &WlSurface) { - let pressed_keys: Vec<_> = self.pressed_keys.borrow().iter().copied().collect(); + let pressed_keys = &*self.pressed_keys.borrow(); let serial = surface.client.next_serial(); self.surface_kb_event(Version::ALL, surface, |k| { - k.send_enter(serial, surface.id, &pressed_keys) + k.send_enter(serial, surface.id, pressed_keys) }); let ModifierState { mods_depressed, diff --git a/src/ifs/wl_seat/wl_keyboard.rs b/src/ifs/wl_seat/wl_keyboard.rs index f5089eaf..73f1f99c 100644 --- a/src/ifs/wl_seat/wl_keyboard.rs +++ b/src/ifs/wl_seat/wl_keyboard.rs @@ -4,12 +4,11 @@ use { ifs::wl_seat::WlSeat, leaks::Tracker, object::{Object, Version}, - utils::oserror::OsError, + utils::{errorfmt::ErrorFmt, numcell::NumCell, oserror::OsError}, wire::{wl_keyboard::*, WlKeyboardId, WlSurfaceId}, }, std::rc::Rc, thiserror::Error, - uapi::OwnedFd, }; pub const REPEAT_INFO_SINCE: Version = Version(4); @@ -24,6 +23,7 @@ pub(super) const PRESSED: u32 = 1; pub struct WlKeyboard { id: WlKeyboardId, seat: Rc, + pub(super) keymap_version: NumCell, pub tracker: Tracker, } @@ -32,20 +32,38 @@ impl WlKeyboard { Self { id, seat: seat.clone(), + keymap_version: NumCell::new(0), tracker: Default::default(), } } - pub fn send_keymap(self: &Rc, format: u32, fd: Rc, size: u32) { + pub fn send_keymap(&self) { + let map = self.seat.global.effective_kb_map.get(); + let fd = match self.seat.keymap_fd(&map) { + Ok(fd) => fd, + Err(e) => { + log::error!( + "Could not creat a file descriptor to transfer the keymap to client {}: {}", + self.seat.client.id, + ErrorFmt(e) + ); + return; + } + }; self.seat.client.event(Keymap { self_id: self.id, - format, + format: XKB_V1, fd, - size, - }) + size: map.map_len as _, + }); + self.keymap_version + .set(self.seat.global.keymap_version.get()); } pub fn send_enter(self: &Rc, serial: u32, surface: WlSurfaceId, keys: &[u32]) { + if self.keymap_version.get() != self.seat.global.keymap_version.get() { + self.send_keymap(); + } self.seat.client.event(Enter { self_id: self.id, serial, diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 74977c19..07a8940f 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -1399,7 +1399,7 @@ impl Node for WlSurface { seat.scroll_surface(&self, event); } - fn node_on_focus(self: Rc, seat: &Rc) { + fn node_on_focus(self: Rc, seat: &WlSeatGlobal) { if let Some(tl) = self.toplevel.get() { tl.tl_data().focus_node.insert(seat.id(), self.clone()); tl.tl_on_activate(); diff --git a/src/state.rs b/src/state.rs index e98715be..0a5522c3 100644 --- a/src/state.rs +++ b/src/state.rs @@ -74,7 +74,7 @@ use { ExtForeignToplevelListV1Id, JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId, ZwpLinuxDmabufFeedbackV1Id, }, - xkbcommon::{XkbContext, XkbKeymap}, + xkbcommon::{KeymapId, XkbContext, XkbKeymap}, xwayland::{self, XWaylandEvent}, }, ahash::AHashMap, @@ -244,6 +244,15 @@ pub struct DeviceHandlerData { pub device: Rc, pub syspath: Option, pub devnode: Option, + pub keymap_id: Cell>, + pub keymap: CloneCell>>, +} + +impl DeviceHandlerData { + pub fn set_keymap(&self, keymap: &Rc) { + self.keymap_id.set(Some(keymap.id)); + self.keymap.set(Some(keymap.clone())); + } } pub struct ConnectorData { diff --git a/src/tasks/input_device.rs b/src/tasks/input_device.rs index b2c84210..f32e8149 100644 --- a/src/tasks/input_device.rs +++ b/src/tasks/input_device.rs @@ -21,6 +21,8 @@ pub fn handle(state: &Rc, dev: Rc) { device: dev.clone(), syspath: props.syspath, devnode: props.devnode, + keymap_id: Default::default(), + keymap: Default::default(), }); let ae = Rc::new(AsyncEvent::default()); let oh = DeviceHandler { diff --git a/src/tree.rs b/src/tree.rs index a652e1a7..975ff0b0 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -190,7 +190,7 @@ pub trait Node: 'static { let _ = event; } - fn node_on_focus(self: Rc, seat: &Rc) { + fn node_on_focus(self: Rc, seat: &WlSeatGlobal) { let _ = seat; } diff --git a/src/utils.rs b/src/utils.rs index 44c9a5ce..0ab8c1d3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -50,6 +50,7 @@ pub mod trim; pub mod unlink_on_drop; pub mod vec_ext; pub mod vecdeque_ext; +pub mod vecset; pub mod vecstorage; pub mod windows; pub mod xrd; diff --git a/src/utils/vecset.rs b/src/utils/vecset.rs new file mode 100644 index 00000000..7333ec9b --- /dev/null +++ b/src/utils/vecset.rs @@ -0,0 +1,45 @@ +use std::ops::Deref; + +pub struct VecSet { + vec: Vec, +} + +impl Default for VecSet { + fn default() -> Self { + Self { vec: vec![] } + } +} + +impl Deref for VecSet { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + &self.vec + } +} + +impl VecSet { + pub fn clear(&mut self) { + self.vec.clear(); + } +} + +impl VecSet { + pub fn insert(&mut self, val: T) -> bool { + if self.vec.contains(&val) { + return false; + } + self.vec.push(val); + true + } + + pub fn remove(&mut self, val: &T) -> bool { + for i in 0..self.vec.len() { + if self.vec[i] == *val { + self.vec.swap_remove(i); + return true; + } + } + false + } +} diff --git a/src/xkbcommon.rs b/src/xkbcommon.rs index bbedd6ae..473f6ef8 100644 --- a/src/xkbcommon.rs +++ b/src/xkbcommon.rs @@ -101,12 +101,15 @@ extern "C" { pub struct XkbContext { context: *mut xkb_context, + ids: KeymapIds, } extern "C" { fn jay_xkbcommon_log_handler_bridge(); } +linear_ids!(KeymapIds, KeymapId, u64); + impl XkbContext { pub fn new() -> Result { let res = unsafe { xkb_context_new(XKB_CONTEXT_NO_FLAGS.raw() as _) }; @@ -117,10 +120,13 @@ impl XkbContext { xkb_context_set_log_verbosity(res, 10); xkb_context_set_log_fn(res, jay_xkbcommon_log_handler_bridge); } - Ok(Self { context: res }) + Ok(Self { + context: res, + ids: Default::default(), + }) } - fn raw_to_map(raw: *mut xkb_keymap) -> Result, XkbCommonError> { + fn raw_to_map(&self, raw: *mut xkb_keymap) -> Result, XkbCommonError> { let res = unsafe { xkb_keymap_get_as_string(raw, XKB_KEYMAP_FORMAT_TEXT_V1.raw() as _) }; if res.is_null() { unsafe { @@ -142,6 +148,7 @@ impl XkbContext { ) .unwrap(); Ok(Rc::new(XkbKeymap { + id: self.ids.next(), keymap: raw, map: Rc::new(memfd), map_len: str.len() + 1, @@ -164,7 +171,7 @@ impl XkbContext { if keymap.is_null() { return Err(XkbCommonError::KeymapFromBuffer); } - Self::raw_to_map(keymap) + self.raw_to_map(keymap) } } } @@ -178,6 +185,7 @@ impl Drop for XkbContext { } pub struct XkbKeymap { + pub id: KeymapId, keymap: *mut xkb_keymap, pub map: Rc, pub map_len: usize, diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index eb2458bf..d9b89760 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -241,6 +241,7 @@ pub struct Input { pub natural_scrolling: Option, pub px_per_wheel_scroll: Option, pub transform_matrix: Option<[[f64; 2]; 2]>, + pub keymap: Option, } #[derive(Debug, Clone)] diff --git a/toml-config/src/config/parsers/input.rs b/toml-config/src/config/parsers/input.rs index 8e8f2c20..ea4edc53 100644 --- a/toml-config/src/config/parsers/input.rs +++ b/toml-config/src/config/parsers/input.rs @@ -4,7 +4,10 @@ use { context::Context, extractor::{bol, fltorint, opt, recover, str, val, Extractor, ExtractorError}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, - parsers::input_match::{InputMatchParser, InputMatchParserError}, + parsers::{ + input_match::{InputMatchParser, InputMatchParserError}, + keymap::KeymapParser, + }, Input, }, toml::{ @@ -62,7 +65,7 @@ impl<'a> Parser for InputParser<'a> { natural_scrolling, px_per_wheel_scroll, ), - (transform_matrix,), + (transform_matrix, keymap), ) = ext.extract(( ( opt(str("tag")), @@ -76,7 +79,7 @@ impl<'a> Parser for InputParser<'a> { recover(opt(bol("natural-scrolling"))), recover(opt(fltorint("px-per-wheel-scroll"))), ), - (recover(opt(val("transform-matrix"))),), + (recover(opt(val("transform-matrix"))), opt(val("keymap"))), ))?; let accel_profile = match accel_profile { None => None, @@ -109,6 +112,19 @@ impl<'a> Parser for InputParser<'a> { ); } } + let keymap = match keymap { + None => None, + Some(map) => match map.parse(&mut KeymapParser { + cx: self.cx, + definition: false, + }) { + Ok(v) => Some(v), + Err(e) => { + log::warn!("Could not parse keymap: {}", self.cx.error(e)); + None + } + }, + }; Ok(Input { tag: tag.despan_into(), match_: match_val.parse_map(&mut InputMatchParser(self.cx))?, @@ -121,6 +137,7 @@ impl<'a> Parser for InputParser<'a> { natural_scrolling: natural_scrolling.despan(), px_per_wheel_scroll: px_per_wheel_scroll.despan(), transform_matrix, + keymap, }) } } diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index d40318f8..fdabf3e1 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -92,7 +92,7 @@ impl Action { Box::new(move || { for c in input_devices() { if input.match_.matches(c, &state) { - input.apply(c); + input.apply(c, &state); } } }) @@ -361,7 +361,7 @@ impl InputMatch { } impl Input { - fn apply(&self, c: InputDevice) { + fn apply(&self, c: InputDevice, state: &State) { if let Some(v) = self.accel_profile { c.set_accel_profile(v); } @@ -389,6 +389,11 @@ impl Input { if let Some(v) = self.transform_matrix { c.set_transform_matrix(v); } + if let Some(v) = &self.keymap { + if let Some(km) = state.get_keymap(v) { + c.set_keymap(km); + } + } } } @@ -554,19 +559,25 @@ impl State { } } - fn set_keymap(&self, map: &ConfigKeymap) { + fn get_keymap(&self, map: &ConfigKeymap) -> Option { let map = match map { ConfigKeymap::Named(n) => match self.keymaps.get(n) { None => { log::warn!("Unknown keymap {n}"); - return; + return None; } Some(m) => *m, }, ConfigKeymap::Defined { map, .. } => *map, ConfigKeymap::Literal(map) => *map, }; - self.persistent.seat.set_keymap(map); + Some(map) + } + + fn set_keymap(&self, map: &ConfigKeymap) { + if let Some(map) = self.get_keymap(map) { + self.persistent.seat.set_keymap(map); + } } fn set_status(&self, status: &Option) { @@ -816,7 +827,7 @@ fn load_config(initial_load: bool, persistent: &Rc) { move |c| { for input in &config.inputs { if input.match_.matches(c, &state) { - input.apply(c); + input.apply(c, &state); } } } diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 560ef5b6..81b1647e 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -790,6 +790,10 @@ "description": "" } } + }, + "keymap": { + "description": "The keymap to use for this device.\n\nThis overrides the global keymap. The keymap becomes active when a key is pressed.\n\n- Example:\n\n ```toml\n [[inputs]]\n match.name = \"ZSA Technology Labs Inc ErgoDox EZ\"\n keymap.name = \"external\"\n ```\n", + "$ref": "#/$defs/Keymap" } }, "required": [ diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index eeaa716d..5032b4fd 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -1519,6 +1519,22 @@ The table has the following fields: The value of this field should be an array of arrays of numbers. +- `keymap` (optional): + + The keymap to use for this device. + + This overrides the global keymap. The keymap becomes active when a key is pressed. + + - Example: + + ```toml + [[inputs]] + match.name = "ZSA Technology Labs Inc ErgoDox EZ" + keymap.name = "external" + ``` + + The value of this field should be a [Keymap](#types-Keymap). + ### `InputMatch` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index 7d2e416b..9d79a882 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -1206,6 +1206,21 @@ Input: match.is-pointer = true transform-matrix = [[0.35, 0], [0, 0.35]] ``` + keymap: + ref: Keymap + required: false + description: | + The keymap to use for this device. + + This overrides the global keymap. The keymap becomes active when a key is pressed. + + - Example: + + ```toml + [[inputs]] + match.name = "ZSA Technology Labs Inc ErgoDox EZ" + keymap.name = "external" + ``` AccelProfile: diff --git a/wire/jay_input.txt b/wire/jay_input.txt index edf04894..3539389e 100644 --- a/wire/jay_input.txt +++ b/wire/jay_input.txt @@ -99,6 +99,16 @@ request get_device { id: u32, } +request set_device_keymap { + id: u32, + keymap: fd, + keymap_len: u32, +} + +request get_device_keymap { + id: u32, +} + # events event seat {