From 39bd8150e1fe70a04a4e276a47e1de7604b13ee9 Mon Sep 17 00:00:00 2001 From: Simon Diesenreiter Date: Thu, 30 Oct 2025 17:19:06 +0100 Subject: [PATCH] feat: initial implementation, refs NOISSUE --- requirements.txt | 1 + usev_device_access_permissions.sh | 8 +++ volume-remapper.service | 9 ++++ volume_remapper.py | 82 +++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 requirements.txt create mode 100644 usev_device_access_permissions.sh create mode 100644 volume-remapper.service create mode 100755 volume_remapper.py diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ac050d0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +evdev \ No newline at end of file diff --git a/usev_device_access_permissions.sh b/usev_device_access_permissions.sh new file mode 100644 index 0000000..fd47773 --- /dev/null +++ b/usev_device_access_permissions.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +sudo tee /etc/udev/rules.d/99-volume-key.rules > /dev/null <<'EOF' +# Grant read permission for the Volume‑Up key device +SUBSYSTEM=="input", ATTRS{name}=="*volume*", MODE="0660", GROUP="input" +EOF +# Replace *volume* with a substring that matches your device’s name (e.g. “Logitech”) if the rule above doesn’t apply. +sudo udevadm control --reload \ No newline at end of file diff --git a/volume-remapper.service b/volume-remapper.service new file mode 100644 index 0000000..310d368 --- /dev/null +++ b/volume-remapper.service @@ -0,0 +1,9 @@ +[Unit] +Description=Volume‑up key remapper + +[Service] +ExecStart=/path/to/volume_remapper.py +Restart=always + +[Install] +WantedBy=default.target \ No newline at end of file diff --git a/volume_remapper.py b/volume_remapper.py new file mode 100755 index 0000000..41f88ee --- /dev/null +++ b/volume_remapper.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +""" +volume_remapper.py +------------------ +Listen for Volume‑Up key events and emit a different key code +(“Ctrl+Shift+M” by default) so the focused application receives it. + +Dependencies: python‑evdev (pip install evdev) +""" + +import sys +import os +import argparse +from evdev import InputDevice, categorize, ecodes, UInput, list_devices +import time + +# ------------------------------------------------------------------ # +# Helpers +# ------------------------------------------------------------------ # + +def find_device(name_substr="volume"): + """Return the first InputDevice that contains a KEY_VOLUMEUP event.""" + for dev_path in [d.path for d in list_devices()]: + dev = InputDevice(dev_path) + caps = dev.capabilities() + if ecodes.EV_KEY in caps and ecodes.KEY_VOLUMEUP in caps[ecodes.EV_KEY]: + if name_substr.lower() in dev.name.lower(): + return dev + return None + +# ------------------------------------------------------------------ # +# Core logic +# ------------------------------------------------------------------ # + +def main(remap_keys, device=None): + """ + :param remap_keys: tuple of ecodes.KEY_* codes to emit (e.g. (ecodes.KEY_LCTRL, ecodes.KEY_LSHIFT, ecodes.KEY_M)) + :param device: path to /dev/input/eventX or None to auto‑detect + """ + if device is None: + dev = find_device() + if dev is None: + print("⚠️ No device with a Volume‑Up key found.", file=sys.stderr) + sys.exit(1) + else: + dev = InputDevice(device) + + print(f"[INFO] Listening on {dev.path} ({dev.name}) ...") + print(f"[INFO] Remapping to: {', '.join(ecodes.KEY.get(k) for k in remap_keys)}") + + # Create a virtual keyboard (uinput) + ui = UInput(events=[ + ecodes.EV_KEY, # only key events + ecodes.EV_SYN + ], name='volume‑remapper') + + try: + for event in dev.read_loop(): + if event.type == ecodes.EV_KEY: + if event.code == ecodes.KEY_VOLUMEUP and event.value == 1: # key_down + # Emit the mapped key sequence + for key in remap_keys: + ui.write(ecodes.EV_KEY, key, 1) # key down + ui.write(ecodes.EV_KEY, key, 0) # key up + ui.syn() + print(f"✅ Mapped Volume‑Up → {', '.join(ecodes.KEY.get(k) for k in remap_keys)}") + except KeyboardInterrupt: + print("\n[INFO] Stopping …") + finally: + ui.close() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Remap Volume‑Up to any key combo.") + parser.add_argument("--device", help="Optional /dev/input/eventX path to listen on.") + parser.add_argument("--to", nargs="+", default=["KEY_LCTRL", "KEY_LSHIFT", "KEY_M"], + help="Space‑separated key names to emit. Default: Ctrl+Shift+M") + args = parser.parse_args() + + # Convert key names to ecodes + remap = [ecodes.KEY[code.upper()] for code in args.to] + main(remap_keys=remap, device=args.device) \ No newline at end of file