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.
220 lines
5.7 KiB
Nix
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" ];
|
|
};
|
|
};
|
|
}
|