mirror of
https://github.com/lhns/steam-frame-nix.git
synced 2026-10-06 01:00:13 +02:00
- steam-keyboard-patch.nix -> vr-keyboard-extra-keys.nix (option
keyboard.vr.extraKeys), helper.mjs -> xdotool-helper.mjs;
homeManagerModules.steam-keyboard-patch stays as an alias.
- vr-keyboard: panel.js/unpatch-panel.js/relay.mjs ->
suggestions-panel/{patch,unpatch}.js + relay.mjs; decoder.js ->
swipe-decoder.js; build.nix split into dictionary.nix and check.nix.
- modules/lib -> modules/steam-ui-patches/lib (the UI patch library).
- docs/development.md: repository layout (what runs where), runtime names.
Patch names, user services and state files are unchanged.
222 lines
9.7 KiB
Nix
222 lines
9.7 KiB
Nix
# Swipe typing, suggestions and a Backspace drag for Steam's VR keyboard
|
|
# (steamFrame.keyboard.vr; the extra keys are vr-keyboard-extra-keys.nix).
|
|
# - vr-keyboard/patch.js (Steam UI, 8080): swipe gestures (swipe-decoder.js),
|
|
# a model of what the keyboard typed (textmodel.js), suggestions (swipe
|
|
# alternatives, corrections and completions, corrector.js; never changing
|
|
# text by themselves), Backspace drag (left: delete with a detent at word
|
|
# borders; right: retype), haptic ticks.
|
|
# - The suggestion strip: over the number row ("inside"), or a SteamVR
|
|
# dashboard panel above/below the keyboard (suggestions-panel/patch.js in
|
|
# systemui, 8087), fed by suggestions-panel/relay.mjs (user service
|
|
# vr-keyboard-relay).
|
|
# - Dictionary: dictionary.nix (gen-dict.py) from wordfreq frequency lists,
|
|
# filtered and cased by Hunspell (nixpkgs' hunspellDicts).
|
|
# Works alongside keyboard.vr.extraKeys (both hook the same keyboard; this
|
|
# one resets its text model on the extra keys it can't follow). Tests:
|
|
# check.nix (built before the patch; also flake check `vr-keyboard`).
|
|
{ config, pkgs, lib, ... }:
|
|
let
|
|
cfg = config.steamFrame.keyboard.vr;
|
|
inherit (lib) mkOption mkEnableOption types;
|
|
uiLib = import ./steam-ui-patches/lib { inherit pkgs; };
|
|
|
|
on = cfg.enable && (cfg.swipe.enable || cfg.autocorrect.enable || cfg.completions.enable || cfg.backspaceDrag.enable);
|
|
panel = on && cfg.suggestions.position != "inside";
|
|
|
|
# Default dictionary: the language of keyboard.layout (if listed here) plus
|
|
# English, else English only.
|
|
layoutLanguages = {
|
|
de = "de_DE"; fr = "fr-moderne"; es = "es_ES"; it = "it_IT"; nl = "nl_NL"; pt = "pt_PT"; sv = "sv_SE";
|
|
};
|
|
layoutLanguage = let l = config.steamFrame.keyboard.layout; in
|
|
if l != null && layoutLanguages ? ${builtins.head (lib.splitString "," l)} then builtins.head (lib.splitString "," l) else null;
|
|
defaultLanguages =
|
|
lib.optional (layoutLanguage != null)
|
|
{ language = layoutLanguage; hunspell = layoutLanguages.${layoutLanguage}; words = 60000; frequencyOffset = 0.0; }
|
|
++ [ { language = "en"; hunspell = "en_US"; words = if layoutLanguage != null then 40000 else 60000;
|
|
frequencyOffset = if layoutLanguage != null then -0.3 else 0.0; } ];
|
|
|
|
dictionaryJs = import ./vr-keyboard/dictionary.nix { inherit pkgs; inherit (cfg) dictionary; };
|
|
checks = import ./vr-keyboard/check.nix { inherit pkgs; inherit (cfg) dictionary; };
|
|
patch = pkgs.runCommand "vr-keyboard.js" { nativeBuildInputs = [ pkgs.nodejs ]; } ''
|
|
: ${checks}
|
|
cp ${uiLib.mkPatch {
|
|
name = "vr-keyboard";
|
|
src = ./vr-keyboard/patch.js;
|
|
opts = {
|
|
swipe = cfg.swipe.enable;
|
|
inherit (cfg.text) autoSpace bufferChars resetAfterIdleSeconds;
|
|
inherit (cfg.suggestions) position count;
|
|
autocorrect = cfg.autocorrect.enable;
|
|
inherit (cfg.autocorrect) maxEditDistance;
|
|
completions = cfg.completions.enable;
|
|
inherit (cfg.completions) minPrefix;
|
|
pixelsPerChar = if cfg.backspaceDrag.enable then cfg.backspaceDrag.pixelsPerChar else 0;
|
|
inherit (cfg.backspaceDrag) wordDetentPixels;
|
|
inherit (cfg) haptics;
|
|
};
|
|
extraArgs = [ ./vr-keyboard/swipe-decoder.js ./vr-keyboard/textmodel.js ./vr-keyboard/corrector.js dictionaryJs ];
|
|
}} $out
|
|
node --check $out
|
|
'';
|
|
|
|
languageType = types.submodule {
|
|
options = {
|
|
language = mkOption { type = types.str; example = "fr"; description = "wordfreq language (de, en, fr, es, it, nl, pt, sv, ...)."; };
|
|
hunspell = mkOption {
|
|
type = types.nullOr types.str;
|
|
example = "fr-moderne";
|
|
description = ''
|
|
pkgs.hunspellDicts attribute that filters the words and sets their
|
|
casing; null: all words of the frequency list, lowercase.
|
|
'';
|
|
};
|
|
words = mkOption { type = types.ints.positive; description = "Most frequent words taken."; };
|
|
frequencyOffset = mkOption {
|
|
type = types.number;
|
|
default = 0.0;
|
|
description = "Added to the words' zipf frequency, e.g. -0.3 for a second language.";
|
|
};
|
|
keepFrequentAbove = mkOption {
|
|
type = types.nullOr types.number;
|
|
default = 4.0;
|
|
description = ''
|
|
Words Hunspell rejects are kept from this zipf frequency on ("ok",
|
|
"lol"; 3+ letters unless zipf >= 5); null: Hunspell only.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
in {
|
|
imports = [ ./session.nix ./cleanup.nix ./steam-ui-patches.nix ./keyboard-layout.nix ];
|
|
|
|
options.steamFrame.keyboard.vr = {
|
|
enable = mkEnableOption ''
|
|
swipe typing, suggestions (swipe alternatives, corrections, completions)
|
|
and a Backspace drag on Steam's VR keyboard (a Steam UI patch; the
|
|
sub-features below are on by default)'';
|
|
|
|
swipe.enable = mkEnableOption "swipe typing" // { default = true; };
|
|
|
|
dictionary = {
|
|
languages = mkOption {
|
|
type = types.listOf languageType;
|
|
default = defaultLanguages;
|
|
defaultText = lib.literalMD ''
|
|
the language of `steamFrame.keyboard.layout` (de, fr, es, it, nl, pt,
|
|
sv; 60000 words) plus English (40000, frequency -0.3), else English
|
|
only (60000)'';
|
|
description = "Dictionary languages.";
|
|
};
|
|
contractions = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = ''Words with apostrophes ("couldn't", "geht's"), swiped by their letters.'';
|
|
};
|
|
extraWords = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ ];
|
|
example = [ "SteamVR" "E-Mail" ];
|
|
description = "Words always included (casing as given; ' and - are typed, not swiped).";
|
|
};
|
|
extraWordsFrequency = mkOption { type = types.number; default = 5.0; description = "Zipf frequency of extra words."; };
|
|
extraWordFiles = mkOption {
|
|
type = types.listOf types.path;
|
|
default = [ ];
|
|
description = ''Word lists: one "word" or "word<TAB>zipf" per line.'';
|
|
};
|
|
excludeWords = mkOption { type = types.listOf types.str; default = [ ]; description = "Words never suggested (any casing)."; };
|
|
};
|
|
|
|
text = {
|
|
bufferChars = mkOption { type = types.ints.between 8 1024; default = 128; description = "Characters of typed text remembered."; };
|
|
resetAfterIdleSeconds = mkOption {
|
|
type = types.ints.unsigned;
|
|
default = 30;
|
|
description = "Forget the remembered text after this long without typing (0: never).";
|
|
};
|
|
autoSpace = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Space before a swiped word after a known non-space, non-opening character.";
|
|
};
|
|
};
|
|
|
|
suggestions = {
|
|
position = mkOption {
|
|
type = types.enum [ "below" "inside" "above" ];
|
|
default = "above";
|
|
description = ''
|
|
Suggestion strip: a SteamVR dashboard panel "below" or "above" the
|
|
keyboard, moving with it, or "inside" the keyboard over the number
|
|
row.
|
|
'';
|
|
};
|
|
count = mkOption { type = types.ints.between 1 8; default = 6; description = "Suggestions shown."; };
|
|
};
|
|
|
|
autocorrect = {
|
|
enable = mkEnableOption "correction suggestions for finished tapped words not in the dictionary" // { default = true; };
|
|
maxEditDistance = mkOption { type = types.ints.between 1 3; default = 2; description = "Largest edit distance (neighbouring keys, swaps: 0.5)."; };
|
|
};
|
|
completions = {
|
|
enable = mkEnableOption "completions of the tapped word" // { default = true; };
|
|
minPrefix = mkOption { type = types.ints.between 1 10; default = 2; description = "Letters typed before completions show."; };
|
|
};
|
|
|
|
backspaceDrag = {
|
|
enable = mkEnableOption "Backspace drag: drag left to delete, back right to retype" // { default = true; };
|
|
pixelsPerChar = mkOption { type = types.ints.positive; default = 25; description = "Travel per character (keyboard px; a key is ~60)."; };
|
|
wordDetentPixels = mkOption {
|
|
type = types.ints.unsigned;
|
|
default = 90;
|
|
description = "Detent at a word border: extra travel to delete across it (0: none).";
|
|
};
|
|
};
|
|
|
|
haptics = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Haptic ticks: Backspace-drag steps, a stronger one at word detents, suggestion picks.";
|
|
};
|
|
|
|
checks = mkOption {
|
|
type = types.package;
|
|
readOnly = true;
|
|
default = checks;
|
|
defaultText = lib.literalMD "the tests, built with the configured dictionary";
|
|
description = "The tests (also built with the patch).";
|
|
};
|
|
};
|
|
|
|
config = lib.mkMerge [
|
|
{
|
|
assertions = map (l: {
|
|
assertion = l.hunspell == null || pkgs.hunspellDicts ? ${l.hunspell};
|
|
message = "steamFrame.keyboard.vr.dictionary.languages: no pkgs.hunspellDicts.${toString l.hunspell}.";
|
|
}) cfg.dictionary.languages;
|
|
|
|
steamFrame.uiPatches.patches = lib.optional on {
|
|
name = "vr-keyboard";
|
|
target.title = "SharedJSContext";
|
|
inherit patch;
|
|
unpatch = ./vr-keyboard/unpatch.js;
|
|
} ++ lib.optional panel {
|
|
name = "vr-keyboard-panel";
|
|
endpoint = "http://127.0.0.1:8087";
|
|
target.title = "systemui";
|
|
patch = pkgs.writeText "vr-keyboard-panel.js" "(${builtins.readFile ./vr-keyboard/suggestions-panel/patch.js})()";
|
|
unpatch = ./vr-keyboard/suggestions-panel/unpatch.js;
|
|
};
|
|
steamFrame.session.services.${if panel then "restart" else "stop"} = [ "vr-keyboard-relay.service" ];
|
|
}
|
|
(lib.mkIf panel {
|
|
systemd.user.services.vr-keyboard-relay = {
|
|
Unit.Description = "Relay of the VR keyboard's suggestion strip (Steam UI <-> SteamVR dashboard)";
|
|
Service = { ExecStart = "${pkgs.nodejs}/bin/node ${./vr-keyboard/suggestions-panel/relay.mjs}"; Restart = "always"; RestartSec = 5; };
|
|
Install.WantedBy = [ "default.target" ];
|
|
};
|
|
})
|
|
];
|
|
}
|