Rename vrKeyboard to steamKeyboardPatch; clipboard-sync on by default

- steamFrame.vrKeyboard -> steamFrame.steamKeyboardPatch (module, service
  and script names follow): it patches Steam's own on-screen keyboard
- clipboardSync.enable defaults to true: without it the nested desktop's
  clipboard is isolated
This commit is contained in:
Pierre Kisters committed 2026-09-27 15:24:52 +02:00
1 parent 4b2885a992
commit d60931ed72
8 files changed
+39 -33

No files matched your search

+8 -9
View File
@@ -75,8 +75,7 @@ Requirements: Nix with flakes enabled and standalone home-manager.
steamFrame = { steamFrame = {
keyboardLayout = "de"; keyboardLayout = "de";
vrKeyboard.enable = true; steamKeyboardPatch.enable = true;
clipboardSync.enable = true;
firefox.enable = true; firefox.enable = true;
# Only relevant with Steam Developer Mode on (see hidden-apps below). # Only relevant with Steam Developer Mode on (see hidden-apps below).
hiddenApps = [ "lxterminal" "cmake-gui" "firewall-config" "renderdoc" ]; hiddenApps = [ "lxterminal" "cmake-gui" "firewall-config" "renderdoc" ];
@@ -100,7 +99,7 @@ home-manager switch --flake .#steamos
``` ```
Individual modules are available as Individual modules are available as
`homeManagerModules.{session,portal,keyboard-layout,vr-keyboard,hidden-apps,clipboard-sync,firefox}`; `homeManagerModules.{session,portal,keyboard-layout,steam-keyboard-patch,hidden-apps,clipboard-sync,firefox}`;
`default` imports all of them. `default` imports all of them.
**Steam Developer Mode** (a Steam setting, not managed here) makes the "+" **Steam Developer Mode** (a Steam setting, not managed here) makes the "+"
@@ -119,9 +118,9 @@ menu list every desktop entry, including terminals such as Konsole.
| `steamFrame.portalFix.enable` | bool | `true` | Working portal config for the Steam session (OpenURI). | | `steamFrame.portalFix.enable` | bool | `true` | Working portal config for the Steam session (OpenURI). |
| `steamFrame.keyboardLayout` | null or str | `null` | XKB layout for the Steam session, e.g. `"de"`. `null` = no drop-in (US). | | `steamFrame.keyboardLayout` | null or str | `null` | XKB layout for the Steam session, e.g. `"de"`. `null` = no drop-in (US). |
| `steamFrame.keyboardVariant` | null or str | `null` | XKB variant for the Steam session. | | `steamFrame.keyboardVariant` | null or str | `null` | XKB variant for the Steam session. |
| `steamFrame.vrKeyboard.enable` | bool | `false` | Esc/Ctrl/Alt/AltGr and arrow keys on Steam's VR keyboard. | | `steamFrame.steamKeyboardPatch.enable` | bool | `false` | Runtime patch of Steam's on-screen keyboard: Esc/Ctrl/Alt, separate arrows, real Ctrl/Alt chords and hold, AltGr/non-ASCII characters. |
| `steamFrame.hiddenApps` | list of str | `[ ]` | Desktop entry ids (without `.desktop`) to hide from the "+" and KDE menus. | | `steamFrame.hiddenApps` | list of str | `[ ]` | Desktop entry ids (without `.desktop`) to hide from the "+" and KDE menus. |
| `steamFrame.clipboardSync.enable` | bool | `false` | Clipboard bridge between the Steam session and the nested desktop. | | `steamFrame.clipboardSync.enable` | bool | `true` | Clipboard bridge between the Steam session and the nested desktop. |
| `steamFrame.clipboardSync.package` | package | built from `dnut/clipboard-sync` | The clipboard-sync package. | | `steamFrame.clipboardSync.package` | package | built from `dnut/clipboard-sync` | The clipboard-sync package. |
| `steamFrame.firefox.enable` | bool | `false` | Launcher for the Flathub Firefox Flatpak (`org.mozilla.firefox`) with the fixes below. | | `steamFrame.firefox.enable` | bool | `false` | Launcher for the Flathub Firefox Flatpak (`org.mozilla.firefox`) with the fixes below. |
| `steamFrame.firefox.vrFullscreenFix` | bool | `true` | Link a `user.js` with `full-screen-api.ignore-widgets` into existing profiles. | | `steamFrame.firefox.vrFullscreenFix` | bool | `true` | Link a `user.js` with `full-screen-api.ignore-widgets` into existing profiles. |
@@ -174,7 +173,7 @@ the Steam session starts.
**Remove when** SteamOS applies a layout setting to gamescope. **Remove when** SteamOS applies a layout setting to gamescope.
### VR keyboard (`vrKeyboard.enable`) ### Steam keyboard patch (`steamKeyboardPatch.enable`)
**Problem:** Steam's VR keyboard has hardcoded layouts without Ctrl, Alt or **Problem:** Steam's VR keyboard has hardcoded layouts without Ctrl, Alt or
Esc. In VR, Steam can't press real keys Esc. In VR, Steam can't press real keys
@@ -183,15 +182,15 @@ and its text emulation (`ControllerKeyboardSendText`) only maps plain ASCII:
non-ASCII characters and anything needing AltGr or a dead key on the German non-ASCII characters and anything needing AltGr or a dead key on the German
keymap (`| @ { [ ] } \ ~ ^`, backtick, `ä ö ü €`) come out as `1`. keymap (`| @ { [ ] } \ ~ ^`, backtick, `ä ö ü €`) come out as `1`.
**Fix:** the `vr-keyboard` user service (`vrkbd-helper.mjs`, Node) injects **Fix:** the `steam-keyboard-patch` user service (`helper.mjs`, Node) injects
`vrkbd-patch.js` into Steam's UI at runtime through Steam's CEF DevTools port `patch.js` into Steam's UI at runtime through Steam's CEF DevTools port
(`127.0.0.1:8080`; SteamOS starts Steam with `-cef-enable-debugging`) and (`127.0.0.1:8080`; SteamOS starts Steam with `-cef-enable-debugging`) and
re-injects it after Steam restarts or the keyboard popup is recreated. re-injects it after Steam restarts or the keyboard popup is recreated.
Steam's files are never modified. Enabling and disabling take effect on Steam's files are never modified. Enabling and disabling take effect on
`home-manager switch`, no reboot or Steam restart needed: when the helper `home-manager switch`, no reboot or Steam restart needed: when the helper
stops (service stopped, or the option disabled, which stops it via stops (service stopped, or the option disabled, which stops it via
`userServices.stop`) it reverts the patch in Steam's running UI `userServices.stop`) it reverts the patch in Steam's running UI
(`vrkbd-unpatch.js`). The service is restarted on every switch (via (`unpatch.js`). The service is restarted on every switch (via
`userServices.restart`) so a changed patch is re-injected. `userServices.restart`) so a changed patch is re-injected.
- Bottom row becomes `Esc Ctrl Alt [Space] AltGr ← ↑ ↓ → Close`. - Bottom row becomes `Esc Ctrl Alt [Space] AltGr ← ↑ ↓ → Close`.
+1 -1
View File
@@ -15,7 +15,7 @@
session = ./modules/session.nix; session = ./modules/session.nix;
portal = ./modules/portal.nix; portal = ./modules/portal.nix;
keyboard-layout = ./modules/keyboard-layout.nix; keyboard-layout = ./modules/keyboard-layout.nix;
vr-keyboard = ./modules/vr-keyboard.nix; steam-keyboard-patch = ./modules/steam-keyboard-patch.nix;
hidden-apps = ./modules/hidden-apps.nix; hidden-apps = ./modules/hidden-apps.nix;
# Built with the consumer's pkgs; only the source comes from our input. # Built with the consumer's pkgs; only the source comes from our input.
# The key lets the module system deduplicate it when it is imported both # The key lets the module system deduplicate it when it is imported both
+8 -4
View File
@@ -11,10 +11,14 @@ let
cfg = config.steamFrame.clipboardSync; cfg = config.steamFrame.clipboardSync;
in { in {
options.steamFrame.clipboardSync = { options.steamFrame.clipboardSync = {
enable = lib.mkEnableOption '' enable = lib.mkOption {
clipboard-sync between the Steam session and the nested desktop type = lib.types.bool;
(KDE autostart; on switch, stale builds and duplicates are stopped) default = true; # without it the nested desktop's clipboard is isolated
''; description = ''
Whether to run clipboard-sync between the Steam session and the nested
desktop (KDE autostart; on switch, stale builds and duplicates are stopped).
'';
};
package = lib.mkOption { package = lib.mkOption {
type = lib.types.package; type = lib.types.package;
default = pkgs.rustPlatform.buildRustPackage { default = pkgs.rustPlatform.buildRustPackage {
+1 -1
View File
@@ -55,7 +55,7 @@ in {
restart = lib.mkOption { restart = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
default = [ ]; default = [ ];
example = [ "vr-keyboard.service" ]; example = [ "steam-keyboard-patch.service" ];
description = "User units restarted on every switch (outer user manager)."; description = "User units restarted on every switch (outer user manager).";
}; };
stop = lib.mkOption { stop = lib.mkOption {
@@ -2,7 +2,7 @@
# The keyboard is part of Steam's UI (hardcoded layouts in steamui JS), and in # The keyboard is part of Steam's UI (hardcoded layouts in steamui JS), and in
# VR it can only send text; SteamClient.Input.ControllerKeyboardSetKeyState # VR it can only send text; SteamClient.Input.ControllerKeyboardSetKeyState
# throws "Unknown method". So: # throws "Unknown method". So:
# - vrkbd-patch.js is injected at runtime into Steam's UI through its CEF # - patch.js is injected at runtime into Steam's UI through its CEF
# DevTools port (127.0.0.1:8080; SteamOS starts Steam with # DevTools port (127.0.0.1:8080; SteamOS starts Steam with
# -cef-enable-debugging). Steam's files are untouched, so updates don't undo # -cef-enable-debugging). Steam's files are untouched, so updates don't undo
# it. It rebuilds the bottom row and, while Ctrl/Alt is active (or for Esc), # it. It rebuilds the bottom row and, while Ctrl/Alt is active (or for Esc),
@@ -10,7 +10,7 @@
# the real Ctrl/Alt down while the toggle is on (Ctrl+scroll), and routes # the real Ctrl/Alt down while the toggle is on (Ctrl+scroll), and routes
# characters Steam's key emulation turns into "1" (non-ASCII, AltGr/dead # characters Steam's key emulation turns into "1" (non-ASCII, AltGr/dead
# keys on the de keymap: |@{[]}\~^`äöü€…) to the helper. # keys on the de keymap: |@{[]}\~^`äöü€…) to the helper.
# - vrkbd-helper.mjs keeps that injection alive (Steam restarts, keyboard popup # - helper.mjs keeps that injection alive (Steam restarts, keyboard popup
# recreated) and presses the combos with `xdotool key` on :0, where gamescope # recreated) and presses the combos with `xdotool key` on :0, where gamescope
# keeps X focus on the window selected in VR. It only accepts ctrl/alt # keeps X focus on the window selected in VR. It only accepts ctrl/alt
# chords and the extra keys, so the Steam UI can't use it to type text or # chords and the extra keys, so the Steam UI can't use it to type text or
@@ -19,21 +19,24 @@
{ config, pkgs, lib, ... }: { { config, pkgs, lib, ... }: {
imports = [ ./session.nix ]; imports = [ ./session.nix ];
options.steamFrame.vrKeyboard.enable = lib.mkEnableOption '' options.steamFrame.steamKeyboardPatch.enable = lib.mkEnableOption ''
Esc/Ctrl/Alt/AltGr and arrow keys on Steam's VR keyboard (runtime patch of the runtime patch of Steam's on-screen (VR) keyboard: Esc/Ctrl/Alt and four
Steam's UI through its CEF DevTools port, plus an xdotool helper service) separate arrow keys, real Ctrl/Alt chords (held while toggled, e.g. for
Ctrl+scroll), and working AltGr/non-ASCII characters. Injected into Steam's
UI through its CEF DevTools port by an xdotool helper service; disabling
it reverts the patch on the next switch
''; '';
config = lib.mkMerge [ config = lib.mkMerge [
(lib.mkIf config.steamFrame.vrKeyboard.enable { (lib.mkIf config.steamFrame.steamKeyboardPatch.enable {
systemd.user.services.vr-keyboard = { systemd.user.services.steam-keyboard-patch = {
Unit.Description = "Modifier keys for Steam's VR keyboard (CEF patch + xdotool)"; Unit.Description = "Modifier keys for Steam's VR keyboard (CEF patch + xdotool)";
Service = { Service = {
ExecStart = lib.escapeShellArgs [ ExecStart = lib.escapeShellArgs [
"${pkgs.nodejs}/bin/node" "${pkgs.nodejs}/bin/node"
"${./vr-keyboard/vrkbd-helper.mjs}" "${./steam-keyboard-patch/helper.mjs}"
"${./vr-keyboard/vrkbd-patch.js}" "${./steam-keyboard-patch/patch.js}"
"${./vr-keyboard/vrkbd-unpatch.js}" "${./steam-keyboard-patch/unpatch.js}"
"${pkgs.xdotool}/bin/xdotool" "${pkgs.xdotool}/bin/xdotool"
]; ];
Environment = "VRKBD_DISPLAY=:0"; Environment = "VRKBD_DISPLAY=:0";
@@ -46,12 +49,12 @@
# Restart on every switch so a changed patch is re-injected (it replaces # Restart on every switch so a changed patch is re-injected (it replaces
# the older version). # the older version).
steamFrame.userServices.restart = [ "vr-keyboard.service" ]; steamFrame.userServices.restart = [ "steam-keyboard-patch.service" ];
}) })
# Disabled: stop a still-running helper, which reverts the patch, so the # Disabled: stop a still-running helper, which reverts the patch, so the
# stock keyboard is back right away (no Steam restart or reboot). # stock keyboard is back right away (no Steam restart or reboot).
(lib.mkIf (!config.steamFrame.vrKeyboard.enable) { (lib.mkIf (!config.steamFrame.steamKeyboardPatch.enable) {
steamFrame.userServices.stop = [ "vr-keyboard.service" ]; steamFrame.userServices.stop = [ "steam-keyboard-patch.service" ];
}) })
]; ];
} }
@@ -1,8 +1,8 @@
// vrkbd-helper: injects vrkbd-patch.js into Steam's UI via CEF DevTools and // helper.mjs: injects patch.js into Steam's UI via CEF DevTools and
// performs the key requests of the patched VR keyboard with xdotool on :0. // performs the key requests of the patched VR keyboard with xdotool on :0.
// On SIGTERM/SIGINT it reverts the patch (unpatch.js), so stopping the service // On SIGTERM/SIGINT it reverts the patch (unpatch.js), so stopping the service
// restores Steam's stock keyboard without restarting Steam. // restores Steam's stock keyboard without restarting Steam.
// usage: node vrkbd-helper.mjs <patch.js> <unpatch.js> [xdotool] // usage: node helper.mjs <patch.js> <unpatch.js> [xdotool]
import { readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
import { execFile } from 'node:child_process'; import { execFile } from 'node:child_process';
@@ -1,4 +1,4 @@
// Injected into Steam's SharedJSContext (CEF, 127.0.0.1:8080) by vrkbd-helper. // Injected into Steam's SharedJSContext (CEF, 127.0.0.1:8080) by helper.mjs.
// Extends Steam's VR keyboard for gamescope app windows: // Extends Steam's VR keyboard for gamescope app windows:
// - bottom row: Esc Ctrl Alt [space] AltGr and four separate arrow keys // - bottom row: Esc Ctrl Alt [space] AltGr and four separate arrow keys
// - Ctrl/Alt chords and Esc are pressed for real (Steam can't: in VR // - Ctrl/Alt chords and Esc are pressed for real (Steam can't: in VR
@@ -9,7 +9,7 @@
// into "1" -- anything non-ASCII or needing AltGr / a dead key on the X // into "1" -- anything non-ASCII or needing AltGr / a dead key on the X
// keymap -- are typed by the helper instead // keymap -- are typed by the helper instead
// Everything goes through the CDP binding window.__vrkbdKey("<op>:<arg>"), // Everything goes through the CDP binding window.__vrkbdKey("<op>:<arg>"),
// executed by vrkbd-helper with xdotool on :0. // executed by helper.mjs with xdotool on :0.
// Idempotent: safe to evaluate repeatedly. // Idempotent: safe to evaluate repeatedly.
(() => { (() => {
const VERSION = 5; const VERSION = 5;
@@ -1,4 +1,4 @@
// Reverts vrkbd-patch.js in Steam's SharedJSContext; evaluated by vrkbd-helper // Reverts patch.js in Steam's SharedJSContext; evaluated by helper.mjs
// when it stops (service stopped, module disabled). Every patched function // when it stops (service stopped, module disabled). Every patched function
// keeps its original as __vrkbdOrig. Safe to run when nothing is patched. // keeps its original as __vrkbdOrig. Safe to run when nothing is patched.
(() => { (() => {