Added support for key combinations
This commit is contained in:
parent
9201e34a00
commit
a2cbcabb3d
13 changed files with 313 additions and 1641 deletions
1367
Cargo.lock
generated
1367
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
17
Cargo.toml
17
Cargo.toml
|
@ -1,17 +0,0 @@
|
|||
[package]
|
||||
name = "makima"
|
||||
version = "0.3.1"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
evdev = { version = "0.12.1", features = ["tokio", "serde"] }
|
||||
tokio = { version = "1.28.1", features = ["full"] }
|
||||
serde = { version = "1.0.163", features = ["derive"] }
|
||||
tokio-stream = "0.1.14"
|
||||
tokio-udev = "0.9.1"
|
||||
hyprland = "<=0.3.9"
|
||||
swayipc-async = "2.0.2"
|
||||
toml = "0.7.3"
|
||||
home = "<=0.5.5"
|
122
README.md
122
README.md
|
@ -1,122 +0,0 @@
|
|||
# makima
|
||||
|
||||
Makima is a daemon for Linux to remap keyboards, mice, controllers and tablets.\
|
||||
It works on Wayland, X11 and even tty, as it relies on the `evdev` kernel interface.\
|
||||
Previously only a controller daemon, the scope has now been expanded because I had nothing better to do.
|
||||
|
||||
## Features:
|
||||
- Configure your keybindings through simple TOML config files, one for each device.
|
||||
- Remap keys/buttons or bind entire macros and key sequences.
|
||||
- Supports keyboards, mice and any other device that uses input events present inside `/usr/include/linux/input-event-codes.h`.
|
||||
- Hotplug to connect and disconnect your devices whenever you want.
|
||||
- Supports wired and Bluetooth connections.
|
||||
- If you connect a [supported game controller](https://github.com/cyber-sushi/makima/tree/main#tested-controllers), you can move your cursor using analog sticks with adjustable sensitivity.
|
||||
- You can have multiple sets of key bindings that automatically switch based on the active window (only on Hyprland and Sway currently).
|
||||
- Written in Rust so it's blazingly fast, I think?
|
||||
|
||||
## How to use:
|
||||
1. Download the executable from the Releases page or compile it yourself using Cargo.
|
||||
2. Create a TOML config file inside `~/.config/makima` and rename it with the _exact_ name of your device. You can check the name by running `evtest`.
|
||||
3. Assign your keybindings and macros inside the config file, follow [this documentation](https://github.com/cyber-sushi/makima/tree/main#configuration) for more info.
|
||||
4. If you're using a [supported game controller](https://github.com/cyber-sushi/makima/tree/main#tested-controllers), you can pick a config file from the 'config examples' folder on this Github and rename it with the _exact_ name of your device.
|
||||
6. Make sure the `makima` executable has permission to be executed as a program. If not, `cd` into the directory of the executable and use `chmod +x makima`. Alternatively, Right Click > Properties > "allow executing as program" or something like that.
|
||||
7. Make sure your user has access to event devices. If it doesn't, use `sudo usermod -aG input yourusername`.
|
||||
8. Launch Makima and it'll automatically recognize all connected devices that have a corresponding config file inside `~/.config/makima`.
|
||||
- You can either:
|
||||
- Launch it from your file manager by double clicking.
|
||||
- Launch it from terminal by `cd`ing to the directory of the executable, then using `./makima`.
|
||||
- Move the executable to a directory that's in PATH, then launch it using `rofi`, `dmenu` or whatever launcher you use. I personally added `~/.local/share/bin` to PATH and put all my executables there.
|
||||
- Create a .desktop file and launch it using that.
|
||||
- Autostart it from your window manager's config file (usually `exec /path/to/makima`).
|
||||
|
||||
## Configuration:
|
||||
You can find some sample config files on this Github; pick one that fits your use case and copy it inside `~/.config/makima`, then edit it to your needs.\
|
||||
**To associate a config file to an input device, the file name should be identical to that of the device.**\
|
||||
For example, if you run `evtest` and see that your Dualshock 4 controller is named `Sony Interactive Entertainment Wireless Controller`, then you'll have to name your config file `Sony Interactive Entertainment Wireless Controller.toml`.\
|
||||
All config files will be parsed automatically when `makima` is launched.
|
||||
|
||||
### Adaptive bindings for each window (Hyprland and Sway only atm):
|
||||
Have you ever wanted to have a different set of macros for each game that you play? Or maybe you want your controller to input Space when you press X, but only when MPV is focused? Then this is exactly what you're looking for!\
|
||||
To have window-specific config files, just put `::window_class` at the end of their filename, before `.toml`. For example, if you want your DS4 controller to have a specific set of keybindings for Firefox, name that file `Sony Interactive Entertainment Wireless Controller::firefox.toml`. To retrieve the window class of a specific application, refer to your compositor's documentation, e.g. on Hyprland type `hyprctl clients` in your terminal while that application is open.\
|
||||
**Note: on Sway, make sure that the `XDG_DESKTOP_SESSION=sway` environment variable is set, or Makima won't be able to use adaptive bindings.**
|
||||
|
||||
## The config files:
|
||||
The config file has three sections, a `[keys]` section, where you'll remap your keys, a `[rel]` section to remap scroll wheels and a `[settings]` section containing a few options.
|
||||
|
||||
### \[keys]
|
||||
Example where the Caps Lock and Ctrl keys are switched:
|
||||
```
|
||||
[keys]
|
||||
KEY_CAPSLOCK = ["KEY_LEFTCTRL"]
|
||||
KEY_LEFTCTRL = ["KEY_CAPSLOCK"]
|
||||
```
|
||||
Example where pressing Caps Lock triggers the Ctrl+C macro:
|
||||
```
|
||||
[keys]
|
||||
KEY_CAPSLOCK = ["KEY_LEFTCTRL", "KEY_C"]
|
||||
```
|
||||
Example where pressing any key on your mouse will immediately shut down your computer if you're focused on a terminal:
|
||||
```
|
||||
[keys]
|
||||
BTN_LEFT = ["KEY_S", "KEY_H", "KEY_U", "KEY_T", "KEY_D", "KEY_O", "KEY_W", "KEY_N", "KEY_SPACE", "KEY_N", "KEY_O", "KEY_W", "KEY_ENTER"]
|
||||
BTN_RIGHT = ["KEY_S", "KEY_H", "KEY_U", "KEY_T", "KEY_D", "KEY_O", "KEY_W", "KEY_N", "KEY_SPACE", "KEY_N", "KEY_O", "KEY_W", "KEY_ENTER"]
|
||||
BTN_MIDDLE = ["KEY_S", "KEY_H", "KEY_U", "KEY_T", "KEY_D", "KEY_O", "KEY_W", "KEY_N", "KEY_SPACE", "KEY_N", "KEY_O", "KEY_W", "KEY_ENTER"]
|
||||
```
|
||||
To see all of the available key codes, refer to the file `/usr/include/linux/input-event-codes.h`.\
|
||||
Remember that keys like Ctrl and Alt will have key codes like `KEY_LEFTCTRL`, `KEY_RIGHTCTRL`, `KEY_LEFTALT` and `KEY_RIGHTALT`. Just using `KEY_CTRL` and `KEY_ALT` will throw a parsing error because the key code does not exist.\
|
||||
Keys that are not explicitly remapped will keep their default functionality.
|
||||
If you don't need to remap any key, you can just omit the entire `[keys]` paragraph.
|
||||
|
||||
### \[rel]
|
||||
Example where the mouse scroll wheel will zoom in and out of a browser page:
|
||||
```
|
||||
[rel]
|
||||
SCROLL_WHEEL_UP = ["KEY_LEFTCTRL", "KEY_LEFTSHIFT", "KEY_EQUAL"]
|
||||
SCROLL_WHEEL_DOWN = ["KEY_LEFTCTRL", "KEY_MINUS"]
|
||||
```
|
||||
If you don't need to remap your scroll wheel, just omit the `[rel]` paragraph and it'll fall back to default functionality.
|
||||
|
||||
### \[settings]
|
||||
There are currently 4 available settings:
|
||||
- `GRAB_DEVICE` will set if Makima should have exclusivity over the device. If set to `"true"`, no other program will read the original input of the device. If set to `"false"`, both the original input and the remapped input will be read by applications. The event reader won't start if this is not set.
|
||||
- `MOVE_MOUSE_WITH_STICK` will set if your mouse cursor should be moved using your controller's analog sticks, and which of the two sticks should move your cursor. Can be set to `"left"`, `"right"` or `"none"`. Defaults to "left" if not set.
|
||||
- `ANALOG_SENSITIVITY` will change the speed of your mouse cursor when moved through an analog stick. Lower value is higher sensitivity, minimum `"1"`, suggested `"6"`. The analog stick won't be read if this is not set.
|
||||
- `SIGNED_AXIS_VALUE` is needed if you're using Xbox controllers and Switch Joy-Cons to properly calibrate the analog stick's sensitivity. Set to `"true"` if you're using those controllers. Can be left out otherwise.
|
||||
|
||||
Example settings for a keyboard or mouse, notice that only the `GRAB_DEVICE` setting is needed in this case and you can leave everything else out:
|
||||
```
|
||||
[settings]
|
||||
GRAB_DEVICE = "true"
|
||||
```
|
||||
Example settings for a an Xbox 360/One controller:
|
||||
```
|
||||
[settings]
|
||||
ANALOG_SENSITIVITY = "6"
|
||||
MOVE_MOUSE_WITH_STICK = "left"
|
||||
GRAB_DEVICE = "false"
|
||||
SIGNED_AXIS_VALUE = "true"
|
||||
```
|
||||
Refer to the sample config files on this Github for more information about controllers.
|
||||
|
||||
## Tested controllers:
|
||||
- DualShock 2
|
||||
- DualShock 3
|
||||
- DualShock 4
|
||||
- DualSense
|
||||
- Xbox 360
|
||||
- Xbox One
|
||||
- Xbox Elite 2
|
||||
- Stadia
|
||||
- Switch Joy-Cons
|
||||
|
||||
To add other controllers, please open an issue.
|
||||
|
||||
## Troubleshooting and possible questions:
|
||||
Q: My device actually shows as three different devices in evtest, do I need to create three different config files, one for each device?\
|
||||
A: Yes, most mice with additional keys are usually seen as a mouse and a keyboard by the kernel and they need to be mapped separately.
|
||||
|
||||
Q: Can I map a key sequence (e.g. Ctrl+C) to something else?\
|
||||
A: Currently, no. Only single key/button strokes can be mapped to other keys or macros. You can map a mouse button to Ctrl+C, but you can't map Ctrl+C to a mouse button.
|
||||
|
||||
Q: My controller works when using Bluetooth but not when using wired connection or vice-versa, why?\
|
||||
A: Some devices have a different evdev name when connected through Bluetooth, for example a `Sony Interactive Entertainment Wireless Controller` is just seen as `Wireless Controller` when connected via Bluetooth. You'll need to create a copy of the config file with that name.
|
|
@ -1,12 +1,18 @@
|
|||
#SAMPLE CONFIG FILE FOR A GENERIC KEYBOARD
|
||||
#Put this in ~/.config/makima and rename it to the exact name of the device as shown by the 'evtest' command, including spaces and capitalization.
|
||||
#You can find all the available keycodes in /usr/include/linux/input-event-codes.h
|
||||
#If you're not sure which keycode corresponds to which key, you can run 'evtest', select your keyboard/mouse and press the corresponding key/button.
|
||||
#If you're not sure which keycode corresponds to which key, you can run 'evtest', select your device and press the corresponding key/button.
|
||||
|
||||
[keys]
|
||||
[bindings.keys]
|
||||
#KEYS MOUSE/KEYBOARD BINDINGS
|
||||
KEY_CAPSLOCK = ["KEY_LEFTCTRL"]
|
||||
KEY_LEFTCTRL = ["KEY_CAPSLOCK"]
|
||||
|
||||
[combinations.keys]
|
||||
KEY_LEFTCTRL-KEY_LEFTSHIFT.KEY_Q = ["KEY_ESC"]
|
||||
KEY_LEFTSHIFT.KEY_UP = ["KEY_LEFTSHIFT", "KEY_PAGEUP"]
|
||||
KEY_LEFTSHIFT.KEY_DOWN = ["KEY_LEFTSHIFT", "KEY_PAGEDOWN"]
|
||||
KEY_LEFTSHIFT-KEY_LEFTMETA-KEY_LEFTALT-KEY_RIGHTCTRL = ["KEY_LEFTCTRL", "KEY_C"]
|
||||
|
||||
[settings]
|
||||
GRAB_DEVICE = "true" #gain exclusivity on the device
|
|
@ -1,15 +1,25 @@
|
|||
#SAMPLE CONFIG FILE FOR A GENERIC MOUSE
|
||||
#Put this in ~/.config/makima and rename it to the exact name of the device as shown by the 'evtest' command, including spaces and capitalization.
|
||||
#You can find all the available keycodes in /usr/include/linux/input-event-codes.h
|
||||
#If you're not sure which keycode corresponds to which key, you can run 'evtest', select your keyboard/mouse and press the corresponding key/button.
|
||||
#If you're not sure which keycode corresponds to which key, you can run 'evtest', select your device and press the corresponding key/button.
|
||||
#Axis values are hard coded instead, use SCROLL_WHEEL_UP and SCROLL_WHEEL_DOWN to rebind wheel movements.
|
||||
|
||||
[keys]
|
||||
[bindings.keys]
|
||||
#MOUSE BUTTONS MOUSE/KEYBOARD BINDINGS
|
||||
BTN_RIGHT = ["KEY_LEFTCTRL", "KEY_C"]
|
||||
|
||||
[rel]
|
||||
SCROLL_WHEEL_UP = ["KEY_LEFTCTRL", "KEY_LEFTSHIFT", "KEY_EQUAL"]
|
||||
SCROLL_WHEEL_DOWN = ["KEY_LEFTCTRL", "KEY_MINUS"]
|
||||
[bindings.axis]
|
||||
SCROLL_WHEEL_UP = ["KEY_LEFTCTRL", "KEY_F"]
|
||||
SCROLL_WHEEL_DOWN = ["KEY_LEFTCTRL", "KEY_Q"]
|
||||
|
||||
[combinations.keys]
|
||||
KEY_LEFTCTRL.BTN_RIGHT = ["KEY_SYSRQ"]
|
||||
KEY_LEFTCTRL-KEY_LEFTSHIFT.BTN_LEFT = ["KEY_LEFTSHIFT", "KEY_DELETE"]
|
||||
|
||||
[combinations.axis]
|
||||
KEY_LEFTCTRL-KEY_LEFTSHIFT.SCROLL_WHEEL_UP = ["KEY_HOME"]
|
||||
KEY_LEFTCTRL-KEY_LEFTSHIFT.SCROLL_WHEEL_DOWN = ["KEY_END"]
|
||||
KEY_LEFTCTRL-KEY_LEFTSHIFT-KEY_LEFTALT.SCROLL_WHEEL_DOWN = ["KEY_LEFTALT", "KEY_F4"]
|
||||
|
||||
[settings]
|
||||
GRAB_DEVICE = "true" #gain exclusivity on the device
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
#SAMPLE CONFIG FILE FOR PLAYSTATION CONTROLLERS
|
||||
#Put this in ~/.config/makima and rename it to the exact name of the device as shown by the 'evtest' command, including spaces and capitalization.
|
||||
#You can find all the available keycodes in /usr/include/linux/input-event-codes.h
|
||||
#If you're not sure which keycode corresponds to which key, you can run 'evtest', select your keyboard/mouse and press the corresponding key/button.
|
||||
#If you're not sure which keycode corresponds to which key, you can run 'evtest', select your device and press the corresponding key/button.
|
||||
#This config file is tested for DualShock 3, DualShock 4 and DualSense controllers. When using a different controller, if no specific config file for your device is available, change the keycodes on the left according to those of your controller (evtest is your friend again). If your controller has a button to enable/disable analog sticks, make sure they're enabled.
|
||||
|
||||
[keys]
|
||||
[bindings.keys]
|
||||
#CONTROLLER MOUSE/KEYBOARD BINDINGS
|
||||
BTN_NORTH = ["KEY_LEFTMETA", "KEY_J"] #triangle
|
||||
BTN_EAST = ["KEY_ENTER"] #circle
|
||||
BTN_SOUTH = ["KEY_LEFTSHIFT"] #X
|
||||
BTN_WEST = ["KEY_LEFTMETA"] #square
|
||||
BTN_TR = ["KEY_LEFTMETA", "KEY_L"] #R1
|
||||
BTN_TR2 = ["KEY_LEFTMETA", "KEY_LEFTSHIFT", "KEY_L"] #R2
|
||||
BTN_TL = ["KEY_LEFTMETA", "KEY_K"] #L1
|
||||
BTN_TL2 = ["KEY_LEFTMETA", "KEY_LEFTSHIFT", "KEY_K"] #L2
|
||||
BTN_START = ["KEY_LEFTMETA", "KEY_D"] #start
|
||||
BTN_SELECT = ["KEY_ESC"] #select
|
||||
BTN_THUMBR = ["KEY_LEFTMETA", "KEY_Q"] #R3
|
||||
BTN_THUMBL = ["BTN_MIDDLE"] #L3
|
||||
BTN_MODE = ["KEY_SPACE"] #PS button
|
||||
|
||||
[bindings.axis]
|
||||
BTN_TL2 = ["KEY_LEFTMETA", "KEY_LEFTSHIFT", "KEY_K"] #L2
|
||||
BTN_TR2 = ["KEY_LEFTMETA", "KEY_LEFTSHIFT", "KEY_L"] #R2
|
||||
BTN_DPAD_UP = ["KEY_UP"] #directional pad up
|
||||
BTN_DPAD_RIGHT = ["KEY_RIGHT"] #directional pad right
|
||||
BTN_DPAD_DOWN = ["KEY_DOWN"] #directional pad down
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#SAMPLE CONFIG FILE FOR PLAYSTATION 2 CONTROLLERS
|
||||
#Put this in ~/.config/makima and rename it to the exact name of the device as shown by the 'evtest' command, including spaces and capitalization.
|
||||
#You can find all the available keycodes in /usr/include/linux/input-event-codes.h
|
||||
#If you're not sure which keycode corresponds to which key, you can run 'evtest', select your keyboard/mouse and press the corresponding key/button.
|
||||
#If you're not sure which keycode corresponds to which key, you can run 'evtest', select your device and press the corresponding key/button.
|
||||
#This config file is tested for DualShock 2 controllers. The "analog" LED on your controller has to be turned on or this daemon won't work properly. When using a different controller, if no specific config file for your device is available, change the keycodes on the left according to those of your controller (evtest is your friend again).
|
||||
|
||||
[keys]
|
||||
[bindings.keys]
|
||||
#CONTROLLER MOUSE/KEYBOARD BINDINGS
|
||||
BTN_TRIGGER = ["KEY_LEFTMETA", "KEY_J"] #triangle
|
||||
BTN_THUMB = ["KEY_ENTER"] #circle
|
||||
|
@ -18,6 +18,8 @@ BTN_BASE4 = ["KEY_LEFTMETA", "KEY_D"] #start
|
|||
BTN_BASE3 = ["KEY_ESC"] #select
|
||||
BTN_BASE6 = ["KEY_LEFTMETA", "KEY_Q"] #R3
|
||||
BTN_BASE5 = ["BTN_MIDDLE"] #L3
|
||||
|
||||
[bindings.axis]
|
||||
BTN_DPAD_UP = ["KEY_UP"] #directional pad up
|
||||
BTN_DPAD_RIGHT = ["KEY_RIGHT"] #directional pad right
|
||||
BTN_DPAD_DOWN = ["KEY_DOWN"] #directional pad down
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#SAMPLE CONFIG FILE FOR GOOGLE STADIA CONTROLLERS
|
||||
#Put this in ~/.config/makima and rename it to the exact name of the device as shown by the 'evtest' command, including spaces and capitalization.
|
||||
#You can find all the available keycodes in /usr/include/linux/input-event-codes.h
|
||||
#If you're not sure which keycode corresponds to which key, you can run 'evtest', select your keyboard/mouse and press the corresponding key/button.
|
||||
#If you're not sure which keycode corresponds to which key, you can run 'evtest', select your device and press the corresponding key/button.
|
||||
#This config file is tested for Stadia controllers. When using a different controller, if no specific config file for your device is available, change the keycodes on the left according to those of your controller (evtest is your friend again). If your controller has a button to enable/disable analog sticks, make sure they're enabled.
|
||||
|
||||
[keys]
|
||||
[bindings.keys]
|
||||
#CONTROLLER MOUSE/KEYBOARD BINDINGS
|
||||
BTN_WEST = ["KEY_LEFTMETA", "KEY_J"] #Y
|
||||
BTN_EAST = ["KEY_ENTER"] #X
|
||||
|
@ -21,6 +21,8 @@ BTN_TRIGGER_HAPPY2 = ["BTN_LEFT"] #share/bubbles?
|
|||
BTN_THUMBR = ["KEY_LEFTMETA", "KEY_Q"] #R3
|
||||
BTN_THUMBL = ["BTN_MIDDLE"] #L3
|
||||
BTN_MODE = ["KEY_SPACE"] #Stadia button
|
||||
|
||||
[bindings.axis]
|
||||
BTN_DPAD_UP = ["KEY_UP"] #directional pad up
|
||||
BTN_DPAD_RIGHT = ["KEY_RIGHT"] #directional pad right
|
||||
BTN_DPAD_DOWN = ["KEY_DOWN"] #directional pad down
|
||||
|
|
|
@ -1,25 +1,27 @@
|
|||
#SAMPLE CONFIG FILE FOR SWITCH JOYCONS
|
||||
#Put this in ~/.config/makima and rename it to the exact name of the device as shown by the 'evtest' command, including spaces and capitalization.
|
||||
#You can find all the available keycodes in /usr/include/linux/input-event-codes.h
|
||||
#If you're not sure which keycode corresponds to which key, you can run 'evtest', select your keyboard/mouse and press the corresponding key/button.
|
||||
#If you're not sure which keycode corresponds to which key, you can run 'evtest', select your device and press the corresponding key/button.
|
||||
#This config file is tested for Switch Joycons (Left and Right). When using a different controller, if no specific config file for your device is available, change the keycodes on the left according to those of your controller (evtest is your friend again). If your controller has a button to enable/disable analog sticks, make sure they're enabled.
|
||||
|
||||
[keys]
|
||||
[bindings.keys]
|
||||
#CONTROLLER MOUSE/KEYBOARD BINDINGS
|
||||
BTN_NORTH = ["KEY_LEFTMETA", "KEY_J"] #X
|
||||
BTN_EAST = ["KEY_ENTER"] #A
|
||||
BTN_SOUTH = ["KEY_LEFTSHIFT"] #B
|
||||
BTN_WEST = ["KEY_LEFTMETA"] #Y
|
||||
BTN_TR = ["KEY_LEFTMETA", "KEY_L"] #R (and SL on left joycon)
|
||||
BTN_TR2 = ["KEY_LEFTMETA", "KEY_LEFTSHIFT", "KEY_L"] #ZR (and SR on left joycon)
|
||||
BTN_TL = ["KEY_LEFTMETA", "KEY_K"] #L (and SL on right joycon)
|
||||
BTN_TL2 = ["KEY_LEFTMETA", "KEY_LEFTSHIFT", "KEY_K"] #ZL (and SR on right joycon)
|
||||
BTN_START = ["KEY_LEFTMETA", "KEY_D"] #plus
|
||||
BTN_SELECT = ["KEY_ESC"] #minus
|
||||
BTN_THUMBR = ["KEY_LEFTMETA", "KEY_Q"] #right stick press
|
||||
BTN_THUMBL = ["BTN_MIDDLE"] #left stick press
|
||||
BTN_MODE = ["KEY_SPACE"] #home
|
||||
BTN_Z = ["BTN_LEFT"] #capture
|
||||
|
||||
[bindings.axis]
|
||||
BTN_TL2 = ["KEY_LEFTMETA", "KEY_LEFTSHIFT", "KEY_K"] #ZL (and SR on right joycon)
|
||||
BTN_TR2 = ["KEY_LEFTMETA", "KEY_LEFTSHIFT", "KEY_L"] #ZR (and SR on left joycon)
|
||||
BTN_DPAD_UP = ["KEY_UP"] #directional pad up
|
||||
BTN_DPAD_RIGHT = ["KEY_RIGHT"] #directional pad right
|
||||
BTN_DPAD_DOWN = ["KEY_DOWN"] #directional pad down
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
#SAMPLE CONFIG FILE FOR XBOX CONTROLLERS
|
||||
#Put this in ~/.config/makima and rename it to the exact name of the device as shown by the 'evtest' command, including spaces and capitalization.
|
||||
#You can find all the available keycodes in /usr/include/linux/input-event-codes.h
|
||||
#If you're not sure which keycode corresponds to which key, you can run 'evtest', select your keyboard/mouse and press the corresponding key/button.
|
||||
#If you're not sure which keycode corresponds to which key, you can run 'evtest', select your device and press the corresponding key/button.
|
||||
#This config file is tested for Xbox 360, Xbox One and Xbox Elite 2 controllers. When using a different controller, if no specific config file for your device is available, change the keycodes on the left according to those of your controller (evtest is your friend again). If your controller has a button to enable/disable analog sticks, make sure they're enabled.
|
||||
|
||||
[keys]
|
||||
[bindings.keys]
|
||||
#CONTROLLER MOUSE/KEYBOARD BINDINGS
|
||||
BTN_NORTH = ["KEY_LEFTMETA", "KEY_J"] #X
|
||||
BTN_EAST = ["KEY_ENTER"] #Y
|
||||
BTN_SOUTH = ["KEY_LEFTSHIFT"] #A
|
||||
BTN_WEST = ["KEY_LEFTMETA"] #B
|
||||
BTN_TR = ["KEY_LEFTMETA", "KEY_L"] #RB
|
||||
BTN_TR2 = ["KEY_LEFTMETA", "KEY_LEFTSHIFT", "KEY_L"] #RT
|
||||
BTN_TL = ["KEY_LEFTMETA", "KEY_K"] #LB
|
||||
BTN_TL2 = ["KEY_LEFTMETA", "KEY_LEFTSHIFT", "KEY_K"] #LT
|
||||
BTN_START = ["KEY_LEFTMETA", "KEY_D"] #start
|
||||
BTN_SELECT = ["KEY_ESC"] #back
|
||||
BTN_THUMBR = ["KEY_LEFTMETA", "KEY_Q"] #RS
|
||||
BTN_THUMBL = ["BTN_MIDDLE"] #LS
|
||||
BTN_MODE = ["KEY_SPACE"] #Xbox button
|
||||
|
||||
[bindings.axis]
|
||||
BTN_TR2 = ["KEY_LEFTMETA", "KEY_LEFTSHIFT", "KEY_L"] #RT
|
||||
BTN_TL2 = ["KEY_LEFTMETA", "KEY_LEFTSHIFT", "KEY_K"] #LT
|
||||
BTN_DPAD_UP = ["KEY_UP"] #directional pad up
|
||||
BTN_DPAD_RIGHT = ["KEY_RIGHT"] #directional pad right
|
||||
BTN_DPAD_DOWN = ["KEY_DOWN"] #directional pad down
|
||||
|
|
110
src/config.rs
110
src/config.rs
|
@ -1,48 +1,110 @@
|
|||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, BTreeMap};
|
||||
use std::str::FromStr;
|
||||
use evdev::Key;
|
||||
use serde;
|
||||
|
||||
|
||||
#[derive(serde::Deserialize, Debug, Clone, Default)]
|
||||
pub struct Bindings {
|
||||
#[serde(default)]
|
||||
pub keys: HashMap<Key, Vec<Key>>,
|
||||
#[serde(default)]
|
||||
pub axis: HashMap<String, Vec<Key>>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug, Clone, Default)]
|
||||
pub struct Combinations {
|
||||
#[serde(default)]
|
||||
pub keys: HashMap<String, HashMap<Key, Vec<Key>>>,
|
||||
#[serde(default)]
|
||||
pub axis: HashMap<String, HashMap<String, Vec<Key>>>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug, Clone, Default)]
|
||||
pub struct Modifiers {
|
||||
pub keys: HashMap<BTreeMap<Key, i32>, HashMap<Key, Vec<Key>>>,
|
||||
pub axis: HashMap<BTreeMap<Key, i32>, HashMap<String, Vec<Key>>>,
|
||||
}
|
||||
|
||||
impl Modifiers {
|
||||
pub fn new() -> Self {
|
||||
let keys: HashMap<BTreeMap<Key, i32>, HashMap<Key, Vec<Key>>> = HashMap::new();
|
||||
let axis: HashMap<BTreeMap<Key, i32>, HashMap<String, Vec<Key>>> = HashMap::new();
|
||||
Self {
|
||||
keys: keys,
|
||||
axis: axis
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug, Clone)]
|
||||
pub struct Config {
|
||||
#[serde(skip)]
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub keys: HashMap<Key, Vec<Key>>,
|
||||
pub settings: HashMap<String, String>,
|
||||
#[serde(skip)]
|
||||
pub abs: HashMap<String, Vec<Key>>,
|
||||
pub bindings: Bindings,
|
||||
#[serde(default)]
|
||||
pub rel: HashMap<String, Vec<Key>>,
|
||||
pub combinations: Combinations,
|
||||
#[serde(skip)]
|
||||
pub modifiers: Modifiers,
|
||||
pub settings: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new_from_file(file: &str, file_name: String) -> Self {
|
||||
println!("Parsing config file at {:?}", file);
|
||||
println!("Parsing config file:\n{:?}\n", file);
|
||||
let file_content: String = std::fs::read_to_string(file).unwrap();
|
||||
let config: Config = toml::from_str(&file_content)
|
||||
.expect("Couldn't parse config file.");
|
||||
let keys: HashMap<Key, Vec<Key>> = config.keys;
|
||||
let rel: HashMap<String, Vec<Key>> = config.rel;
|
||||
let mut abs: HashMap<String, Vec<Key>> = HashMap::new();
|
||||
let mut pad_horizontal: Vec<Key> = keys.get(&Key::BTN_DPAD_LEFT)
|
||||
.unwrap_or(&Vec::new()).clone();
|
||||
pad_horizontal.extend(keys.get(&Key::BTN_DPAD_RIGHT)
|
||||
.unwrap_or(&Vec::new()));
|
||||
let mut pad_vertical: Vec<Key> = keys.get(&Key::BTN_DPAD_UP)
|
||||
.unwrap_or(&Vec::new()).clone();
|
||||
pad_vertical.extend(keys.get(&Key::BTN_DPAD_DOWN)
|
||||
.unwrap_or(&Vec::new()));
|
||||
abs.insert("NONE_X".to_string(), pad_horizontal);
|
||||
abs.insert("NONE_Y".to_string(), pad_vertical);
|
||||
let mut bindings: Bindings = config.bindings;
|
||||
let combinations: Combinations = config.combinations;
|
||||
let settings: HashMap<String, String> = config.settings;
|
||||
|
||||
let empty_modmap = BTreeMap::from ([
|
||||
(Key::KEY_LEFTSHIFT, 0),
|
||||
(Key::KEY_LEFTCTRL, 0),
|
||||
(Key::KEY_LEFTALT, 0),
|
||||
(Key::KEY_RIGHTSHIFT, 0),
|
||||
(Key::KEY_RIGHTCTRL, 0),
|
||||
(Key::KEY_RIGHTALT, 0),
|
||||
(Key::KEY_LEFTMETA, 0)
|
||||
]);
|
||||
let mut modifiers = Modifiers::new();
|
||||
for (mods, map) in combinations.keys.iter() {
|
||||
let mods_vector = mods.split("-").map(str::to_string).collect::<Vec<String>>();
|
||||
let mut modmap = empty_modmap.clone();
|
||||
for modifier in mods_vector {
|
||||
modmap.insert(Key::from_str(&modifier).unwrap(), 1);
|
||||
}
|
||||
modifiers.keys.insert(modmap, map.clone());
|
||||
}
|
||||
|
||||
for (mods, map) in combinations.axis.iter() {
|
||||
let mods_vector = mods.split("-").map(str::to_string).collect::<Vec<String>>();
|
||||
let mut modmap = empty_modmap.clone();
|
||||
for modifier in mods_vector {
|
||||
modmap.insert(Key::from_str(&modifier).unwrap(), 1);
|
||||
}
|
||||
modifiers.axis.insert(modmap, map.clone());
|
||||
}
|
||||
|
||||
let mut pad_horizontal: Vec<Key> = bindings.axis.get("BTN_DPAD_LEFT")
|
||||
.unwrap_or(&Vec::new()).clone();
|
||||
pad_horizontal.extend(bindings.axis.get("BTN_DPAD_RIGHT")
|
||||
.unwrap_or(&Vec::new()));
|
||||
let mut pad_vertical: Vec<Key> = bindings.axis.get("BTN_DPAD_UP")
|
||||
.unwrap_or(&Vec::new()).clone();
|
||||
pad_vertical.extend(bindings.axis.get("BTN_DPAD_DOWN")
|
||||
.unwrap_or(&Vec::new()));
|
||||
bindings.axis.insert("NONE_X".to_string(), pad_horizontal);
|
||||
bindings.axis.insert("NONE_Y".to_string(), pad_vertical);
|
||||
|
||||
Self {
|
||||
name: file_name,
|
||||
keys: keys,
|
||||
bindings: bindings,
|
||||
combinations: combinations,
|
||||
modifiers: modifiers,
|
||||
settings: settings,
|
||||
abs: abs,
|
||||
rel: rel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::HashMap, sync::Arc, option::Option, env};
|
||||
use std::{collections::{HashMap, BTreeMap}, sync::Arc, option::Option};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_stream::StreamExt;
|
||||
use evdev::{EventStream, Key, RelativeAxisType, AbsoluteAxisType, EventType, InputEvent};
|
||||
|
@ -13,37 +13,29 @@ pub struct EventReader {
|
|||
stream: Arc<Mutex<EventStream>>,
|
||||
virt_dev: Arc<Mutex<VirtualDevices>>,
|
||||
analog_position: Arc<Mutex<Vec<i32>>>,
|
||||
modifiers: Arc<Mutex<BTreeMap<Key, i32>>>,
|
||||
device_is_connected: Arc<Mutex<bool>>,
|
||||
current_desktop: Option<String>,
|
||||
}
|
||||
|
||||
impl EventReader {
|
||||
pub fn new(config: HashMap<String, Config>, stream: Arc<Mutex<EventStream>>, virt_dev: Arc<Mutex<VirtualDevices>>) -> Self {
|
||||
pub fn new(
|
||||
config: HashMap<String, Config>,
|
||||
stream: Arc<Mutex<EventStream>>,
|
||||
virt_dev: Arc<Mutex<VirtualDevices>>,
|
||||
modifiers: Arc<Mutex<BTreeMap<Key, i32>>>,
|
||||
current_desktop: Option<String>,
|
||||
) -> Self {
|
||||
let mut position_vector: Vec<i32> = Vec::new();
|
||||
for i in [0, 0] {position_vector.push(i)};
|
||||
let position_vector_mutex = Arc::new(Mutex::new(position_vector));
|
||||
let device_is_connected: Arc<Mutex<bool>> = Arc::new(Mutex::new(true));
|
||||
let current_desktop: Option<String> = match env::var("XDG_CURRENT_DESKTOP") {
|
||||
Ok(desktop) if vec!["Hyprland".to_string(), "sway".to_string()].contains(&desktop) => {
|
||||
println!("Running on {}, active window detection enabled.", desktop);
|
||||
Option::Some(desktop)
|
||||
},
|
||||
Ok(desktop) => {
|
||||
println!("Unsupported desktop: {}, won't be able to change bindings according to active window.\n
|
||||
Currently supported desktops: Hyprland", desktop);
|
||||
Option::None
|
||||
},
|
||||
Err(_) => {
|
||||
println!("Unable to retrieve current desktop based on XDG_CURRENT_DESKTOP env var.\n
|
||||
Won't be able to change bindings according to active window.");
|
||||
Option::None
|
||||
},
|
||||
};
|
||||
Self {
|
||||
config: config,
|
||||
stream: stream,
|
||||
virt_dev: virt_dev,
|
||||
analog_position: position_vector_mutex,
|
||||
modifiers: modifiers,
|
||||
device_is_connected: device_is_connected,
|
||||
current_desktop: current_desktop,
|
||||
}
|
||||
|
@ -62,97 +54,118 @@ impl EventReader {
|
|||
while let Some(Ok(event)) = stream.next().await {
|
||||
match (event.event_type(), RelativeAxisType(event.code()), AbsoluteAxisType(event.code()), analog_mode) {
|
||||
(EventType::KEY, _, _, _) => {
|
||||
if let Some(event_list) = self.config.get(&self.get_active_window().await).unwrap().keys.get(&Key(event.code())) {
|
||||
self.emit_event(event_list, event.value()).await
|
||||
} else {
|
||||
self.emit_default_event(event).await;
|
||||
}
|
||||
self.convert_key_events(event).await;
|
||||
},
|
||||
(_, RelativeAxisType::REL_WHEEL | RelativeAxisType::REL_WHEEL_HI_RES, _, _) => {
|
||||
let event_list_option: Option<&Vec<Key>> = match event.value() {
|
||||
-1 => self.config.get(&self.get_active_window().await).unwrap().rel.get(&"SCROLL_WHEEL_DOWN".to_string()),
|
||||
1 => self.config.get(&self.get_active_window().await).unwrap().rel.get(&"SCROLL_WHEEL_UP".to_string()),
|
||||
_ => None,
|
||||
let event_string_option: Option<String> = match event.value() {
|
||||
-1 => Option::Some("SCROLL_WHEEL_DOWN".to_string()),
|
||||
1 => Option::Some("SCROLL_WHEEL_UP".to_string()),
|
||||
_ => Option::None,
|
||||
};
|
||||
if let Some(event_list) = event_list_option {
|
||||
self.emit_event(event_list, event.value()).await;
|
||||
self.emit_event(event_list, 0).await;
|
||||
if let Some(event_string) = event_string_option {
|
||||
self.convert_axis_events(event, &event_string, true, false).await;
|
||||
} else {
|
||||
if !self.config.get(&self.get_active_window().await).unwrap().rel.contains_key("SCROLL_WHEEL_DOWN")
|
||||
&& !self.config.get(&self.get_active_window().await).unwrap().rel.contains_key("SCROLL_WHEEL_UP") {
|
||||
self.emit_default_event(event).await;
|
||||
}
|
||||
}
|
||||
},
|
||||
(_, _, AbsoluteAxisType::ABS_HAT0X, _) => {
|
||||
let event_list_option: Option<&Vec<Key>> = match event.value() {
|
||||
-1 => self.config.get(&self.get_active_window().await).unwrap().keys.get(&Key::BTN_DPAD_LEFT),
|
||||
0 => self.config.get(&self.get_active_window().await).unwrap().abs.get(&"NONE_X".to_string()),
|
||||
1 => self.config.get(&self.get_active_window().await).unwrap().keys.get(&Key::BTN_DPAD_RIGHT),
|
||||
_ => self.config.get(&self.get_active_window().await).unwrap().abs.get(&"NONE_X".to_string()),
|
||||
let event_string: String = match event.value() {
|
||||
-1 => "BTN_DPAD_LEFT".to_string(),
|
||||
0 => "NONE_X".to_string(),
|
||||
1 => "BTN_DPAD_RIGHT".to_string(),
|
||||
_ => "NONE_X".to_string(),
|
||||
};
|
||||
if let Some(event_list) = event_list_option {
|
||||
self.emit_event(event_list, event.value()).await;
|
||||
} else {
|
||||
println!("Button not set in the config file!");
|
||||
}
|
||||
self.convert_axis_events(event, &event_string, false, false).await;
|
||||
},
|
||||
(_, _, AbsoluteAxisType::ABS_HAT0Y, _) => {
|
||||
let event_list_option: Option<&Vec<Key>> = match event.value() {
|
||||
-1 => self.config.get(&self.get_active_window().await).unwrap().keys.get(&Key::BTN_DPAD_UP),
|
||||
0 => self.config.get(&self.get_active_window().await).unwrap().abs.get(&"NONE_Y".to_string()),
|
||||
1 => self.config.get(&self.get_active_window().await).unwrap().keys.get(&Key::BTN_DPAD_DOWN),
|
||||
_ => self.config.get(&self.get_active_window().await).unwrap().abs.get(&"NONE_Y".to_string()),
|
||||
let event_string: String = match event.value() {
|
||||
-1 => "BTN_DPAD_UP".to_string(),
|
||||
0 => "NONE_Y".to_string(),
|
||||
1 => "BTN_DPAD_DOWN".to_string(),
|
||||
_ => "NONE_Y".to_string(),
|
||||
};
|
||||
if let Some(event_list) = event_list_option {
|
||||
self.emit_event(event_list, event.value()).await;
|
||||
} else {
|
||||
println!("Button not set in the config file!");
|
||||
}
|
||||
self.convert_axis_events(event, &event_string, false, false).await;
|
||||
},
|
||||
(EventType::ABSOLUTE, _, AbsoluteAxisType::ABS_X | AbsoluteAxisType::ABS_Y, "left") => {
|
||||
let rel_value = self.get_rel_value(&has_signed_axis_value, &event).await;
|
||||
let axis_value = self.get_axis_value(&has_signed_axis_value, &event).await;
|
||||
let mut analog_position = self.analog_position.lock().await;
|
||||
analog_position[event.code() as usize] = rel_value;
|
||||
analog_position[event.code() as usize] = axis_value;
|
||||
},
|
||||
(EventType::ABSOLUTE, _, AbsoluteAxisType::ABS_RX | AbsoluteAxisType::ABS_RY, "right") => {
|
||||
let rel_value = self.get_rel_value(&has_signed_axis_value, &event).await;
|
||||
let axis_value = self.get_axis_value(&has_signed_axis_value, &event).await;
|
||||
let mut analog_position = self.analog_position.lock().await;
|
||||
analog_position[(event.code() as usize) -3] = rel_value;
|
||||
analog_position[(event.code() as usize) -3] = axis_value;
|
||||
},
|
||||
(EventType::ABSOLUTE, _, AbsoluteAxisType::ABS_Z, _) => {
|
||||
if let Some(event_list) = self.config.get(&self.get_active_window().await).unwrap().keys.get(&Key::BTN_TL2) {
|
||||
if event.value() == 0 {
|
||||
self.emit_event(event_list, event.value()).await
|
||||
} else {
|
||||
self.emit_event(event_list, 1).await
|
||||
};
|
||||
} else {
|
||||
println!("Button not set in the config file!");
|
||||
};
|
||||
self.convert_axis_events(event, &"BTN_TL2".to_string(), false, true).await;
|
||||
},
|
||||
(EventType::ABSOLUTE, _, AbsoluteAxisType::ABS_RZ, _) => {
|
||||
if let Some(event_list) = self.config.get(&self.get_active_window().await).unwrap().keys.get(&Key::BTN_TR2) {
|
||||
if event.value() == 0 {
|
||||
self.emit_event(event_list, event.value()).await
|
||||
} else {
|
||||
self.emit_event(event_list, 1).await
|
||||
};
|
||||
} else {
|
||||
println!("Button not set in the config file!");
|
||||
};
|
||||
self.convert_axis_events(event, &"BTN_TR2".to_string(), false, true).await;
|
||||
},
|
||||
_ => {self.emit_default_event(event).await}
|
||||
_ => {self.emit_default_event(event).await;}
|
||||
}
|
||||
}
|
||||
let mut device_is_connected = self.device_is_connected.lock().await;
|
||||
*device_is_connected = false;
|
||||
}
|
||||
|
||||
async fn convert_key_events(&self, event: InputEvent) {
|
||||
let path = self.config.get(&self.get_active_window().await).unwrap();
|
||||
let modifiers = self.modifiers.lock().await.clone();
|
||||
if let Some(event_hashmap) = path.modifiers.keys.get(&modifiers) {
|
||||
if let Some(event_list) = event_hashmap.get(&Key(event.code())) {
|
||||
self.emit_event_without_modifiers(event_list, &modifiers, event.value()).await;
|
||||
return
|
||||
}
|
||||
}
|
||||
if let Some(event_list) = path.bindings.keys.get(&Key(event.code())) {
|
||||
self.emit_event(event_list, event.value()).await;
|
||||
} else {
|
||||
self.emit_default_event(event).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn convert_axis_events(&self, event: InputEvent, event_string: &String, send_zero: bool, clamp_value: bool) {
|
||||
let path = self.config.get(&self.get_active_window().await).unwrap();
|
||||
let modifiers = self.modifiers.lock().await.clone();
|
||||
let value = {
|
||||
if clamp_value == true && event.value() > 1 {
|
||||
1
|
||||
} else {
|
||||
event.value()
|
||||
}
|
||||
};
|
||||
if let Some(event_hashmap) = path.modifiers.axis.get(&modifiers) {
|
||||
if let Some(event_list) = event_hashmap.get(event_string) {
|
||||
self.emit_event_without_modifiers(event_list, &modifiers, value).await;
|
||||
if send_zero {
|
||||
self.emit_event_without_modifiers(event_list, &modifiers, 0).await;
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
if let Some(event_list) = path.bindings.axis.get(event_string) {
|
||||
self.emit_event(event_list, value).await;
|
||||
if send_zero {
|
||||
self.emit_event_without_modifiers(event_list, &modifiers, 0).await;
|
||||
}
|
||||
} else {
|
||||
self.emit_default_event(event).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn emit_event(&self, event_list: &Vec<Key>, value: i32) {
|
||||
for key in event_list {
|
||||
let virtual_event: InputEvent = InputEvent::new_now(EventType::KEY, key.code(), value);
|
||||
let mut virt_dev = self.virt_dev.lock().await;
|
||||
let modifiers = self.modifiers.lock().await.clone();
|
||||
let released_keys: Vec<Key> = self.released_keys(&modifiers).await;
|
||||
for key in released_keys {
|
||||
self.toggle_modifiers(key, 0).await;
|
||||
let virtual_event: InputEvent = InputEvent::new_now(EventType::KEY, key.code(), 0);
|
||||
virt_dev.keys.emit(&[virtual_event]).unwrap();
|
||||
}
|
||||
for key in event_list {
|
||||
self.toggle_modifiers(*key, value).await;
|
||||
let virtual_event: InputEvent = InputEvent::new_now(EventType::KEY, key.code(), value);
|
||||
virt_dev.keys.emit(&[virtual_event]).unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -160,13 +173,40 @@ impl EventReader {
|
|||
async fn emit_default_event(&self, event: InputEvent) {
|
||||
let mut virt_dev = self.virt_dev.lock().await;
|
||||
match event.event_type() {
|
||||
EventType::KEY => virt_dev.keys.emit(&[event]).unwrap(),
|
||||
EventType::KEY => {
|
||||
let modifiers = self.modifiers.lock().await.clone();
|
||||
let released_keys: Vec<Key> = self.released_keys(&modifiers).await;
|
||||
for key in released_keys {
|
||||
self.toggle_modifiers(key, 0).await;
|
||||
let virtual_event: InputEvent = InputEvent::new_now(EventType::KEY, key.code(), 0);
|
||||
virt_dev.keys.emit(&[virtual_event]).unwrap()
|
||||
}
|
||||
self.toggle_modifiers(Key(event.code()), event.value()).await;
|
||||
virt_dev.keys.emit(&[event]).unwrap();
|
||||
},
|
||||
EventType::RELATIVE => virt_dev.relative_axes.emit(&[event]).unwrap(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_rel_value(&self, has_signed_axis_value: &str, event: &InputEvent) -> i32 {
|
||||
async fn emit_event_without_modifiers(&self, event_list: &Vec<Key>, modifiers: &BTreeMap<Key, i32>, value: i32) {
|
||||
let modifiers_list = modifiers.iter()
|
||||
.filter(|(_key, value)| value == &&1)
|
||||
.collect::<HashMap<&Key, &i32>>()
|
||||
.into_keys().copied()
|
||||
.collect::<Vec<Key>>();
|
||||
let mut virt_dev = self.virt_dev.lock().await;
|
||||
for key in modifiers_list {
|
||||
let virtual_event: InputEvent = InputEvent::new_now(EventType::KEY, key.code(), 0);
|
||||
virt_dev.keys.emit(&[virtual_event]).unwrap();
|
||||
}
|
||||
for key in event_list {
|
||||
let virtual_event: InputEvent = InputEvent::new_now(EventType::KEY, key.code(), value);
|
||||
virt_dev.keys.emit(&[virtual_event]).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_axis_value(&self, has_signed_axis_value: &str, event: &InputEvent) -> i32 {
|
||||
let rel_value: i32 = match &has_signed_axis_value {
|
||||
&"false" => {
|
||||
let distance_from_center: i32 = event.value() as i32 - 128;
|
||||
|
@ -179,6 +219,25 @@ impl EventReader {
|
|||
return rel_value
|
||||
}
|
||||
|
||||
async fn toggle_modifiers(&self, key: Key, value: i32) {
|
||||
let mut modifiers = self.modifiers.lock().await;
|
||||
if modifiers.contains_key(&key) && vec![0, 1].contains(&value) {
|
||||
modifiers.insert(key, value).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
async fn released_keys(&self, modifiers: &BTreeMap<Key, i32>) -> Vec<Key> {
|
||||
let path = self.config.get(&self.get_active_window().await).unwrap();
|
||||
let mut released_keys: Vec<Key> = Vec::new();
|
||||
if let Some(event_hashmap) = path.modifiers.keys.get(&modifiers) {
|
||||
event_hashmap.iter().for_each(|(_modifiers, event_list)| released_keys.extend(event_list));
|
||||
}
|
||||
if let Some(event_hashmap) = path.modifiers.axis.get(&modifiers) {
|
||||
event_hashmap.iter().for_each(|(_modifiers, event_list)| released_keys.extend(event_list));
|
||||
}
|
||||
released_keys
|
||||
}
|
||||
|
||||
pub async fn cursor_loop(&self) {
|
||||
if let Some(sensitivity) = self.config.get(&self.get_active_window().await).unwrap().settings.get("ANALOG_SENSITIVITY") {
|
||||
let polling_rate: u64 = sensitivity.parse::<u64>().expect("Invalid analog sensitivity.");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::HashMap, sync::Arc, path::Path};
|
||||
use std::{collections::{HashMap, BTreeMap}, sync::Arc, path::Path, env};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_stream::StreamExt;
|
||||
|
@ -28,13 +28,42 @@ pub async fn start_monitoring_udev(config_files: Vec<Config>, mut tasks: Vec<Joi
|
|||
}
|
||||
|
||||
pub fn launch_tasks(config_files: &Vec<Config>, tasks: &mut Vec<JoinHandle<()>>) {
|
||||
let modifiers: Arc<Mutex<BTreeMap<Key, i32>>> = Arc::new (
|
||||
Mutex::new (
|
||||
BTreeMap::from ([
|
||||
(Key::KEY_LEFTSHIFT, 0),
|
||||
(Key::KEY_LEFTCTRL, 0),
|
||||
(Key::KEY_LEFTALT, 0),
|
||||
(Key::KEY_RIGHTSHIFT, 0),
|
||||
(Key::KEY_RIGHTCTRL, 0),
|
||||
(Key::KEY_RIGHTALT, 0),
|
||||
(Key::KEY_LEFTMETA, 0)
|
||||
])
|
||||
)
|
||||
);
|
||||
let current_desktop: Option<String> = match env::var("XDG_CURRENT_DESKTOP") {
|
||||
Ok(desktop) if vec!["Hyprland".to_string(), "sway".to_string()].contains(&desktop) => {
|
||||
println!("Running on {}, active window detection enabled.", desktop);
|
||||
Option::Some(desktop)
|
||||
},
|
||||
Ok(desktop) => {
|
||||
println!("Unsupported desktop: {}, won't be able to change bindings according to active window.\n
|
||||
Currently supported desktops: Hyprland, Sway.", desktop);
|
||||
Option::None
|
||||
},
|
||||
Err(_) => {
|
||||
println!("Unable to retrieve the current desktop based on XDG_CURRENT_DESKTOP env var.\n
|
||||
Won't be able to change bindings according to the active window.");
|
||||
Option::None
|
||||
},
|
||||
};
|
||||
let devices: evdev::EnumerateDevices = evdev::enumerate();
|
||||
for device in devices {
|
||||
let mut config_map: HashMap<String, Config> = HashMap::new();
|
||||
for config in config_files {
|
||||
let split_config_name = config.name.split("::").collect::<Vec<&str>>();
|
||||
let associated_device_name = split_config_name[0];
|
||||
if associated_device_name == device.1.name().unwrap() {
|
||||
if associated_device_name == device.1.name().unwrap().replace("/", "") {
|
||||
let window_class = if split_config_name.len() == 1 {
|
||||
String::from("default")
|
||||
} else {
|
||||
|
@ -48,7 +77,9 @@ pub fn launch_tasks(config_files: &Vec<Config>, tasks: &mut Vec<JoinHandle<()>>)
|
|||
tokio::spawn(
|
||||
create_new_reader(
|
||||
device.0.as_path().to_str().unwrap().to_string(),
|
||||
config_map.clone()
|
||||
config_map.clone(),
|
||||
modifiers.clone(),
|
||||
current_desktop.clone(),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -56,7 +87,7 @@ pub fn launch_tasks(config_files: &Vec<Config>, tasks: &mut Vec<JoinHandle<()>>)
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn create_new_reader(device: String, config: HashMap<String, Config>) {
|
||||
pub async fn create_new_reader(device: String, config: HashMap<String, Config>, modifiers: Arc<Mutex<BTreeMap<Key, i32>>>, current_desktop: Option<String>) {
|
||||
let stream: Arc<Mutex<EventStream>> = Arc::new (
|
||||
Mutex::new (
|
||||
get_event_stream (
|
||||
|
@ -68,8 +99,8 @@ pub async fn create_new_reader(device: String, config: HashMap<String, Config>)
|
|||
let virt_dev: Arc<Mutex<VirtualDevices>> = Arc::new (
|
||||
Mutex::new(new_virtual_devices())
|
||||
);
|
||||
let reader = EventReader::new(config.clone(), stream, virt_dev);
|
||||
println!("Mapped device detected at {}, reading events.", device);
|
||||
let reader = EventReader::new(config.clone(), stream, virt_dev, modifiers, current_desktop);
|
||||
println!("Mapped device detected at {:?}, reading events.", device);
|
||||
tokio::join!(
|
||||
reader.start(),
|
||||
reader.cursor_loop(),
|
||||
|
|
Loading…
Add table
Reference in a new issue