From 535e8c5a7246f5aeea632434124434f9baf076f9 Mon Sep 17 00:00:00 2001 From: Kuba Orlik Date: Sat, 18 Jul 2026 15:52:39 +0200 Subject: [PATCH] Add cec-remote scripts --- cec-remote/cec-remote.service | 14 ---- cec-remote/cec_receiver.py | 127 ++++++++++++++++++++++++++++++++++ cec-remote/list_caps.py | 13 ++++ cec-remote/setup.sh | 17 ++++- cec-remote/tick.py | 32 +++++++++ 5 files changed, 188 insertions(+), 15 deletions(-) delete mode 100644 cec-remote/cec-remote.service create mode 100644 cec-remote/cec_receiver.py create mode 100644 cec-remote/list_caps.py create mode 100644 cec-remote/tick.py diff --git a/cec-remote/cec-remote.service b/cec-remote/cec-remote.service deleted file mode 100644 index a8fdd62..0000000 --- a/cec-remote/cec-remote.service +++ /dev/null @@ -1,14 +0,0 @@ -[Unit] -Description=CEC Receiver Service -After=network.target - -[Service] -ExecStart=/usr/bin/python3 /home/kuba/cec-remote/cec_receiver.py -WorkingDirectory=/home/kuba/cec-remote -Restart=on-failure -RestartSec=5 -User=kuba -Environment=PYTHONUNBUFFERED=1 - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/cec-remote/cec_receiver.py b/cec-remote/cec_receiver.py new file mode 100644 index 0000000..f6f4889 --- /dev/null +++ b/cec-remote/cec_receiver.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +import socket +import json +from evdev import UInput, ecodes as e + +# Capabilities for both gamepad and keyboard +capabilities = { + e.EV_KEY: [ + e.BTN_A, e.BTN_B, e.BTN_START, + e.BTN_DPAD_UP, e.BTN_DPAD_DOWN, e.BTN_DPAD_LEFT, e.BTN_DPAD_RIGHT, + e.KEY_LEFTCTRL, e.KEY_1 + ] +} + +# Mapping CEC keys to gamepad buttons or keyboard keys +key_map = { + 'SELECT': e.BTN_A, + 'EXIT': e.BTN_B, + 'PLAY': e.BTN_START, + 'PAUSE': e.BTN_START, + 'UP': e.BTN_DPAD_UP, + 'DOWN': e.BTN_DPAD_DOWN, + 'LEFT': e.BTN_DPAD_LEFT, + 'RIGHT': e.BTN_DPAD_RIGHT + # 'CLEAR' handled separately +} + + +import time +from evdev import InputDevice, ecodes, list_devices, UInput + +# Define key codes for Ctrl and 1 +SHIFT = ecodes.KEY_LEFTSHIFT +TAB = ecodes.KEY_TAB + +# Define virtual keyboard capabilities (DO NOT include EV_SYN) +keyboard_events = { + ecodes.EV_KEY: [SHIFT,TAB], +} + +# Create the virtual input device +keyboard_ui = UInput(events=keyboard_events, name="Virtual Ctrl+1 Injector", bustype=ecodes.BUS_USB) + + +def home(): + print("Sending shift+home") + keyboard_ui.write(ecodes.EV_KEY, SHIFT, 1) # Press Ctrl + keyboard_ui.write(ecodes.EV_KEY, TAB, 1) # Press 1 + keyboard_ui.syn() + time.sleep(0.1) + keyboard_ui.write(ecodes.EV_KEY, SHIFT, 0) # Release 1 + keyboard_ui.write(ecodes.EV_KEY, TAB, 0) # Release Ctrl + keyboard_ui.syn() + + +last_pressed = None # Track last key to release + +def create_virtual_device(): + return UInput(capabilities, name='CEC Virtual Input', bustype=e.BUS_USB) + +def handle_press(ui, key): + global last_pressed + + if key == 'CLEAR': + home() + return + + code = key_map.get(key) + if not code: + print(f"Unknown key: {key}") + return + + if code == last_pressed: + return + + handle_release(ui) + ui.write(e.EV_KEY, code, 1) + ui.syn() + last_pressed = code + print(f"Pressed {key} ({code})") + +def handle_release(ui): + global last_pressed + if last_pressed is None: + return + + if isinstance(last_pressed, tuple): + # Release both Ctrl and 1 + for code in last_pressed: + ui.write(e.EV_KEY, code, 0) + print("Released Ctrl+1") + else: + ui.write(e.EV_KEY, last_pressed, 0) + print(f"Released {last_pressed}") + + ui.syn() + last_pressed = None + +def start_udp_listener(): + ui = create_virtual_device() + print("Virtual input device ready") + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.bind(('0.0.0.0', 41234)) + print("Listening for UDP input...") + + while True: + data, _ = sock.recvfrom(1024) + try: + print("new payload") + payload = json.loads(data.decode()) + if payload['type'] == 'key': + event_type = payload.get('event') + key_name = payload.get('key', '').upper() + + if event_type == 'press': + handle_press(ui, key_name) + elif event_type == 'release': + handle_release(ui) + except Exception as ex: + print(f"Error: {ex}") + +if __name__ == "__main__": + try: + start_udp_listener() + except KeyboardInterrupt: + print("Shutting down...") diff --git a/cec-remote/list_caps.py b/cec-remote/list_caps.py new file mode 100644 index 0000000..2172209 --- /dev/null +++ b/cec-remote/list_caps.py @@ -0,0 +1,13 @@ +import libevdev +import sys + +device_path = sys.argv[1] + +with open(device_path, 'rb') as f: + d = libevdev.Device(f) + print(f"Device: {d.name}") + print("Capabilities:") + for event_type, codes in d.capabilities().items(): + print(f" {event_type}:") + for code in codes: + print(f" {code}") diff --git a/cec-remote/setup.sh b/cec-remote/setup.sh index 93f842c..d2c531c 100755 --- a/cec-remote/setup.sh +++ b/cec-remote/setup.sh @@ -3,4 +3,19 @@ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" echo $SCRIPT_DIR -sudo cp $SCRIPT_DIR/cec-remote.service /etc/systemd/system/cec-remote.service +cat | sudo tee /etc/systemd/system/cec-remote.service <