mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2025-02-13 08:47:56 +01:00
390 lines
13 KiB
Rust
390 lines
13 KiB
Rust
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
|
//! Input management.
|
|
//!
|
|
//! This module provides [`Input`], a struct that gives you several different
|
|
//! methods for setting key- and mousebinds, changing xkeyboard settings, and more.
|
|
//! View the struct's documentation for more information.
|
|
|
|
use futures::{future::BoxFuture, FutureExt, StreamExt};
|
|
use num_enum::TryFromPrimitive;
|
|
use pinnacle_api_defs::pinnacle::input::{
|
|
self,
|
|
v0alpha1::{
|
|
input_service_client::InputServiceClient,
|
|
set_libinput_setting_request::{CalibrationMatrix, Setting},
|
|
SetKeybindRequest, SetLibinputSettingRequest, SetMousebindRequest, SetRepeatRateRequest,
|
|
SetXkbConfigRequest,
|
|
},
|
|
};
|
|
use tokio::sync::mpsc::UnboundedSender;
|
|
use tonic::transport::Channel;
|
|
use xkbcommon::xkb::Keysym;
|
|
|
|
use crate::block_on_tokio;
|
|
|
|
use self::libinput::LibinputSetting;
|
|
|
|
pub mod libinput;
|
|
|
|
/// A mouse button.
|
|
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
|
pub enum MouseButton {
|
|
/// The left mouse button
|
|
Left = 0x110,
|
|
/// The right mouse button
|
|
Right = 0x111,
|
|
/// The middle mouse button
|
|
Middle = 0x112,
|
|
/// The side mouse button
|
|
Side = 0x113,
|
|
/// The extra mouse button
|
|
Extra = 0x114,
|
|
/// The forward mouse button
|
|
Forward = 0x115,
|
|
/// The backward mouse button
|
|
Back = 0x116,
|
|
}
|
|
|
|
/// Keyboard modifiers.
|
|
#[repr(i32)]
|
|
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, TryFromPrimitive)]
|
|
pub enum Mod {
|
|
/// The shift key
|
|
Shift = 1,
|
|
/// The ctrl key
|
|
Ctrl,
|
|
/// The alt key
|
|
Alt,
|
|
/// The super key, aka meta, win, mod4
|
|
Super,
|
|
}
|
|
|
|
/// Press or release.
|
|
#[repr(i32)]
|
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, TryFromPrimitive)]
|
|
pub enum MouseEdge {
|
|
/// Perform actions on button press
|
|
Press = 1,
|
|
/// Perform actions on button release
|
|
Release,
|
|
}
|
|
|
|
/// A struct that lets you define xkeyboard config options.
|
|
///
|
|
/// See `xkeyboard-config(7)` for more information.
|
|
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
|
|
pub struct XkbConfig {
|
|
/// Files of rules to be used for keyboard mapping composition
|
|
pub rules: Option<&'static str>,
|
|
/// Name of the model of your keyboard type
|
|
pub model: Option<&'static str>,
|
|
/// Layout(s) you intend to use
|
|
pub layout: Option<&'static str>,
|
|
/// Variant(s) of the layout you intend to use
|
|
pub variant: Option<&'static str>,
|
|
/// Extra xkb configuration options
|
|
pub options: Option<&'static str>,
|
|
}
|
|
|
|
/// The `Input` struct.
|
|
///
|
|
/// This struct contains methods that allow you to set key- and mousebinds,
|
|
/// change xkeyboard and libinput settings, and change the keyboard's repeat rate.
|
|
#[derive(Debug, Clone)]
|
|
pub struct Input {
|
|
channel: Channel,
|
|
fut_sender: UnboundedSender<BoxFuture<'static, ()>>,
|
|
}
|
|
|
|
impl Input {
|
|
pub(crate) fn new(
|
|
channel: Channel,
|
|
fut_sender: UnboundedSender<BoxFuture<'static, ()>>,
|
|
) -> Self {
|
|
Self {
|
|
channel,
|
|
fut_sender,
|
|
}
|
|
}
|
|
|
|
fn create_input_client(&self) -> InputServiceClient<Channel> {
|
|
InputServiceClient::new(self.channel.clone())
|
|
}
|
|
|
|
/// Set a keybind.
|
|
///
|
|
/// If called with an already set keybind, it gets replaced.
|
|
///
|
|
/// You must supply:
|
|
/// - `mods`: A list of [`Mod`]s. These must be held down for the keybind to trigger.
|
|
/// - `key`: The key that needs to be pressed. This can be anything that implements the [Key] trait:
|
|
/// - `char`
|
|
/// - `&str` and `String`: This is any name from
|
|
/// [xkbcommon-keysyms.h](https://xkbcommon.org/doc/current/xkbcommon-keysyms_8h.html)
|
|
/// without the `XKB_KEY_` prefix.
|
|
/// - `u32`: The numerical key code from the website above.
|
|
/// - A [`keysym`][Keysym] from the [`xkbcommon`] re-export.
|
|
/// - `action`: A closure that will be run when the keybind is triggered.
|
|
/// - Currently, any captures must be both `Send` and `'static`. If you want to mutate
|
|
/// something, consider using channels or [`Box::leak`].
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use pinnacle_api::input::Mod;
|
|
///
|
|
/// // Set `Super + Shift + c` to close the focused window
|
|
/// input.keybind([Mod::Super, Mod::Shift], 'c', || {
|
|
/// if let Some(win) = window.get_focused() {
|
|
/// win.close();
|
|
/// }
|
|
/// });
|
|
///
|
|
/// // With a string key
|
|
/// input.keybind([], "BackSpace", || { /* ... */ });
|
|
///
|
|
/// // With a numeric key
|
|
/// input.keybind([], 65, || { /* ... */ }); // 65 = 'A'
|
|
///
|
|
/// // With a `Keysym`
|
|
/// input.keybind([], pinnacle_api::xkbcommon::xkb::Keysym::Return, || { /* ... */ });
|
|
/// ```
|
|
pub fn keybind(
|
|
&self,
|
|
mods: impl IntoIterator<Item = Mod>,
|
|
key: impl Key + Send + 'static,
|
|
mut action: impl FnMut() + Send + 'static,
|
|
) {
|
|
let mut client = self.create_input_client();
|
|
|
|
let modifiers = mods.into_iter().map(|modif| modif as i32).collect();
|
|
|
|
self.fut_sender
|
|
.send(
|
|
async move {
|
|
let mut stream = client
|
|
.set_keybind(SetKeybindRequest {
|
|
modifiers,
|
|
key: Some(input::v0alpha1::set_keybind_request::Key::RawCode(
|
|
key.into_keysym().raw(),
|
|
)),
|
|
})
|
|
.await
|
|
.unwrap()
|
|
.into_inner();
|
|
|
|
while let Some(Ok(_response)) = stream.next().await {
|
|
action();
|
|
tokio::task::yield_now().await;
|
|
}
|
|
}
|
|
.boxed(),
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
/// Set a mousebind.
|
|
///
|
|
/// If called with an already set mousebind, it gets replaced.
|
|
///
|
|
/// You must supply:
|
|
/// - `mods`: A list of [`Mod`]s. These must be held down for the keybind to trigger.
|
|
/// - `button`: A [`MouseButton`].
|
|
/// - `edge`: A [`MouseEdge`]. This allows you to trigger the bind on either mouse press or release.
|
|
/// - `action`: A closure that will be run when the mousebind is triggered.
|
|
/// - Currently, any captures must be both `Send` and `'static`. If you want to mutate
|
|
/// something, consider using channels or [`Box::leak`].
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use pinnacle_api::input::{Mod, MouseButton, MouseEdge};
|
|
///
|
|
/// // Set `Super + left click` to start moving a window
|
|
/// input.mousebind([Mod::Super], MouseButton::Left, MouseEdge::Press, || {
|
|
/// window.begin_move(MouseButton::Press);
|
|
/// });
|
|
/// ```
|
|
pub fn mousebind(
|
|
&self,
|
|
mods: impl IntoIterator<Item = Mod>,
|
|
button: MouseButton,
|
|
edge: MouseEdge,
|
|
mut action: impl FnMut() + 'static + Send,
|
|
) {
|
|
let mut client = self.create_input_client();
|
|
|
|
let modifiers = mods.into_iter().map(|modif| modif as i32).collect();
|
|
|
|
self.fut_sender
|
|
.send(
|
|
async move {
|
|
let mut stream = client
|
|
.set_mousebind(SetMousebindRequest {
|
|
modifiers,
|
|
button: Some(button as u32),
|
|
edge: Some(edge as i32),
|
|
})
|
|
.await
|
|
.unwrap()
|
|
.into_inner();
|
|
|
|
while let Some(Ok(_response)) = stream.next().await {
|
|
action();
|
|
tokio::task::yield_now().await;
|
|
}
|
|
}
|
|
.boxed(),
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
/// Set the xkeyboard config.
|
|
///
|
|
/// This allows you to set several xkeyboard options like `layout` and `rules`.
|
|
///
|
|
/// See `xkeyboard-config(7)` for more information.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use pinnacle_api::input::XkbConfig;
|
|
///
|
|
/// input.set_xkb_config(XkbConfig {
|
|
/// layout: Some("us,fr,ge"),
|
|
/// options: Some("ctrl:swapcaps,caps:shift"),
|
|
/// ..Default::default()
|
|
/// });
|
|
/// ```
|
|
pub fn set_xkb_config(&self, xkb_config: XkbConfig) {
|
|
let mut client = self.create_input_client();
|
|
|
|
block_on_tokio(client.set_xkb_config(SetXkbConfigRequest {
|
|
rules: xkb_config.rules.map(String::from),
|
|
variant: xkb_config.variant.map(String::from),
|
|
layout: xkb_config.layout.map(String::from),
|
|
model: xkb_config.model.map(String::from),
|
|
options: xkb_config.options.map(String::from),
|
|
}))
|
|
.unwrap();
|
|
}
|
|
|
|
/// Set the keyboard's repeat rate.
|
|
///
|
|
/// This allows you to set the time between holding down a key and it repeating
|
|
/// as well as the time between each repeat.
|
|
///
|
|
/// Units are in milliseconds.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// // Set keyboard to repeat after holding down for half a second,
|
|
/// // and repeat once every 25ms (40 times a second)
|
|
/// input.set_repeat_rate(25, 500);
|
|
/// ```
|
|
pub fn set_repeat_rate(&self, rate: i32, delay: i32) {
|
|
let mut client = self.create_input_client();
|
|
|
|
block_on_tokio(client.set_repeat_rate(SetRepeatRateRequest {
|
|
rate: Some(rate),
|
|
delay: Some(delay),
|
|
}))
|
|
.unwrap();
|
|
}
|
|
|
|
/// Set a libinput setting.
|
|
///
|
|
/// From [freedesktop.org](https://www.freedesktop.org/wiki/Software/libinput/):
|
|
/// > libinput is a library to handle input devices in Wayland compositors
|
|
///
|
|
/// As such, this method allows you to set various settings related to input devices.
|
|
/// This includes things like pointer acceleration and natural scrolling.
|
|
///
|
|
/// See [`LibinputSetting`] for all the settings you can change.
|
|
///
|
|
/// Note: currently Pinnacle applies anything set here to *every* device, regardless of what it
|
|
/// actually is. This will be fixed in the future.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use pinnacle_api::input::libinput::*;
|
|
///
|
|
/// // Set pointer acceleration to flat
|
|
/// input.set_libinput_setting(LibinputSetting::AccelProfile(AccelProfile::Flat));
|
|
///
|
|
/// // Enable natural scrolling (reverses scroll direction; usually used with trackpads)
|
|
/// input.set_libinput_setting(LibinputSetting::NaturalScroll(true));
|
|
/// ```
|
|
pub fn set_libinput_setting(&self, setting: LibinputSetting) {
|
|
let mut client = self.create_input_client();
|
|
|
|
let setting = match setting {
|
|
LibinputSetting::AccelProfile(profile) => Setting::AccelProfile(profile as i32),
|
|
LibinputSetting::AccelSpeed(speed) => Setting::AccelSpeed(speed),
|
|
LibinputSetting::CalibrationMatrix(matrix) => {
|
|
Setting::CalibrationMatrix(CalibrationMatrix {
|
|
matrix: matrix.to_vec(),
|
|
})
|
|
}
|
|
LibinputSetting::ClickMethod(method) => Setting::ClickMethod(method as i32),
|
|
LibinputSetting::DisableWhileTyping(disable) => Setting::DisableWhileTyping(disable),
|
|
LibinputSetting::LeftHanded(enable) => Setting::LeftHanded(enable),
|
|
LibinputSetting::MiddleEmulation(enable) => Setting::MiddleEmulation(enable),
|
|
LibinputSetting::RotationAngle(angle) => Setting::RotationAngle(angle),
|
|
LibinputSetting::ScrollButton(button) => Setting::RotationAngle(button),
|
|
LibinputSetting::ScrollButtonLock(enable) => Setting::ScrollButtonLock(enable),
|
|
LibinputSetting::ScrollMethod(method) => Setting::ScrollMethod(method as i32),
|
|
LibinputSetting::NaturalScroll(enable) => Setting::NaturalScroll(enable),
|
|
LibinputSetting::TapButtonMap(map) => Setting::TapButtonMap(map as i32),
|
|
LibinputSetting::TapDrag(enable) => Setting::TapDrag(enable),
|
|
LibinputSetting::TapDragLock(enable) => Setting::TapDragLock(enable),
|
|
LibinputSetting::Tap(enable) => Setting::Tap(enable),
|
|
};
|
|
|
|
block_on_tokio(client.set_libinput_setting(SetLibinputSettingRequest {
|
|
setting: Some(setting),
|
|
}))
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
/// A trait that designates anything that can be converted into a [`Keysym`].
|
|
pub trait Key {
|
|
/// Convert this into a [`Keysym`].
|
|
fn into_keysym(self) -> Keysym;
|
|
}
|
|
|
|
impl Key for Keysym {
|
|
fn into_keysym(self) -> Keysym {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Key for char {
|
|
fn into_keysym(self) -> Keysym {
|
|
Keysym::from_char(self)
|
|
}
|
|
}
|
|
|
|
impl Key for &str {
|
|
fn into_keysym(self) -> Keysym {
|
|
xkbcommon::xkb::keysym_from_name(self, xkbcommon::xkb::KEYSYM_NO_FLAGS)
|
|
}
|
|
}
|
|
|
|
impl Key for String {
|
|
fn into_keysym(self) -> Keysym {
|
|
xkbcommon::xkb::keysym_from_name(&self, xkbcommon::xkb::KEYSYM_NO_FLAGS)
|
|
}
|
|
}
|
|
|
|
impl Key for u32 {
|
|
fn into_keysym(self) -> Keysym {
|
|
Keysym::from(self)
|
|
}
|
|
}
|