diff --git a/Cargo.lock b/Cargo.lock index 038845e9..cf51603e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1042,9 +1042,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "uapi" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "651f13cef1d1988a4c73d215c39fec385fc1be4c7eb6daf89822d5021f7029f8" +checksum = "3bf073840d1b16485bfe28b4e752eccee38d9ac53f152adf869708e3136561e6" dependencies = [ "cc", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index 98706ef5..763b423b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ panic = "abort" panic = "abort" [dependencies] -uapi = "0.2.12" +uapi = "0.2.13" thiserror = "1.0.56" ahash = "0.8.7" log = { version = "0.4.20", features = ["std"] } diff --git a/src/backend.rs b/src/backend.rs index 704f31b3..82a2569e 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -5,6 +5,7 @@ use { fixed::Fixed, gfx_api::GfxFramebuffer, ifs::wl_seat::wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL}, + libinput::consts::DeviceCapability, video::drm::{ConnectorType, DrmError, DrmVersion}, }, jay_config::video::GfxApi, @@ -167,6 +168,21 @@ pub enum InputDeviceCapability { Switch, } +impl InputDeviceCapability { + pub fn to_libinput(self) -> DeviceCapability { + use crate::libinput::consts::*; + match self { + InputDeviceCapability::Keyboard => LIBINPUT_DEVICE_CAP_KEYBOARD, + InputDeviceCapability::Pointer => LIBINPUT_DEVICE_CAP_POINTER, + InputDeviceCapability::Touch => LIBINPUT_DEVICE_CAP_TOUCH, + InputDeviceCapability::TabletTool => LIBINPUT_DEVICE_CAP_TABLET_TOOL, + InputDeviceCapability::TabletPad => LIBINPUT_DEVICE_CAP_TABLET_PAD, + InputDeviceCapability::Gesture => LIBINPUT_DEVICE_CAP_GESTURE, + InputDeviceCapability::Switch => LIBINPUT_DEVICE_CAP_SWITCH, + } + } +} + #[derive(Debug, Copy, Clone)] pub enum InputDeviceAccelProfile { Flat, diff --git a/src/backends/metal.rs b/src/backends/metal.rs index d4fbfd80..7130bf70 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -16,10 +16,7 @@ use { libinput::{ consts::{ AccelProfile, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, - LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, LIBINPUT_DEVICE_CAP_GESTURE, - LIBINPUT_DEVICE_CAP_KEYBOARD, LIBINPUT_DEVICE_CAP_POINTER, - LIBINPUT_DEVICE_CAP_SWITCH, LIBINPUT_DEVICE_CAP_TABLET_PAD, - LIBINPUT_DEVICE_CAP_TABLET_TOOL, LIBINPUT_DEVICE_CAP_TOUCH, + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, }, device::RegisteredDevice, LibInput, LibInputAdapter, LibInputError, @@ -454,15 +451,7 @@ impl InputDevice for MetalInputDevice { } fn has_capability(&self, cap: InputDeviceCapability) -> bool { - let li = match cap { - InputDeviceCapability::Keyboard => LIBINPUT_DEVICE_CAP_KEYBOARD, - InputDeviceCapability::Pointer => LIBINPUT_DEVICE_CAP_POINTER, - InputDeviceCapability::Touch => LIBINPUT_DEVICE_CAP_TOUCH, - InputDeviceCapability::TabletTool => LIBINPUT_DEVICE_CAP_TABLET_TOOL, - InputDeviceCapability::TabletPad => LIBINPUT_DEVICE_CAP_TABLET_PAD, - InputDeviceCapability::Gesture => LIBINPUT_DEVICE_CAP_GESTURE, - InputDeviceCapability::Switch => LIBINPUT_DEVICE_CAP_SWITCH, - }; + let li = cap.to_libinput(); match self.inputdev.get() { Some(dev) => dev.device().has_cap(li), _ => false, diff --git a/src/cli.rs b/src/cli.rs index 1191a2b7..9162103b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,5 +1,6 @@ mod generate; mod idle; +mod input; mod log; mod quit; mod randr; @@ -10,7 +11,11 @@ mod set_log_level; mod unlock; use { - crate::{cli::randr::RandrArgs, compositor::start_compositor, portal}, + crate::{ + cli::{input::InputArgs, randr::RandrArgs}, + compositor::start_compositor, + portal, + }, ::log::Level, clap::{Args, Parser, Subcommand, ValueEnum}, clap_complete::Shell, @@ -58,6 +63,8 @@ pub enum Cmd { Portal, /// Inspect/modify graphics card and connector settings. Randr(RandrArgs), + /// Inspect/modify input settings. + Input(InputArgs), #[cfg(feature = "it")] RunTests, } @@ -221,6 +228,7 @@ pub fn main() { Cmd::SeatTest(a) => seat_test::main(cli.global, a), Cmd::Portal => portal::run(cli.global), Cmd::Randr(a) => randr::main(cli.global, a), + Cmd::Input(a) => input::main(cli.global, a), #[cfg(feature = "it")] Cmd::RunTests => crate::it::run_tests(), } diff --git a/src/cli/input.rs b/src/cli/input.rs new file mode 100644 index 00000000..6b55249b --- /dev/null +++ b/src/cli/input.rs @@ -0,0 +1,732 @@ +use { + crate::{ + backend::{InputDeviceAccelProfile, InputDeviceCapability}, + cli::GlobalArgs, + clientmem::ClientMem, + libinput::consts::{ + LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, + }, + tools::tool_client::{with_tool_client, Handle, ToolClient}, + utils::{errorfmt::ErrorFmt, string_ext::StringExt}, + wire::{jay_compositor, jay_input, JayInputId}, + }, + clap::{Args, Subcommand, ValueEnum}, + isnt::std_1::vec::IsntVecExt, + std::{ + cell::RefCell, + io::{stdin, stdout, Read, Write}, + mem, + ops::DerefMut, + rc::Rc, + }, + uapi::c, +}; + +#[derive(Args, Debug)] +pub struct InputArgs { + #[clap(subcommand)] + pub command: Option, +} + +#[derive(Subcommand, Debug)] +pub enum InputCmd { + /// Show the current settings. + Show(ShowArgs), + /// Modify the settings of a seat. + Seat(SeatArgs), + /// Modify the settings of a device. + Device(DeviceArgs), +} + +impl Default for InputCmd { + fn default() -> Self { + Self::Show(Default::default()) + } +} + +#[derive(Args, Debug, Default)] +pub struct ShowArgs { + /// Print more information about devices. + #[arg(short, long)] + pub verbose: bool, +} + +#[derive(Args, Debug)] +pub struct SeatArgs { + /// The seat to modify, e.g. default. + pub seat: String, + #[clap(subcommand)] + pub command: Option, +} + +#[derive(Args, Debug)] +pub struct DeviceArgs { + /// The ID of the device to modify. + pub device: u32, + #[clap(subcommand)] + pub command: Option, +} + +#[derive(Subcommand, Debug, Clone)] +pub enum SeatCommand { + /// Show information about this seat. + Show(SeatShowArgs), + /// Set the repeat rate of the keyboard. + SetRepeatRate(SetRepeatRateArgs), + /// Set the keymap. + SetKeymap(SetKeymapArgs), + /// Retrieve the keymap. + Keymap, + /// Configure whether this seat uses the hardware cursor. + UseHardwareCursor(UseHardwareCursorArgs), + /// Set the size of the cursor. + SetCursorSize(SetCursorSizeArgs), +} + +impl Default for SeatCommand { + fn default() -> Self { + Self::Show(SeatShowArgs::default()) + } +} + +#[derive(Args, Debug, Default, Clone)] +pub struct SeatShowArgs { + /// Print more information about devices. + #[arg(short, long)] + pub verbose: bool, +} + +#[derive(Subcommand, Debug, Clone, Default)] +pub enum DeviceCommand { + /// Show information about this device. + #[default] + Show, + /// Set the acceleration profile. + SetAccelProfile(SetAccelProfileArgs), + /// Set the acceleration speed. + SetAccelSpeed(SetAccelSpeedArgs), + /// Set whether tap is enabled. + SetTapEnabled(SetTapDragEnabledArgs), + /// Set whether tap-drag is enabled. + SetTapDragEnabled(SetTapDragEnabledArgs), + /// Set whether tap-drag-lock is enabled. + SetTapDragLockEnabled(SetTapDragLockEnabledArgs), + /// Set whether the device is left-handed. + SetLeftHanded(SetLeftHandedArgs), + /// Set whether the device uses natural scrolling. + SetNaturalScrolling(SetNaturalScrollingArgs), + /// Set the pixels to scroll per scroll-wheel dedent. + SetPxPerWheelScroll(SetPxPerWheelScrollArgs), + /// Set the transformation matrix. + SetTransformMatrix(SetTransformMatrixArgs), + /// Attach the device to a seat. + Attach(AttachArgs), + /// Detach the device from its seat. + Detach, +} + +#[derive(ValueEnum, Debug, Clone)] +pub enum AccelProfile { + Flat, + Adaptive, +} + +#[derive(Args, Debug, Clone)] +pub struct SetAccelProfileArgs { + /// The profile. + pub profile: AccelProfile, +} + +#[derive(Args, Debug, Clone)] +pub struct SetAccelSpeedArgs { + /// The speed. Must be in the range \[-1, 1]. + pub speed: f64, +} + +#[derive(Args, Debug, Clone)] +pub struct SetTapEnabledArgs { + /// Whether tap is enabled. + #[arg(action = clap::ArgAction::Set)] + pub enabled: bool, +} + +#[derive(Args, Debug, Clone)] +pub struct SetTapDragEnabledArgs { + /// Whether tap-drag is enabled. + #[arg(action = clap::ArgAction::Set)] + pub enabled: bool, +} + +#[derive(Args, Debug, Clone)] +pub struct SetTapDragLockEnabledArgs { + /// Whether tap-drag-lock is enabled. + #[arg(action = clap::ArgAction::Set)] + pub enabled: bool, +} + +#[derive(Args, Debug, Clone)] +pub struct SetLeftHandedArgs { + /// Whether the device is left handed. + #[arg(action = clap::ArgAction::Set)] + pub left_handed: bool, +} + +#[derive(Args, Debug, Clone)] +pub struct SetNaturalScrollingArgs { + /// Whether natural scrolling is enabled. + #[arg(action = clap::ArgAction::Set)] + pub natural_scrolling: bool, +} + +#[derive(Args, Debug, Clone)] +pub struct SetPxPerWheelScrollArgs { + /// The number of pixels to scroll. + pub px: f64, +} + +#[derive(Args, Debug, Clone)] +pub struct SetTransformMatrixArgs { + pub m11: f64, + pub m12: f64, + pub m21: f64, + pub m22: f64, +} + +#[derive(Args, Debug, Clone)] +pub struct AttachArgs { + /// The seat to attach to. + pub seat: String, +} + +#[derive(Args, Debug, Clone)] +pub struct SetRepeatRateArgs { + /// The number of repeats per second. + pub rate: i32, + /// The delay before the first repeat in milliseconds. + pub delay: i32, +} + +#[derive(Args, Debug, Clone)] +pub struct SetCursorSizeArgs { + /// The size of the cursor. + pub size: u32, +} + +#[derive(Args, Debug, Clone)] +pub struct SetKeymapArgs { + /// The file to read the keymap from. Omit for stdin. + pub file: Option, +} + +#[derive(Args, Debug, Clone)] +pub struct UseHardwareCursorArgs { + /// Whether the seat uses the hardware cursor. + #[arg(action = clap::ArgAction::Set)] + pub enabled: bool, +} + +pub fn main(global: GlobalArgs, args: InputArgs) { + with_tool_client(global.log_level.into(), |tc| async move { + let idle = Rc::new(Input { tc: tc.clone() }); + idle.run(args).await; + }); +} + +#[derive(Clone, Debug)] +struct Seat { + pub name: String, + pub repeat_rate: i32, + pub repeat_delay: i32, + pub hardware_cursor: bool, +} + +#[derive(Clone, Debug)] +struct InputDevice { + pub id: u32, + pub name: String, + pub seat: Option, + pub syspath: Option, + pub devnode: Option, + pub capabilities: Vec, + pub accel_profile: Option, + pub accel_speed: Option, + pub tap_enabled: Option, + pub tap_drag_enabled: Option, + pub tap_drag_lock_enabled: Option, + pub left_handed: Option, + pub natural_scrolling_enabled: Option, + pub px_per_wheel_scroll: Option, + pub transform_matrix: Option<[[f64; 2]; 2]>, +} + +#[derive(Clone, Debug, Default)] +struct Data { + seats: Vec, + input_device: Vec, +} + +struct Input { + tc: Rc, +} + +impl Input { + async fn run(self: &Rc, args: InputArgs) { + let tc = &self.tc; + let comp = tc.jay_compositor().await; + let input = tc.id(); + tc.send(jay_compositor::GetInput { + self_id: comp, + id: input, + }); + match args.command.unwrap_or_default() { + InputCmd::Show(args) => self.show(input, args).await, + InputCmd::Seat(args) => self.seat(input, args).await, + InputCmd::Device(args) => self.device(input, args).await, + } + } + + fn handle_error(&self, input: JayInputId, f: F) { + jay_input::Error::handle(&self.tc, input, (), move |_, msg| { + f(msg.msg); + std::process::exit(1); + }); + } + + async fn seat(self: &Rc, input: JayInputId, args: SeatArgs) { + let tc = &self.tc; + match args.command.unwrap_or_default() { + SeatCommand::Show(a) => { + self.handle_error(input, |e| { + eprintln!("Could not retrieve seat data: {}", e); + }); + tc.send(jay_input::GetSeat { + self_id: input, + name: &args.seat, + }); + let data = self.get(input).await; + self.print_data(data, a.verbose); + } + SeatCommand::SetRepeatRate(a) => { + self.handle_error(input, |e| { + eprintln!("Could not set repeat rate: {}", e); + }); + tc.send(jay_input::SetRepeatRate { + self_id: input, + seat: &args.seat, + repeat_rate: a.rate, + repeat_delay: a.delay, + }); + } + SeatCommand::SetKeymap(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 _, + }); + } + SeatCommand::UseHardwareCursor(a) => { + self.handle_error(input, |e| { + eprintln!("Could not set hardware cursor: {}", e); + }); + tc.send(jay_input::UseHardwareCursor { + self_id: input, + seat: &args.seat, + use_hardware_cursor: a.enabled as _, + }); + } + SeatCommand::Keymap => { + 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(); + stdout().write_all(&map).unwrap(); + } + SeatCommand::SetCursorSize(a) => { + self.handle_error(input, |e| { + eprintln!("Could not set cursor size: {}", e); + }); + tc.send(jay_input::SetCursorSize { + self_id: input, + seat: &args.seat, + size: a.size, + }); + } + } + tc.round_trip().await; + } + + async fn device(self: &Rc, input: JayInputId, args: DeviceArgs) { + let tc = &self.tc; + match args.command.unwrap_or_default() { + DeviceCommand::Show => { + self.handle_error(input, |e| { + eprintln!("Could not retrieve device data: {}", e); + }); + tc.send(jay_input::GetDevice { + self_id: input, + id: args.device, + }); + let data = self.get(input).await; + for device in &data.input_device { + self.print_device("", true, device); + } + } + DeviceCommand::SetAccelProfile(a) => { + let profile = match a.profile { + AccelProfile::Flat => LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT.0, + AccelProfile::Adaptive => LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE.0, + }; + self.handle_error(input, |e| { + eprintln!("Could not set the acceleration profile: {}", e); + }); + tc.send(jay_input::SetAccelProfile { + self_id: input, + id: args.device, + profile, + }); + } + DeviceCommand::SetAccelSpeed(a) => { + self.handle_error(input, |e| { + eprintln!("Could not set the acceleration speed: {}", e); + }); + tc.send(jay_input::SetAccelSpeed { + self_id: input, + id: args.device, + speed: a.speed, + }); + } + DeviceCommand::SetTapEnabled(a) => { + self.handle_error(input, |e| { + eprintln!("Could not modify the tap-enabled setting: {}", e); + }); + tc.send(jay_input::SetTapEnabled { + self_id: input, + id: args.device, + enabled: a.enabled as _, + }); + } + DeviceCommand::SetTapDragEnabled(a) => { + self.handle_error(input, |e| { + eprintln!("Could not modify the tap-drag-enabled setting: {}", e); + }); + tc.send(jay_input::SetTapDragEnabled { + self_id: input, + id: args.device, + enabled: a.enabled as _, + }); + } + DeviceCommand::SetTapDragLockEnabled(a) => { + self.handle_error(input, |e| { + eprintln!("Could not modify the tap-drag-lock-enabled setting: {}", e); + }); + tc.send(jay_input::SetTapDragLockEnabled { + self_id: input, + id: args.device, + enabled: a.enabled as _, + }); + } + DeviceCommand::SetLeftHanded(a) => { + self.handle_error(input, |e| { + eprintln!("Could not modify the left-handed setting: {}", e); + }); + tc.send(jay_input::SetLeftHanded { + self_id: input, + id: args.device, + enabled: a.left_handed as _, + }); + } + DeviceCommand::SetNaturalScrolling(a) => { + self.handle_error(input, |e| { + eprintln!("Could not modify the natural-scrolling setting: {}", e); + }); + tc.send(jay_input::SetNaturalScrolling { + self_id: input, + id: args.device, + enabled: a.natural_scrolling as _, + }); + } + DeviceCommand::SetPxPerWheelScroll(a) => { + self.handle_error(input, |e| { + eprintln!("Could not modify the px-per-wheel-scroll setting: {}", e); + }); + tc.send(jay_input::SetPxPerWheelScroll { + self_id: input, + id: args.device, + px: a.px, + }); + } + DeviceCommand::SetTransformMatrix(a) => { + self.handle_error(input, |e| { + eprintln!("Could not modify the transform matrix: {}", e); + }); + tc.send(jay_input::SetTransformMatrix { + self_id: input, + id: args.device, + m11: a.m11, + m12: a.m12, + m21: a.m21, + m22: a.m22, + }); + } + DeviceCommand::Attach(a) => { + self.handle_error(input, |e| { + eprintln!("Could not attach the device: {}", e); + }); + tc.send(jay_input::Attach { + self_id: input, + id: args.device, + seat: &a.seat, + }); + } + DeviceCommand::Detach => { + self.handle_error(input, |e| { + eprintln!("Could not detach the device: {}", e); + }); + tc.send(jay_input::Detach { + self_id: input, + id: args.device, + }); + } + } + tc.round_trip().await; + } + + async fn show(self: &Rc, input: JayInputId, args: ShowArgs) { + self.tc.send(jay_input::GetAll { self_id: input }); + let data = self.get(input).await; + self.print_data(data, args.verbose); + } + + fn print_data(self: &Rc, mut data: Data, verbose: bool) { + data.seats.sort_by(|l, r| l.name.cmp(&r.name)); + data.input_device.sort_by_key(|l| l.id); + let mut first = true; + let print_devices = |d: &[&InputDevice]| { + for device in d { + if verbose { + self.print_device(" ", false, device); + } else { + println!(" {}: {}", device.id, device.name); + } + } + }; + for seat in &data.seats { + if !mem::take(&mut first) { + println!(); + } + self.print_seat(seat); + let input_devices: Vec<_> = data + .input_device + .iter() + .filter(|c| c.seat.as_ref() == Some(&seat.name)) + .collect(); + if input_devices.is_not_empty() { + println!(" devices:"); + } + print_devices(&input_devices); + } + { + let input_devices: Vec<_> = data + .input_device + .iter() + .filter(|c| c.seat.is_none()) + .collect(); + if input_devices.is_not_empty() { + if !mem::take(&mut first) { + println!(); + } + println!("Detached devices:"); + print_devices(&input_devices); + } + } + } + + fn print_seat(&self, seat: &Seat) { + println!("Seat {}:", seat.name); + println!(" repeat rate: {}", seat.repeat_rate); + println!(" repeat delay: {}", seat.repeat_delay); + if !seat.hardware_cursor { + println!(" hardware cursor disabled"); + } + } + + fn print_device(&self, prefix: &str, print_seat: bool, device: &InputDevice) { + println!("{prefix}{}:", device.id); + println!("{prefix} name: {}", device.name); + if print_seat { + let seat = match device.seat.as_deref() { + Some(s) => s, + _ => "", + }; + println!("{prefix} seat: {}", seat); + } + if let Some(v) = &device.syspath { + println!("{prefix} syspath: {}", v); + } + if let Some(v) = &device.devnode { + println!("{prefix} devnode: {}", v); + } + print!("{prefix} capabilities:"); + let mut first = true; + for cap in &device.capabilities { + use InputDeviceCapability::*; + print!(" "); + if !mem::take(&mut first) { + print!("| "); + } + let name = match cap { + Keyboard => "keyboard", + Pointer => "pointer", + Touch => "touch", + TabletTool => "tablet tool", + TabletPad => "tablet pad", + Gesture => "gesture", + Switch => "switch", + }; + print!("{}", name); + } + println!(); + if let Some(v) = &device.accel_profile { + let name = match v { + InputDeviceAccelProfile::Flat => "flat", + InputDeviceAccelProfile::Adaptive => "adaptive", + }; + println!("{prefix} accel profile: {}", name); + } + if let Some(v) = &device.accel_speed { + println!("{prefix} accel speed: {}", v); + } + if let Some(v) = &device.tap_enabled { + println!("{prefix} tap enabled: {}", v); + } + if let Some(v) = &device.tap_drag_enabled { + println!("{prefix} tap drag enabled: {}", v); + } + if let Some(v) = &device.tap_drag_lock_enabled { + println!("{prefix} tap drag lock enabled: {}", v); + } + if let Some(v) = &device.left_handed { + println!("{prefix} left handed: {}", v); + } + if let Some(v) = &device.natural_scrolling_enabled { + println!("{prefix} natural scrolling: {}", v); + } + if let Some(v) = &device.px_per_wheel_scroll { + println!("{prefix} px per wheel scroll: {}", v); + } + if let Some(v) = &device.transform_matrix { + println!("{prefix} transform matrix: {:?}", v); + } + } + + async fn get(self: &Rc, input: JayInputId) -> Data { + let tc = &self.tc; + let data = Rc::new(RefCell::new(Data::default())); + jay_input::Seat::handle(tc, input, data.clone(), |data, msg| { + data.borrow_mut().seats.push(Seat { + name: msg.name.to_string(), + repeat_rate: msg.repeat_rate, + repeat_delay: msg.repeat_delay, + hardware_cursor: msg.hardware_cursor != 0, + }); + }); + jay_input::InputDevice::handle(tc, input, data.clone(), |data, msg| { + use crate::{backend::InputDeviceCapability::*, libinput::consts::*}; + let mut capabilities = vec![]; + let mut is_pointer = false; + for cap in msg.capabilities { + let cap = match DeviceCapability(*cap) { + LIBINPUT_DEVICE_CAP_KEYBOARD => Keyboard, + LIBINPUT_DEVICE_CAP_POINTER => { + is_pointer = true; + Pointer + } + LIBINPUT_DEVICE_CAP_TOUCH => Touch, + LIBINPUT_DEVICE_CAP_TABLET_TOOL => TabletTool, + LIBINPUT_DEVICE_CAP_TABLET_PAD => TabletPad, + LIBINPUT_DEVICE_CAP_GESTURE => Gesture, + LIBINPUT_DEVICE_CAP_SWITCH => InputDeviceCapability::Switch, + _ => continue, + }; + capabilities.push(cap); + } + let accel_available = msg.accel_available != 0; + let tap_available = msg.tap_available != 0; + let left_handed_available = msg.left_handed_available != 0; + let natural_scrolling_available = msg.natural_scrolling_available != 0; + let mut accel_profile = match AccelProfile(msg.accel_profile) { + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT => Some(InputDeviceAccelProfile::Flat), + LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE => Some(InputDeviceAccelProfile::Adaptive), + _ => None, + }; + if !accel_available { + accel_profile = None; + } + let mut data = data.borrow_mut(); + data.input_device.push(InputDevice { + id: msg.id, + name: msg.name.to_string(), + seat: msg.seat.to_string_if_not_empty(), + syspath: msg.syspath.to_string_if_not_empty(), + devnode: msg.devnode.to_string_if_not_empty(), + capabilities, + accel_profile, + accel_speed: accel_available.then_some(msg.accel_speed), + tap_enabled: tap_available.then_some(msg.tap_enabled != 0), + tap_drag_enabled: tap_available.then_some(msg.tap_drag_enabled != 0), + tap_drag_lock_enabled: tap_available.then_some(msg.tap_drag_lock_enabled != 0), + left_handed: left_handed_available.then_some(msg.left_handed != 0), + natural_scrolling_enabled: natural_scrolling_available + .then_some(msg.natural_scrolling_enabled != 0), + px_per_wheel_scroll: is_pointer.then_some(msg.px_per_wheel_scroll), + transform_matrix: uapi::pod_read(msg.transform_matrix).ok(), + }); + }); + tc.round_trip().await; + let x = data.borrow_mut().clone(); + x + } +} diff --git a/src/clientmem.rs b/src/clientmem.rs index 685b67ae..f85dc5e7 100644 --- a/src/clientmem.rs +++ b/src/clientmem.rs @@ -1,4 +1,5 @@ use { + crate::utils::vec_ext::VecExt, std::{ cell::Cell, mem::MaybeUninit, @@ -33,7 +34,7 @@ pub struct ClientMemOffset { } impl ClientMem { - pub fn new(fd: i32, len: usize) -> Result { + pub fn new(fd: i32, len: usize, read_only: bool) -> Result { let mut sigbus_impossible = false; if let Ok(seals) = uapi::fcntl_get_seals(fd) { if seals & c::F_SEAL_SHRINK != 0 { @@ -45,15 +46,12 @@ impl ClientMem { let data = if len == 0 { &mut [][..] } else { + let prot = match read_only { + true => c::PROT_READ, + false => c::PROT_READ | c::PROT_WRITE, + }; unsafe { - let data = c::mmap64( - ptr::null_mut(), - len, - c::PROT_READ | c::PROT_WRITE, - c::MAP_SHARED, - fd, - 0, - ); + let data = c::mmap64(ptr::null_mut(), len, prot, c::MAP_SHARED, fd, 0); if data == c::MAP_FAILED { return Err(ClientMemError::MmapFailed(uapi::Errno::default().into())); } @@ -101,6 +99,17 @@ impl ClientMemOffset { } } } + + pub fn read(&self, dst: &mut Vec) -> Result<(), ClientMemError> { + self.access(|v| { + dst.reserve(v.len()); + let (_, unused) = dst.split_at_spare_mut_ext(); + unused[..v.len()].copy_from_slice(uapi::as_maybe_uninit_bytes(v)); + unsafe { + dst.set_len(dst.len() + v.len()); + } + }) + } } impl Drop for ClientMem { diff --git a/src/ifs.rs b/src/ifs.rs index e77d55c8..d1e0c6ea 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -7,6 +7,7 @@ pub mod ext_session_lock_v1; pub mod ipc; pub mod jay_compositor; pub mod jay_idle; +pub mod jay_input; pub mod jay_log_file; pub mod jay_output; pub mod jay_pointer; diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index cdad0134..2b9487be 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -4,10 +4,11 @@ use { client::{Client, ClientError}, globals::{Global, GlobalName}, ifs::{ - jay_idle::JayIdle, jay_log_file::JayLogFile, jay_output::JayOutput, - jay_pointer::JayPointer, jay_randr::JayRandr, jay_render_ctx::JayRenderCtx, - jay_screencast::JayScreencast, jay_screenshot::JayScreenshot, - jay_seat_events::JaySeatEvents, jay_workspace_watcher::JayWorkspaceWatcher, + jay_idle::JayIdle, jay_input::JayInput, jay_log_file::JayLogFile, + jay_output::JayOutput, jay_pointer::JayPointer, jay_randr::JayRandr, + jay_render_ctx::JayRenderCtx, jay_screencast::JayScreencast, + jay_screenshot::JayScreenshot, jay_seat_events::JaySeatEvents, + jay_workspace_watcher::JayWorkspaceWatcher, }, leaks::Tracker, object::Object, @@ -316,6 +317,14 @@ impl JayCompositor { self.client.add_client_obj(&sc)?; Ok(()) } + + fn get_input(&self, parser: MsgParser<'_, '_>) -> Result<(), JayCompositorError> { + let req: GetInput = self.client.parse(self, parser)?; + let sc = Rc::new(JayInput::new(req.id, &self.client)); + track!(self.client, sc); + self.client.add_client_obj(&sc)?; + Ok(()) + } } object_base! { @@ -338,6 +347,7 @@ object_base! { WATCH_WORKSPACES => watch_workspaces, CREATE_SCREENCAST => create_screencast, GET_RANDR => get_randr, + GET_INPUT => get_input, } impl Object for JayCompositor {} diff --git a/src/ifs/jay_input.rs b/src/ifs/jay_input.rs new file mode 100644 index 00000000..560b301a --- /dev/null +++ b/src/ifs/jay_input.rs @@ -0,0 +1,411 @@ +use { + crate::{ + backend::{self, InputDeviceAccelProfile, InputDeviceId}, + client::{Client, ClientError}, + clientmem::{ClientMem, ClientMemError}, + ifs::wl_seat::WlSeatGlobal, + leaks::Tracker, + libinput::consts::{ + AccelProfile, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, + }, + object::Object, + state::{DeviceHandlerData, InputDeviceData}, + utils::{ + buffd::{MsgParser, MsgParserError}, + errorfmt::ErrorFmt, + }, + wire::{jay_input::*, JayInputId}, + xkbcommon::XkbCommonError, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct JayInput { + pub id: JayInputId, + pub client: Rc, + pub tracker: Tracker, +} + +impl JayInput { + pub fn new(id: JayInputId, client: &Rc) -> Self { + Self { + id, + client: client.clone(), + tracker: Default::default(), + } + } + + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let _req: Destroy = self.client.parse(self, parser)?; + self.client.remove_obj(self)?; + Ok(()) + } + + fn get_all(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let _req: GetAll = self.client.parse(self, parser)?; + let state = &self.client.state; + for seat in state.globals.seats.lock().values() { + self.send_seat(seat); + } + for dev in state.input_device_handlers.borrow().values() { + self.send_input_device(dev); + } + Ok(()) + } + + fn get_seat(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: GetSeat = self.client.parse(self, parser)?; + self.or_error(|| { + let seat = self.seat(req.name)?; + self.send_seat(&seat); + for dev in self.client.state.input_device_handlers.borrow().values() { + if let Some(attached) = dev.data.seat.get() { + if attached.id() == seat.id() { + self.send_input_device(dev); + } + } + } + Ok(()) + }) + } + + fn get_device(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: GetDevice = self.client.parse(self, parser)?; + self.or_error(|| { + match self + .client + .state + .input_device_handlers + .borrow() + .get(&InputDeviceId::from_raw(req.id)) + { + None => Err(JayInputError::DeviceDoesNotExist(req.id)), + Some(d) => { + self.send_input_device(d); + Ok(()) + } + } + }) + } + + fn seat(&self, name: &str) -> Result, JayInputError> { + for seat in self.client.state.globals.seats.lock().values() { + if seat.seat_name() == name { + return Ok(seat.clone()); + } + } + Err(JayInputError::SeatDoesNotExist(name.to_string())) + } + + fn or_error(&self, f: impl FnOnce() -> Result<(), JayInputError>) -> Result<(), JayInputError> { + if let Err(e) = f() { + self.send_error(&ErrorFmt(e).to_string()); + } + Ok(()) + } + + fn set_repeat_rate(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: SetRepeatRate = self.client.parse(self, parser)?; + self.or_error(|| { + if req.repeat_rate < 0 { + return Err(JayInputError::NegativeRepeatRate); + } + if req.repeat_delay < 0 { + return Err(JayInputError::NegativeRepeatDelay); + } + let seat = self.seat(req.seat)?; + seat.set_rate(req.repeat_rate, req.repeat_delay); + Ok(()) + }) + } + + fn set_keymap(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: SetKeymap = self.client.parse(self, parser)?; + 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)?; + let seat = self.seat(req.seat)?; + seat.set_keymap(&map); + Ok(()) + }) + } + + fn get_keymap(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: GetKeymap = self.client.parse(self, parser)?; + self.or_error(|| { + let seat = self.seat(req.seat)?; + self.send_keymap(&seat); + Ok(()) + }) + } + + fn use_hardware_cursor(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: UseHardwareCursor = self.client.parse(self, parser)?; + self.or_error(|| { + let seat = self.seat(req.seat)?; + seat.set_hardware_cursor(req.use_hardware_cursor != 0); + Ok(()) + }) + } + + fn send_seat(&self, data: &WlSeatGlobal) { + self.client.event(Seat { + self_id: self.id, + name: data.seat_name(), + repeat_rate: data.get_rate().0, + repeat_delay: data.get_rate().1, + hardware_cursor: data.hardware_cursor() as _, + }); + } + + fn send_error(&self, error: &str) { + self.client.event(Error { + self_id: self.id, + msg: error, + }); + } + + fn send_keymap(&self, data: &WlSeatGlobal) { + let map = data.keymap(); + self.client.event(Keymap { + self_id: self.id, + keymap: map.map.clone(), + keymap_len: (map.map_len - 1) as _, + }); + } + + fn send_input_device(&self, data: &InputDeviceData) { + use backend::InputDeviceCapability::*; + let mut caps = vec![]; + for cap in [ + Keyboard, Pointer, Touch, TabletTool, TabletPad, Gesture, Switch, + ] { + if data.data.device.has_capability(cap) { + caps.push(cap.to_libinput().raw()); + } + } + let dev = &data.data.device; + let accel_profile = dev.accel_profile(); + let left_handed = dev.left_handed(); + let natural_scrolling = dev.natural_scrolling_enabled(); + let tap_enabled = dev.tap_enabled(); + let transform_matrix = dev.transform_matrix(); + self.client.event(InputDevice { + self_id: self.id, + seat: data + .data + .seat + .get() + .as_deref() + .map(|s| s.seat_name()) + .unwrap_or_default(), + id: data.id.raw(), + syspath: data.syspath.as_deref().unwrap_or_default(), + devnode: data.devnode.as_deref().unwrap_or_default(), + name: dev.name().as_str(), + capabilities: &caps, + accel_available: accel_profile.is_some() as _, + accel_profile: match accel_profile { + None => 0, + Some(p) => match p { + InputDeviceAccelProfile::Flat => LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT.0, + InputDeviceAccelProfile::Adaptive => LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE.0, + }, + }, + accel_speed: dev.accel_speed().unwrap_or_default(), + left_handed_available: left_handed.is_some() as _, + left_handed: left_handed.unwrap_or_default() as _, + natural_scrolling_available: natural_scrolling.is_some() as _, + natural_scrolling_enabled: natural_scrolling.unwrap_or_default() as _, + px_per_wheel_scroll: data.data.px_per_scroll_wheel.get(), + tap_available: tap_enabled.is_some() as _, + tap_enabled: tap_enabled.unwrap_or_default() as _, + tap_drag_enabled: dev.drag_enabled().unwrap_or_default() as _, + tap_drag_lock_enabled: dev.drag_lock_enabled().unwrap_or_default() as _, + transform_matrix: transform_matrix + .as_ref() + .map(uapi::as_bytes) + .unwrap_or_default(), + }); + } + + fn device(&self, id: u32) -> Result, JayInputError> { + let idh = self.client.state.input_device_handlers.borrow_mut(); + match idh.get(&InputDeviceId::from_raw(id)) { + None => Err(JayInputError::DeviceDoesNotExist(id)), + Some(d) => Ok(d.data.clone()), + } + } + + fn set_accel_profile(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: SetAccelProfile = self.client.parse(self, parser)?; + self.or_error(|| { + let dev = self.device(req.id)?; + let profile = match AccelProfile(req.profile) { + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT => InputDeviceAccelProfile::Flat, + LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE => InputDeviceAccelProfile::Adaptive, + _ => return Err(JayInputError::UnknownAccelerationProfile(req.profile)), + }; + dev.device.set_accel_profile(profile); + Ok(()) + }) + } + + fn set_accel_speed(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: SetAccelSpeed = self.client.parse(self, parser)?; + self.or_error(|| { + let dev = self.device(req.id)?; + dev.device.set_accel_speed(req.speed); + Ok(()) + }) + } + + fn set_tap_enabled(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: SetTapEnabled = self.client.parse(self, parser)?; + self.or_error(|| { + let dev = self.device(req.id)?; + dev.device.set_tap_enabled(req.enabled != 0); + Ok(()) + }) + } + + fn set_tap_drag_enabled(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: SetTapDragEnabled = self.client.parse(self, parser)?; + self.or_error(|| { + let dev = self.device(req.id)?; + dev.device.set_drag_enabled(req.enabled != 0); + Ok(()) + }) + } + + fn set_tap_drag_lock_enabled(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: SetTapDragEnabled = self.client.parse(self, parser)?; + self.or_error(|| { + let dev = self.device(req.id)?; + dev.device.set_drag_lock_enabled(req.enabled != 0); + Ok(()) + }) + } + + fn set_left_handed(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: SetLeftHanded = self.client.parse(self, parser)?; + self.or_error(|| { + let dev = self.device(req.id)?; + dev.device.set_left_handed(req.enabled != 0); + Ok(()) + }) + } + + fn set_natural_scrolling(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: SetNaturalScrolling = self.client.parse(self, parser)?; + self.or_error(|| { + let dev = self.device(req.id)?; + dev.device.set_natural_scrolling_enabled(req.enabled != 0); + Ok(()) + }) + } + + fn set_px_per_wheel_scroll(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: SetPxPerWheelScroll = self.client.parse(self, parser)?; + self.or_error(|| { + let dev = self.device(req.id)?; + dev.px_per_scroll_wheel.set(req.px); + Ok(()) + }) + } + + fn set_transform_matrix(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: SetTransformMatrix = self.client.parse(self, parser)?; + self.or_error(|| { + let dev = self.device(req.id)?; + dev.device + .set_transform_matrix([[req.m11, req.m12], [req.m21, req.m22]]); + Ok(()) + }) + } + + fn set_cursor_size(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: SetCursorSize = self.client.parse(self, parser)?; + self.or_error(|| { + let seat = self.seat(req.seat)?; + seat.set_cursor_size(req.size); + Ok(()) + }) + } + + fn attach(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: Attach = self.client.parse(self, parser)?; + self.or_error(|| { + let seat = self.seat(req.seat)?; + let dev = self.device(req.id)?; + dev.seat.set(Some(seat)); + Ok(()) + }) + } + + fn detach(&self, parser: MsgParser<'_, '_>) -> Result<(), JayInputError> { + let req: Detach = self.client.parse(self, parser)?; + self.or_error(|| { + let dev = self.device(req.id)?; + dev.seat.set(None); + Ok(()) + }) + } +} + +object_base! { + self = JayInput; + + DESTROY => destroy, + GET_ALL => get_all, + SET_REPEAT_RATE => set_repeat_rate, + SET_KEYMAP => set_keymap, + USE_HARDWARE_CURSOR => use_hardware_cursor, + GET_KEYMAP => get_keymap, + SET_ACCEL_PROFILE => set_accel_profile, + SET_ACCEL_SPEED => set_accel_speed, + SET_TAP_ENABLED => set_tap_enabled, + SET_TAP_DRAG_ENABLED => set_tap_drag_enabled, + SET_TAP_DRAG_LOCK_ENABLED => set_tap_drag_lock_enabled, + SET_LEFT_HANDED => set_left_handed, + SET_NATURAL_SCROLLING => set_natural_scrolling, + SET_PX_PER_WHEEL_SCROLL => set_px_per_wheel_scroll, + SET_TRANSFORM_MATRIX => set_transform_matrix, + SET_CURSOR_SIZE => set_cursor_size, + ATTACH => attach, + DETACH => detach, + GET_SEAT => get_seat, + GET_DEVICE => get_device, +} + +impl Object for JayInput {} + +simple_add_obj!(JayInput); + +#[derive(Debug, Error)] +pub enum JayInputError { + #[error("Parsing failed")] + MsgParserError(Box), + #[error(transparent)] + ClientError(Box), + #[error("There is no seat called {0}")] + SeatDoesNotExist(String), + #[error("There is no device with id {0}")] + DeviceDoesNotExist(u32), + #[error("There is no acceleration profile with id {0}")] + UnknownAccelerationProfile(i32), + #[error("Repeat rate must not be negative")] + NegativeRepeatRate, + #[error("Repeat delay must not be negative")] + NegativeRepeatDelay, + #[error("Could not access client memory")] + ClientMemError(#[from] ClientMemError), + #[error("Could not parse keymap")] + XkbCommonError(#[from] XkbCommonError), +} +efrom!(JayInputError, MsgParserError); +efrom!(JayInputError, ClientError); diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 5c036ed9..d027602e 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -231,6 +231,10 @@ impl WlSeatGlobal { slf } + pub fn keymap(&self) -> Rc { + self.kb_map.get() + } + pub fn toplevel_drag(&self) -> Option> { self.pointer_owner.toplevel_drag() } diff --git a/src/ifs/wl_shm_pool.rs b/src/ifs/wl_shm_pool.rs index 8d512774..e4124c4d 100644 --- a/src/ifs/wl_shm_pool.rs +++ b/src/ifs/wl_shm_pool.rs @@ -35,7 +35,7 @@ impl WlShmPool { Ok(Self { id, client: client.clone(), - mem: CloneCell::new(Rc::new(ClientMem::new(fd.raw(), len)?)), + mem: CloneCell::new(Rc::new(ClientMem::new(fd.raw(), len, false)?)), fd, tracker: Default::default(), }) @@ -80,8 +80,11 @@ impl WlShmPool { if (req.size as usize) < self.mem.get().len() { return Err(WlShmPoolError::CannotShrink); } - self.mem - .set(Rc::new(ClientMem::new(self.fd.raw(), req.size as usize)?)); + self.mem.set(Rc::new(ClientMem::new( + self.fd.raw(), + req.size as usize, + false, + )?)); Ok(()) } } diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index b60d380c..00a2d219 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -492,6 +492,10 @@ impl InputDevice for T { self.common().name.clone() } + fn dev_t(&self) -> Option { + None + } + fn set_tap_enabled(&self, enabled: bool) { ::set_tap_enabled(self, enabled) } diff --git a/src/macros.rs b/src/macros.rs index d1cdfe42..f570d831 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -257,7 +257,7 @@ macro_rules! linear_ids { macro_rules! cenum { ($name:ident, $uc:ident; $($name2:ident = $val:expr,)*) => { #[derive(Copy, Clone, Debug, Eq, PartialEq)] - pub struct $name(pub(super) i32); + pub struct $name(pub i32); impl $name { pub fn raw(self) -> i32 { diff --git a/src/utils.rs b/src/utils.rs index b6ac70e6..db7b8e05 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -35,6 +35,7 @@ pub mod run_toplevel; pub mod scroller; pub mod smallmap; pub mod stack; +pub mod string_ext; pub mod syncqueue; pub mod threshold_counter; pub mod timer; diff --git a/src/utils/string_ext.rs b/src/utils/string_ext.rs new file mode 100644 index 00000000..3dc19eb5 --- /dev/null +++ b/src/utils/string_ext.rs @@ -0,0 +1,11 @@ +use isnt::std_1::primitive::IsntStrExt; + +pub trait StringExt { + fn to_string_if_not_empty(&self) -> Option; +} + +impl StringExt for str { + fn to_string_if_not_empty(&self) -> Option { + self.is_not_empty().then(|| self.to_string()) + } +} diff --git a/src/xkbcommon.rs b/src/xkbcommon.rs index 0fba7212..bbedd6ae 100644 --- a/src/xkbcommon.rs +++ b/src/xkbcommon.rs @@ -148,11 +148,15 @@ impl XkbContext { })) } - pub fn keymap_from_str(&self, s: &str) -> Result, XkbCommonError> { + pub fn keymap_from_str(&self, s: &S) -> Result, XkbCommonError> + where + S: AsRef<[u8]> + ?Sized, + { + let s = s.as_ref(); unsafe { let keymap = xkb_keymap_new_from_buffer( self.context, - s.as_bytes().as_ptr(), + s.as_ptr(), s.len(), XKB_KEYMAP_FORMAT_TEXT_V1.raw(), 0, diff --git a/wire/jay_compositor.txt b/wire/jay_compositor.txt index ac69231f..10fb4d60 100644 --- a/wire/jay_compositor.txt +++ b/wire/jay_compositor.txt @@ -69,6 +69,10 @@ msg get_randr = 16 { id: id(jay_randr), } +msg get_input = 17 { + id: id(jay_input), +} + # events msg client_id = 0 { diff --git a/wire/jay_input.txt b/wire/jay_input.txt new file mode 100644 index 00000000..f083c743 --- /dev/null +++ b/wire/jay_input.txt @@ -0,0 +1,140 @@ +# requests + +msg destroy = 0 { + +} + +msg get_all = 1 { + +} + +msg set_repeat_rate = 2 { + seat: str, + repeat_rate: i32, + repeat_delay: i32, +} + +msg set_keymap = 3 { + seat: str, + keymap: fd, + keymap_len: u32, +} + +msg use_hardware_cursor = 4 { + seat: str, + use_hardware_cursor: u32, +} + +msg get_keymap = 5 { + seat: str, +} + +msg set_accel_profile = 6 { + id: u32, + profile: i32, +} + +msg set_accel_speed = 7 { + id: u32, + speed: pod(f64), +} + +msg set_tap_enabled = 8 { + id: u32, + enabled: u32, +} + +msg set_tap_drag_enabled = 9 { + id: u32, + enabled: u32, +} + +msg set_tap_drag_lock_enabled = 10 { + id: u32, + enabled: u32, +} + +msg set_left_handed = 11 { + id: u32, + enabled: u32, +} + +msg set_natural_scrolling = 12 { + id: u32, + enabled: u32, +} + +msg set_px_per_wheel_scroll = 13 { + id: u32, + px: pod(f64), +} + +msg set_transform_matrix = 14 { + id: u32, + m11: pod(f64), + m12: pod(f64), + m21: pod(f64), + m22: pod(f64), +} + +msg set_cursor_size = 15 { + seat: str, + size: u32, +} + +msg attach = 16 { + id: u32, + seat: str, +} + +msg detach = 17 { + id: u32, +} + +msg get_seat = 18 { + name: str, +} + +msg get_device = 19 { + id: u32, +} + +# events + +msg seat = 0 { + name: str, + repeat_rate: i32, + repeat_delay: i32, + hardware_cursor: u32, +} + +msg input_device = 1 { + id: u32, + name: str, + seat: str, + syspath: str, + devnode: str, + capabilities: array(pod(i32)), + accel_available: u32, + accel_profile: i32, + accel_speed: pod(f64), + tap_available: u32, + tap_enabled: u32, + tap_drag_enabled: u32, + tap_drag_lock_enabled: u32, + left_handed_available: u32, + left_handed: u32, + natural_scrolling_available: u32, + natural_scrolling_enabled: u32, + px_per_wheel_scroll: pod(f64), + transform_matrix: array(pod(u8)), +} + +msg error = 2 { + msg: str, +} + +msg keymap = 3 { + keymap: fd, + keymap_len: u32, +}