Merge pull request #96 from pinnacle-comp/input

Add input options to API
This commit is contained in:
Ottatop 2023-09-28 19:15:02 -05:00 committed by GitHub
commit a109c704ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 500 additions and 70 deletions

View file

@ -35,7 +35,6 @@ xdg = "2.5.2"
lazy_static = "1.4.0"
sysinfo = "0.29.10"
[features]
default = ["egl", "winit", "udev", "xwayland"]
egl = ["smithay/use_system_lib", "smithay/backend_egl"]

View file

@ -31,14 +31,19 @@ require("pinnacle").setup(function(pinnacle)
process.set_env("MOZ_ENABLE_WAYLAND", "1")
-- Outputs -----------------------------------------------------------------------
-- You can set your own monitor layout as I have done below for my monitors.
--
-- local lg = output.get_by_name("DP-2") --[[@as Output]]
-- local dell = output.get_by_name("DP-3") --[[@as Output]]
--
-- dell:set_loc_left_of(lg, "bottom")
-- Libinput settings -------------------------------------------------------------
-- If you want to change settings like pointer acceleration,
-- you can do them in `input.libinput`.
--
-- input.libinput.set_accel_profile("Flat")
-- Mousebinds --------------------------------------------------------------------
input.mousebind({ "Ctrl" }, buttons.left, "Press", function()

View file

@ -26,6 +26,13 @@ local buttons = {
back = 0x116,
}
---@class XkbConfig
---@field rules string?
---@field model string?
---@field layout string?
---@field variant string?
---@field options string?
---Input management.
---
---This module provides utilities to set keybinds.
@ -36,6 +43,8 @@ local input_module = {
--- A table with mouse button codes. You can use indexes (1, 2, and 3 are left, right, and middle)
--- or keyed values (buttons.left, buttons.right, etc.).
buttons = buttons,
--- Libinput settings.
libinput = require("input.libinput"),
}
---Set a keybind. If called with an already existing keybind, it gets replaced.
@ -114,4 +123,25 @@ function input_module.mousebind(modifiers, button, edge, action)
})
end
---Set the xkbconfig for your keyboard.
---
---Fields not present will be set to their default values.
---
---Read `xkeyboard-config(7)` for more information.
---
---### Example
---```lua
---input.set_xkb_config({
--- layout = "us,fr,ge",
--- options = "ctrl:swapcaps,caps:shift"
---})
---```
---
---@param xkb_config XkbConfig
function input_module.set_xkb_config(xkb_config)
SendMsg({
SetXkbConfig = xkb_config,
})
end
return input_module

211
api/lua/input/libinput.lua Normal file
View file

