commit a8db1ba2b32c81e2450e759b9455f94e99dac279 Author: Brad Stein Date: Sun Jun 1 04:41:59 2025 -0500 first commit diff --git a/install-navka-relay.sh b/install-navka-relay.sh new file mode 100755 index 0000000..73714c8 --- /dev/null +++ b/install-navka-relay.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ---------- paths ---------- +SRC_DIR=$(pwd) # where the script is launched +APPDIR=/opt/navka-relay +VENV=$APPDIR/venv +SERVICE=$HOME/.config/systemd/user/navka-relay.service +USR=${SUDO_USER:-$USER} + +green(){ printf '\e[1;32m==>\e[0m %s\n' "$*"; } + +############################################################################## +green "1. Packages (python + evdev) …" +############################################################################## +sudo pacman -Sy --needed --noconfirm python python-evdev >/dev/null + +############################################################################## +green "2. Install /opt/navka-relay …" +############################################################################## +sudo mkdir -p "$APPDIR" +sudo chown "$USR":"$USR" "$APPDIR" + +# use a symlink when run from dev folder so edits are live +if [[ $SRC_DIR != "$APPDIR" ]]; then + ln -sf "$SRC_DIR/navka-relay.sh" "$APPDIR/navka-relay.sh" +fi +chmod +x "$SRC_DIR/navka-relay.sh" + +############################################################################## +green "3. Build virtual-env (system pkgs visible) …" +############################################################################## +if [[ ! -d $VENV/bin ]]; then + python -m venv --system-site-packages "$VENV" +fi + +# ensure evdev importable inside venv (pacman pkg provides it; pip fallback) +"$VENV/bin/pip" install --quiet --disable-pip-version-check --upgrade pip >/dev/null +"$VENV/bin/python" - <<'PY' +import importlib, subprocess, sys +try: + importlib.import_module('evdev') +except ImportError: + subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'evdev']) +PY + +############################################################################## +green "4. systemd *user* unit …" +############################################################################## +mkdir -p "$(dirname "$SERVICE")" +cat >"$SERVICE" </dev/null 2>&1 || true + +green "✔ Install complete" +cat <<'TXT' +Start relay : systemctl --user start navka-relay +Stop relay : systemctl --user stop navka-relay +Manual run : /opt/navka-relay/navka-relay.sh +Variables : NAVKA_IP=… NAVKA_PORT=… NAVKA_KBD=… navka-relay.sh +TXT + diff --git a/navka-relay.sh b/navka-relay.sh new file mode 100755 index 0000000..2f9cf1b --- /dev/null +++ b/navka-relay.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Wrapper that activates local venv then executes embedded Python + +DIR=$(dirname "$(readlink -f "$0")") +VENV=/opt/navka-relay/venv +PY="$VENV/bin/python" + +if [[ ! -x $PY ]]; then + echo "❌ venv missing: run install-navka-relay.sh first." >&2 + exit 1 +fi + +# shellcheck disable=SC2091 +exec $PY - <<'PYCODE' +import os, socket, selectors, signal, sys +from evdev import InputDevice, ecodes + +# --------- config via env ---------- +DEV = os.getenv("NAVKA_KBD", "/dev/input/event4") +IP = os.getenv("NAVKA_IP", "192.168.42.253") +PORT = int(os.getenv("NAVKA_PORT", "4000")) +# ----------------------------------- + +# full HID tables (letters, numbers, F1-F12, arrows, etc.) +HID = {**{getattr(ecodes,f"KEY_F{i}"):i+53 for i in range(1,13)}} +for ev,h in { + **{k:4+i for i,k in enumerate(range(ecodes.KEY_A,ecodes.KEY_Z+1))}, + **{k:30+i for i,k in enumerate(range(ecodes.KEY_1,ecodes.KEY_0+1))}, + ecodes.KEY_ENTER:40, ecodes.KEY_ESC:41, ecodes.KEY_BACKSPACE:42, + ecodes.KEY_TAB:43, ecodes.KEY_SPACE:44, ecodes.KEY_MINUS:45, + ecodes.KEY_EQUAL:46, ecodes.KEY_LEFTBRACE:47, ecodes.KEY_RIGHTBRACE:48, + ecodes.KEY_BACKSLASH:49, ecodes.KEY_SEMICOLON:51, ecodes.KEY_APOSTROPHE:52, + ecodes.KEY_GRAVE:53, ecodes.KEY_COMMA:54, ecodes.KEY_DOT:55, + ecodes.KEY_SLASH:56, ecodes.KEY_RIGHT:79, ecodes.KEY_LEFT:80, + ecodes.KEY_DOWN:81, ecodes.KEY_UP:82 +}.items(): HID.setdefault(ev, h) + +MOD = { ecodes.KEY_LEFTCTRL:1, ecodes.KEY_LEFTSHIFT:2, ecodes.KEY_LEFTALT:4, + ecodes.KEY_LEFTMETA:8, ecodes.KEY_RIGHTCTRL:16, ecodes.KEY_RIGHTSHIFT:32, + ecodes.KEY_RIGHTALT:64, ecodes.KEY_RIGHTMETA:128 } + +try: + dev = InputDevice(DEV) +except FileNotFoundError: + sys.exit(f"Input device {DEV} not found") + +dev.grab() +sock = socket.create_connection((IP,PORT), timeout=5) +sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + +mods, keys = 0, [] + +def send(): + sock.sendall(bytes([mods,0]+keys[:6]+[0]*(6-len(keys)))) + +def quit_handler(sig, frame): # graceful Ctrl-C + dev.ungrab(); sock.close(); sys.exit(0) + +signal.signal(signal.SIGINT, quit_handler) + +for ev in dev.read_loop(): + if ev.type != ecodes.EV_KEY: continue + pressed = ev.value == 1 + if ev.code in MOD: + mods = mods | MOD[ev.code] if pressed else mods & ~MOD[ev.code] + elif ev.code in HID: + h = HID[ev.code] + if pressed and h not in keys: keys.insert(0,h) + elif not pressed and h in keys: keys.remove(h) + else: + continue + send() +PYCODE +