first commit
This commit is contained in:
commit
a8db1ba2b3
76
install-navka-relay.sh
Executable file
76
install-navka-relay.sh
Executable file
@ -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" <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Navka keyboard relay
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/opt/navka-relay/navka-relay.sh
|
||||||
|
Restart=on-failure
|
||||||
|
EnvironmentFile=%h/.config/navka-relay.env
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user enable navka-relay >/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
|
||||||
|
|
||||||
74
navka-relay.sh
Executable file
74
navka-relay.sh
Executable file
@ -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
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user