@ -0,0 +1,211 @@
-- SPDX-License-Identifier: GPL-3.0-or-later
---@class LibinputSetting
---@field AccelProfile (AccelProfile)?
---@field AccelSpeed float?
---@field CalibrationMatrix float[]?
---@field ClickMethod (ClickMethod)?
---@field DisableWhileTypingEnabled boolean?
---@field LeftHanded boolean?
---@field MiddleEmulationEnabled boolean?
---@field RotationAngle integer? A u32
---@field ScrollMethod (ScrollMethod)?
---@field NaturalScrollEnabled boolean?
---@field ScrollButton integer? A u32
---@field TapButtonMap TapButtonMap?
---@field TapDragEnabled boolean?
---@field TapDragLockEnabled boolean?
---@field TapEnabled boolean?
---@alias AccelProfile
---| "Flat" # Flat pointer acceleration.
---| "Adaptive" Adaptive pointer acceleration. This is the default for most devices.
---@alias ClickMethod
---| "ButtonAreas" # Use software-button areas to generate button events.
---| "Clickfinger" # The number of fingers decides which button press to generate.
---@alias ScrollMethod
---| "NoScroll" # Never send scroll events.
---| "TwoFinger" # Send scroll events when two fingers are logically down on the device.
---| "Edge" # Send scroll events when a finger moves along the bottom or right edge of a device.
---| "OnButtonDown" # Send scroll events when a button is down and the device moves along a scroll-capable axis.
---@alias TapButtonMap
---| "LeftRightMiddle" # 1/2/3 finger tap is mapped to left/right/middle click.
---| "LeftMiddleRight" # 1/2/3 finger tap is mapped to left/middle/right click.
---Configuration options for libinput.
---
---Here, you can configure how input devices like your mouse and touchpad function.
---@class Libinput
local libinput = {}
---Set the acceleration profile.
---@param profile AccelProfile
function libinput.set_accel_profile(profile)
SendMsg({
SetLibinputSetting = {
AccelProfile = profile,
},
})
end
---Set the acceleration speed.
---@param speed float The speed from -1 to 1.
function libinput.set_accel_speed(speed)
SendMsg({
SetLibinputSetting = {
AccelSpeed = speed,
},
})
end
---Set the calibration matrix.
---@param matrix float[] A 6-element float array.
function libinput.set_calibration_matrix(matrix)
if #matrix ~= 6 then
return
end
SendMsg({
SetLibinputSetting = {
CalibrationMatrix = matrix,
},
})
end
---Set the click method.
---
---The click method defines when to generate software-emulated buttons, usually on a device
---that does not have a specific physical button available.
---@param method ClickMethod
function libinput.set_click_method(method)
SendMsg({
SetLibinputSetting = {
ClickMethod = method,
},
})
end
---Set whether or not the device will be disabled while typing.
---@param enabled boolean
function libinput.set_disable_while_typing_enabled(enabled)
SendMsg({
SetLibinputSetting = {
DisableWhileTypingEnabled = enabled,
},
})
end
---Set device left-handedness.
---@param enabled boolean
function libinput.set_left_handed(enabled)
SendMsg({
SetLibinputSetting = {
LeftHanded = enabled,
},
})
end
---Set whether or not the middle click can be emulated.
---@param enabled boolean
function libinput.set_middle_emulation_enabled(enabled)
SendMsg({
SetLibinputSetting = {
MiddleEmulationEnabled = enabled,
},
})
end
---Set the rotation angle of a device.
---@param angle integer An integer in the range [0, 360].
function libinput.set_rotation_angle(angle)
SendMsg({
SetLibinputSetting = {
RotationAngle = angle,
},
})
end
---Set the scroll method.
---@param method ScrollMethod
function libinput.set_scroll_method(method)
SendMsg({
SetLibinputSetting = {
ScrollMethod = method,
},
})
end
---Set whether or not natural scroll is enabled.
---
---This reverses the direction of scrolling and is mainly used with touchpads.
---@param enabled boolean
function libinput.set_natural_scroll_enabled(enabled)
SendMsg({
SetLibinputSetting = {
NaturalScrollEnabled = enabled,
},
})
end
---Set the scroll button.
---@param button integer
function libinput.set_scroll_button(button)
SendMsg({
SetLibinputSetting = {
ScrollButton = button,
},
})
end
---Set the tap button map.
---
---This determines whether taps with 2 and 3 fingers register as right and middle clicks or the reverse.
---@param map TapButtonMap
function libinput.set_tap_button_map(map)
SendMsg({
SetLibinputSetting = {
TapButtonMap = map,
},
})
end
---Set whether or not tap-to-click is enabled.
---@param enabled boolean
function libinput.set_tap_enabled(enabled)
SendMsg({
SetLibinputSetting = {
TapEnabled = enabled,
},
})
end
---Set whether or not tap-and-drag is enabled.
---
---When enabled, a single-finger tap immediately followed by a finger down results in
---a button down event, and subsequent finger motion thus triggers a drag. The button is released on finger up.
---@param enabled boolean
function libinput.set_tap_drag_enabled(enabled)
SendMsg({
SetLibinputSetting = {
TapDragEnabled = enabled,
},
})
end
---Set whether or not tap drag lock is enabled.
---
---When enabled, a finger may be lifted and put back on the touchpad within a timeout and the drag process
---continues. When disabled, lifting the finger during a tap-and-drag will immediately stop the drag.
---@param enabled boolean
function libinput.set_tap_drag_lock_enabled(enabled)
SendMsg({
SetLibinputSetting = {
TapDragLockEnabled = enabled,
},
})
end
return libinput

View file

@ -19,7 +19,6 @@
--
---@field Spawn { command: string[], callback_id: integer? }?
---@field SetEnv { key: string, value: string }?
---@field Request Request?
--Tags
---@field ToggleTag { tag_id: TagId }?
---@field SwitchToTag { tag_id: TagId }?
@ -29,6 +28,10 @@
--Outputs
---@field ConnectForAllOutputs { callback_id: integer }?
---@field SetOutputLocation { output_name: OutputName, x: integer?, y: integer? }?
--Input
---@field SetXkbConfig XkbConfig?
---@field SetLibinputSetting LibinputSetting?
---@field Request Request?
---@alias Msg _Msg | "Quit"

