Files
Nix-Vibe/home-manager/modules/hyprland-tablet.nix
T
petere eb08cd4282 Nix-Vibe public snapshot (squashed history)
Current state of main at 0240060 feat(hp-laptop): install TeleportFling from its flake. History intentionally
collapsed to a single commit; this repo mirrors only the latest state.
2026-09-19 13:53:39 +01:00

220 lines
5.7 KiB
Nix

{
config,
pkgs,
...
}:
let
palette = import ./palette.nix;
evdevPython = pkgs.python3.withPackages (ps: [ ps.evdev ]);
# Text-capable window classes. wvkbd's own --auto relies on Hyprland's
# input-method-v2 protocol, which Hyprland advertises but never actually
# fires — so auto show/hide is driven here by watching activewindow events.
# Add classes (lowercase prefixes are allowed) for anything you type into.
textApps = [
"kitty"
"alacritty"
"wezterm"
"konsole"
"com.mitchellh.ghostty"
"ghostty"
"xterm"
"firefox"
"librewolf"
"chromium"
"chromium-browser"
"google-chrome"
"microsoft-edge"
"brave-browser"
"thunderbird"
"com.github.xournalpp.xournalpp"
"code"
"code-url-handler"
"zen"
];
# Shell-escaped arg list for the CLI fallback (same content as wvkbdFlags
# but space-joined for shell expansion). The daemon receives the JSON form.
# The font spec is double-quoted so it survives shell word-splitting.
wvkbdFlagsShell = pkgs.lib.concatStringsSep " " [
"-l"
"simple"
"-H"
"300"
"-L"
"200"
"--fn"
"\"DejaVu Sans 24\""
"--alpha"
"235"
"--bg"
"${palette.wvkbdBg}"
"--fg"
"${palette.surface}"
"--fg-sp"
"${palette.wvkbdFgSp}"
"--press"
"${palette.neon}"
"--press-sp"
"${palette.violet}"
"--text"
"${palette.text}"
"--text-sp"
"${palette.muted}"
"--text-press"
"${palette.ink}"
"--text-press-sp"
"${palette.ink}"
"--swipe"
"${palette.cyan}"
"--swipe-sp"
"${palette.violet}"
"--text-swipe"
"${palette.ink}"
"--text-swipe-sp"
"${palette.ink}"
];
# wvkbd argv as a JSON array. The daemon splices this into place as a bare
# Python expression — a JSON array of strings IS valid Python (list of
# double-quoted strings), so no quote-escaping headaches at build time.
wvkbdArgs = [
"-l"
"simple"
"-H"
"300"
"-L"
"200"
"--fn"
"DejaVu Sans 24"
"--alpha"
"235"
"--bg"
"${palette.wvkbdBg}"
"--fg"
"${palette.surface}"
"--fg-sp"
"${palette.wvkbdFgSp}"
"--press"
"${palette.neon}"
"--press-sp"
"${palette.violet}"
"--text"
"${palette.text}"
"--text-sp"
"${palette.muted}"
"--text-press"
"${palette.ink}"
"--text-press-sp"
"${palette.ink}"
"--swipe"
"${palette.cyan}"
"--swipe-sp"
"${palette.violet}"
"--text-swipe"
"${palette.ink}"
"--text-swipe-sp"
"${palette.ink}"
];
wvkbdArgsJson = builtins.toJSON wvkbdArgs;
# Text-app allowlist as a JSON array, spliced the same way (valid Python
# list literal after substitution).
textAppsJson = builtins.toJSON textApps;
# Place build-time paths into the daemon (replaceVars honours the
# @VAR@ placeholders) and run it with the evdev-enabled python.
daemon = pkgs.replaceVarsWith {
name = "hyprland-tablet-daemon";
src = pkgs.writeScript "hyprland-tablet-daemon-src" ''
#!${evdevPython}/bin/python3
${builtins.readFile ./hyprland-tablet-daemon.py}
'';
isExecutable = true;
replacements = {
EVDEV_DEVICE = "/dev/input/event17";
WVKBD_PATH = "${pkgs.wvkbd}/bin/wvkbd-mobintl";
WVKBD_ARGS = wvkbdArgsJson;
TEXT_APPS_JSON = textAppsJson;
MONITOR_SENSOR = "${pkgs.iio-sensor-proxy}/bin/monitor-sensor";
HYPRCTL = "${pkgs.hyprland}/bin/hyprctl";
};
};
# Toggle/show/hide are forwarded to the daemon over a Unix datagram socket
# so manual control and focus-driven auto-hide share one visibility state.
cli = pkgs.writeShellScriptBin "hyprland-tablet" ''
set -eu
state_file="$XDG_RUNTIME_DIR/hyprland-tablet"
ctl_sock="$XDG_RUNTIME_DIR/hyprland-tablet.ctl"
ctl_send() {
if [ -S "$ctl_sock" ]; then
${pkgs.python3}/bin/python3 -c '
import os, socket, sys
path = os.environ.get("XDG_RUNTIME_DIR", "/tmp") + "/hyprland-tablet.ctl"
s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
s.sendto(sys.argv[1].encode(), path)
' "$1"
return 0
fi
return 1
}
osk_pid() {
pgrep -f 'wvkbd-mobintl' | head -n1 || true
}
case "''${1:-}" in
status)
if [ -f "$state_file" ]; then
cat "$state_file"
else
echo '{"tablet":false,"osk":false}'
fi
;;
# Toggle on-screen keyboard visibility. Manual control wins over the
# daemon's focus-derived state until the next activewindow change.
keyboard-toggle|osk-toggle)
ctl_send toggle || {
pid="$(osk_pid)"
if [ -z "$pid" ]; then
nohup ${pkgs.wvkbd}/bin/wvkbd-mobintl ${wvkbdFlagsShell} >/dev/null 2>&1 &
else
kill -SIGRTMIN "$pid"
fi
}
;;
osk-show)
ctl_send show || { pid="$(osk_pid)"; [ -n "$pid" ] && kill -SIGUSR2 "$pid"; }
;;
osk-hide)
ctl_send hide || { pid="$(osk_pid)"; [ -n "$pid" ] && kill -SIGUSR1 "$pid"; }
;;
*)
echo "usage: hyprland-tablet {status|keyboard-toggle|osk-show|osk-hide}" >&2
exit 2
;;
esac
'';
in
{
home.packages = [ cli ];
systemd.user.services.hyprland-tablet = {
Unit = {
Description = "Hyprland tablet-mode auto-rotation + on-screen keyboard";
After = [ "graphical-session.target" ];
PartOf = [ "graphical-session.target" ];
};
Service = {
Type = "simple";
ExecStart = daemon;
Restart = "on-failure";
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
};
}