-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 08d0ee4
Showing
2 changed files
with
163 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# Pedal Mapper | ||
|
||
This is a simple program for mapping the buttons on an Elgato Stream Deck Pedal | ||
to keyboard inputs. I use a pedal for push-to-talk when I play games with a | ||
controller, and there wasn't anything that let me do that easily with the Elgato | ||
Stream Deck Pedal. | ||
|
||
## Dependencies | ||
|
||
There aren't a lot of dependencies, but what you do need are: | ||
- Python | ||
- hidapi | ||
- evdev | ||
|
||
## How it works | ||
|
||
The actual flow is really simple. You just configure three lists of key presses, | ||
one for each button on the pedal, and then run the program. The program then | ||
listens for button presses and sends the mapped keys to `/dev/uinput`. | ||
|
||
Run the Python script and press your pedal. You can run it as a `systemd` unit | ||
or equivalent to have it always running in the background. | ||
|
||
You can find a list of all possible keycodes | ||
[here](https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h). | ||
|
||
## Possible issues | ||
|
||
If you're using Wayland it won't work if your voice chat client isn't focused. | ||
However, if you have a means of forwarding keystrokes to Xwayland applications | ||
and your client is running in Xwayland, then it will work fine. KDE has this | ||
built-in and that's what I use. There are other solutions like [this | ||
script](https://github.com/Rush/wayland-push-to-talk-fix/) for forwarding | ||
keystrokes. | ||
|
||
There may be other issues. I haven't really run into any myself yet, but you | ||
never know. | ||
|
||
## Possible additions | ||
|
||
This was just my first pass at implementing this specifically for my needs. The | ||
code sucks, is basic as hell, and isn't very configurable. In no particular | ||
order, things that I may implement in the future if I feel inspired are: | ||
- Reading configuration from a file rather than being hardcoded into the | ||
program. | ||
- Configuring other types of devices. | ||
[input-remapper](https://github.com/sezanzeb/input-remapper) is already a good | ||
general purpose solution, but it's possible there are unsupported devices for | ||
which people might want a similar solution to what is here. | ||
- More advanced key combinations / macros. Right now the program just handles | ||
basic hold keys and press keys. Adding something like callbacks to associate | ||
with buttons could be neat, with a default one being how it works right now. | ||
- Profiles/profile-switching would be neat. Being able to switch stuff on the | ||
fly would be handy if someone wanted to use different bindings for different | ||
situations. | ||
|
||
These are all highly aspirational and I don't actually need any of these myself | ||
currently. I'm lazy and selfish so it's actually unlikely I'll implement these, | ||
but they're here as ideas in case anyone else wants to contribute. | ||
|
||
## Contributing | ||
|
||
Feel free to submit merge requests. I'm not too scary and would happily accept | ||
contributions. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
from collections import namedtuple | ||
from enum import Enum | ||
from evdev import UInput, ecodes as e | ||
import hid | ||
|
||
# Elgato Stream Deck Pedal Vendor / Product IDs | ||
VENDOR_ID = 0x0FD9 | ||
PRODUCT_ID = 0x0086 | ||
|
||
|
||
# Represents a key combination of mods, which are held for the whole | ||
# combination, and keys, which are pressed and released. | ||
KeyCombo = namedtuple("KeyCombo", ["mods", "keys"], defaults=([], [])) | ||
|
||
|
||
# Simple enum that represents the three buttons. | ||
class Button(Enum): | ||
LEFT = 0 | ||
MIDDLE = 1 | ||
RIGHT = 2 | ||
|
||
|
||
class PedalMapper: | ||
def __init__(self, left_keys=[], middle_keys=[], right_keys=[], polling_rate=10): | ||
self.button_key_mappings = (left_keys, middle_keys, right_keys) | ||
self.button_state = [False, False, False] | ||
self.polling_rate = polling_rate | ||
self.dev = hid.device() | ||
self.dev.open(VENDOR_ID, PRODUCT_ID) | ||
self.dev.set_nonblocking(1) | ||
|
||
# Register keys in UInput capabilities. | ||
reg_keys = set() | ||
for key_combos in self.button_key_mappings: | ||
for combo in key_combos: | ||
reg_keys.update(combo.mods) | ||
reg_keys.update(combo.keys) | ||
cap = {e.EV_KEY: reg_keys} | ||
self.ui = UInput(cap, name="Pedal Mapper Virtual Input") | ||
|
||
def get_event(self): | ||
# We're non-blocking, so wait for a poll. This could just be changed to | ||
# blocking instead without issues, but it is non-blocking in case it's | ||
# actually needed in the future (e.g. handling multiple devices). | ||
read = self.dev.read(8, self.polling_rate) | ||
if not read: | ||
return | ||
button = None | ||
if read[4] != self.button_state[0]: | ||
button = Button.LEFT | ||
elif read[5] != self.button_state[1]: | ||
button = Button.MIDDLE | ||
elif read[6] != self.button_state[2]: | ||
button = Button.RIGHT | ||
else: | ||
return | ||
self.button_state[button.value] = not self.button_state[button.value] | ||
return button, self.button_state[button.value] | ||
|
||
def handle_key(self, button, state): | ||
key_combos = self.button_key_mappings[button.value] | ||
for combo in key_combos: | ||
# Press mods first | ||
if state: | ||
for mod in combo.mods: | ||
self.write_key(mod, state) | ||
for key in combo.keys: | ||
self.write_key(key, state) | ||
# Release mods last | ||
else: | ||
for key in combo.keys: | ||
self.write_key(key, state) | ||
for mod in combo.mods: | ||
self.write_key(mod, state) | ||
|
||
# Writes a key to uinput and then syncs. | ||
def write_key(self, key, state): | ||
self.ui.write(e.EV_KEY, key, state) | ||
self.ui.syn() | ||
|
||
|
||
if __name__ == "__main__": | ||
# Create Pedal | ||
# Example mappings: | ||
# Left: no-op | ||
# Middle: right meta + F13 | ||
# Right: shift + right meta + F13 | ||
pm = PedalMapper( | ||
middle_keys=[KeyCombo(mods=[e.KEY_RIGHTMETA], keys=[e.KEY_F13])], | ||
right_keys=[ | ||
KeyCombo(mods=[e.KEY_RIGHTSHIFT, e.KEY_RIGHTMETA], keys=[e.KEY_F13]) | ||
], | ||
) | ||
|
||
# Loop to get events and handle them accordingly. | ||
while True: | ||
ev = pm.get_event() | ||
if ev: | ||
pm.handle_key(ev[0], ev[1]) |