View file

@ -259,6 +259,7 @@ pub fn run_udev() -> anyhow::Result<()> {
.handle()
.insert_source(libinput_backend, move |event, _, data| {
// println!("event: {:?}", event);
data.state.apply_libinput_settings(&event);
data.state.process_input_event(event);
});

View file

@ -150,6 +150,7 @@ impl State {
tracing::debug!("Clearing mouse and keybinds");
self.input_state.keybinds.clear();
self.input_state.mousebinds.clear();
self.input_state.libinput_settings.clear();
self.config.window_rules.clear();
tracing::debug!("Killing old config");

View file

@ -8,6 +8,7 @@ pub mod window_rules;
use smithay::input::keyboard::ModifiersState;
use crate::{
input::LibinputSetting,
layout::Layout,
output::OutputName,
tag::TagId,
@ -31,7 +32,7 @@ pub enum MouseEdge {
Release,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Deserialize)]
pub enum Msg {
// Input
SetKeybind {
@ -134,6 +135,22 @@ pub enum Msg {
/// Quit the compositor.
Quit,
// Input management
SetXkbConfig {
#[serde(default)]
rules: Option<String>,
#[serde(default)]
variant: Option<String>,
#[serde(default)]
layout: Option<String>,
#[serde(default)]
model: Option<String>,
#[serde(default)]
options: Option<String>,
},
SetLibinputSetting(LibinputSetting),
Request {
request_id: RequestId,
request: Request,

View file

@ -14,6 +14,7 @@ use smithay::{
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent,
KeyState, KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, PointerMotionEvent,
},
libinput::LibinputInputBackend,
session::Session,
},
desktop::{layer_map_for_output, space::SpaceElement},
@ -21,6 +22,7 @@ use smithay::{
keyboard::{keysyms, FilterResult},
pointer::{AxisFrame, ButtonEvent, MotionEvent, RelativeMotionEvent},
},
reexports::input::{self, AccelProfile, ClickMethod, Led, ScrollMethod, TapButtonMap},
utils::{Logical, Point, SERIAL_COUNTER},
wayland::{seat::WaylandFocus, shell::wlr_layer},
};
@ -35,6 +37,8 @@ pub struct InputState {
pub mousebinds: HashMap<(ModifierMask, u32, MouseEdge), CallbackId>,
pub reload_keybind: Option<(ModifierMask, u32)>,
pub kill_keybind: Option<(ModifierMask, u32)>,
pub libinput_settings: Vec<LibinputSetting>,
pub libinput_devices: Vec<input::Device>,
}
impl InputState {
@ -51,7 +55,125 @@ enum KeyAction {
ReloadConfig,
}
#[derive(Debug, serde::Deserialize)]
#[serde(remote = "AccelProfile")]
enum AccelProfileDef {
Flat,
Adaptive,
}
#[derive(Debug, serde::Deserialize)]
#[serde(remote = "ClickMethod")]
enum ClickMethodDef {
ButtonAreas,
Clickfinger,
}
#[derive(Debug, serde::Deserialize)]
#[serde(remote = "ScrollMethod")]
enum ScrollMethodDef {
NoScroll,
TwoFinger,
Edge,
OnButtonDown,
}
#[derive(Debug, serde::Deserialize)]
#[serde(remote = "TapButtonMap")]
enum TapButtonMapDef {
LeftRightMiddle,
LeftMiddleRight,
}
#[derive(Debug, PartialEq, Copy, Clone, serde::Deserialize)]
pub enum LibinputSetting {
#[serde(with = "AccelProfileDef")]
AccelProfile(AccelProfile),
AccelSpeed(f64),
CalibrationMatrix([f32; 6]),
#[serde(with = "ClickMethodDef")]
ClickMethod(ClickMethod),
DisableWhileTypingEnabled(bool),
LeftHanded(bool),
MiddleEmulationEnabled(bool),
RotationAngle(u32),
#[serde(with = "ScrollMethodDef")]
ScrollMethod(ScrollMethod),
NaturalScrollEnabled(bool),
ScrollButton(u32),
#[serde(with = "TapButtonMapDef")]
TapButtonMap(TapButtonMap),
TapDragEnabled(bool),
TapDragLockEnabled(bool),
TapEnabled(bool),
}
impl LibinputSetting {
pub fn apply_to_device(&self, device: &mut input::Device) {
let _ = match self {
LibinputSetting::AccelProfile(profile) => device.config_accel_set_profile(*profile),
LibinputSetting::AccelSpeed(speed) => device.config_accel_set_speed(*speed),
LibinputSetting::CalibrationMatrix(matrix) => {
device.config_calibration_set_matrix(*matrix)
}
LibinputSetting::ClickMethod(method) => device.config_click_set_method(*method),
LibinputSetting::DisableWhileTypingEnabled(enabled) => {
device.config_dwt_set_enabled(*enabled)
}
LibinputSetting::LeftHanded(enabled) => device.config_left_handed_set(*enabled),
LibinputSetting::MiddleEmulationEnabled(enabled) => {
device.config_middle_emulation_set_enabled(*enabled)
}
LibinputSetting::RotationAngle(angle) => device.config_rotation_set_angle(*angle),
LibinputSetting::ScrollMethod(method) => device.config_scroll_set_method(*method),
LibinputSetting::NaturalScrollEnabled(enabled) => {
device.config_scroll_set_natural_scroll_enabled(*enabled)
}
LibinputSetting::ScrollButton(button) => device.config_scroll_set_button(*button),
LibinputSetting::TapButtonMap(map) => device.config_tap_set_button_map(*map),
LibinputSetting::TapDragEnabled(enabled) => {
device.config_tap_set_drag_enabled(*enabled)
}
LibinputSetting::TapDragLockEnabled(enabled) => {
device.config_tap_set_drag_lock_enabled(*enabled)
}
LibinputSetting::TapEnabled(enabled) => device.config_tap_set_enabled(*enabled),
};
}
}
// We want to completely replace old settings, so we hash only the discriminant.
impl std::hash::Hash for LibinputSetting {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
}
}
impl State {
/// Apply current libinput settings to new devices.
pub fn apply_libinput_settings(&mut self, event: &InputEvent<LibinputInputBackend>) {
let mut device = match event {
InputEvent::DeviceAdded { device } => device.clone(),
InputEvent::DeviceRemoved { device } => {
self.input_state
.libinput_devices
.retain(|dev| dev != device);
return;
}
_ => return,
};
if self.input_state.libinput_devices.contains(&device) {
return;
}
for setting in self.input_state.libinput_settings.iter() {
setting.apply_to_device(&mut device);
}
self.input_state.libinput_devices.push(device);
}
pub fn process_input_event<B: InputBackend>(&mut self, event: InputEvent<B>) {
match event {
// TODO: rest of input events
@ -152,79 +274,89 @@ impl State {
let reload_keybind = self.input_state.reload_keybind;
let kill_keybind = self.input_state.kill_keybind;
let action = self
.seat
.get_keyboard()
.expect("Seat has no keyboard") // FIXME: handle err
.input(
self,
event.key_code(),
press_state,
serial,
time,
|state, modifiers, keysym| {
if press_state == KeyState::Pressed {
let mut modifier_mask = Vec::<Modifier>::new();
if modifiers.alt {
modifier_mask.push(Modifier::Alt);
}
if modifiers.shift {
modifier_mask.push(Modifier::Shift);
}
if modifiers.ctrl {
modifier_mask.push(Modifier::Ctrl);
}
if modifiers.logo {
modifier_mask.push(Modifier::Super);
}
let modifier_mask = ModifierMask::from(modifier_mask);
let raw_sym = keysym.raw_syms().iter().next();
let mod_sym = keysym.modified_sym();
let keyboard = self.seat.get_keyboard().expect("Seat has no keyboard");
let cb_id_mod = state
.input_state
.keybinds
.get(&(modifier_mask, mod_sym));
let modifiers = keyboard.modifier_state();
let cb_id_raw = if let Some(raw_sym) = raw_sym {
state.input_state.keybinds.get(&(modifier_mask, *raw_sym))
} else {
None
};
let mut leds = Led::empty();
if modifiers.num_lock {
leds |= Led::NUMLOCK;
}
if modifiers.caps_lock {
leds |= Led::CAPSLOCK;
}
match (cb_id_mod, cb_id_raw) {
(Some(cb_id), _) | (None, Some(cb_id)) => {
return FilterResult::Intercept(KeyAction::CallCallback(*cb_id));
}
(None, None) => ()
// FIXME: Leds only update once another key is pressed.
for device in self.input_state.libinput_devices.iter_mut() {
device.led_update(leds);
}
let action = keyboard.input(
self,
event.key_code(),
press_state,
serial,
time,
|state, modifiers, keysym| {
if press_state == KeyState::Pressed {
let mut modifier_mask = Vec::<Modifier>::new();
if modifiers.alt {
modifier_mask.push(Modifier::Alt);
}
if modifiers.shift {
modifier_mask.push(Modifier::Shift);
}
if modifiers.ctrl {
modifier_mask.push(Modifier::Ctrl);
}
if modifiers.logo {
modifier_mask.push(Modifier::Super);
}
let modifier_mask = ModifierMask::from(modifier_mask);
let raw_sym = keysym.raw_syms().iter().next();
let mod_sym = keysym.modified_sym();
let cb_id_mod = state.input_state.keybinds.get(&(modifier_mask, mod_sym));
let cb_id_raw = if let Some(raw_sym) = raw_sym {
state.input_state.keybinds.get(&(modifier_mask, *raw_sym))
} else {
None
};
match (cb_id_mod, cb_id_raw) {
(Some(cb_id), _) | (None, Some(cb_id)) => {
return FilterResult::Intercept(KeyAction::CallCallback(*cb_id));
}
if Some((modifier_mask, mod_sym)) == kill_keybind {
return FilterResult::Intercept(KeyAction::Quit);
} else if Some((modifier_mask, mod_sym)) == reload_keybind {
return FilterResult::Intercept(KeyAction::ReloadConfig);
} else if let mut vt @ keysyms::KEY_XF86Switch_VT_1..=keysyms::KEY_XF86Switch_VT_12 =
keysym.modified_sym() {
vt = vt - keysyms::KEY_XF86Switch_VT_1 + 1;
tracing::info!("Switching to vt {vt}");
return FilterResult::Intercept(KeyAction::SwitchVt(vt as i32));
}
(None, None) => (),
}
if keysym.modified_sym() == keysyms::KEY_Control_L {
match press_state {
KeyState::Pressed => {
move_mode = true;
}
KeyState::Released => {
move_mode = false;
}
if Some((modifier_mask, mod_sym)) == kill_keybind {
return FilterResult::Intercept(KeyAction::Quit);
} else if Some((modifier_mask, mod_sym)) == reload_keybind {
return FilterResult::Intercept(KeyAction::ReloadConfig);
} else if let mut vt @ keysyms::KEY_XF86Switch_VT_1
..=keysyms::KEY_XF86Switch_VT_12 = keysym.modified_sym()
{
vt = vt - keysyms::KEY_XF86Switch_VT_1 + 1;
tracing::info!("Switching to vt {vt}");
return FilterResult::Intercept(KeyAction::SwitchVt(vt as i32));
}
}
if keysym.modified_sym() == keysyms::KEY_Control_L {
match press_state {
KeyState::Pressed => {
move_mode = true;
}
KeyState::Released => {
move_mode = false;
}
}
FilterResult::Forward
},
);
}
FilterResult::Forward
},
);
match action {
Some(KeyAction::CallCallback(callback_id)) => {

View file

@ -4,6 +4,7 @@ use async_process::Stdio;
use futures_lite::AsyncBufReadExt;
use smithay::{
desktop::space::SpaceElement,
input::keyboard::XkbConfig,
reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge,
utils::{Point, Rectangle, SERIAL_COUNTER},
wayland::{compositor, shell::xdg::XdgToplevelSurfaceData},
@ -349,6 +350,36 @@ impl State {
self.loop_signal.stop();
}
Msg::SetXkbConfig {
rules,
variant,
layout,
model,
options,
} => {
let new_config = XkbConfig {
rules: &rules.unwrap_or_default(),
model: &model.unwrap_or_default(),
layout: &layout.unwrap_or_default(),
variant: &variant.unwrap_or_default(),
options,
};
if let Some(kb) = self.seat.get_keyboard() {
if let Err(err) = kb.set_xkb_config(self, new_config) {
tracing::error!("Failed to set xkbconfig: {err}");
}
}
}
Msg::SetLibinputSetting(setting) => {
for device in self.input_state.libinput_devices.iter_mut() {
// We're just gonna indiscriminately apply everything and ignore errors
setting.apply_to_device(device);
}
self.input_state.libinput_settings.push(setting);
}
Msg::Request {
request_id,
request,