mirror of
https://github.com/lhns/steam-frame-nix.git
synced 2026-10-06 04:00:12 +02:00
Patch headers and comments: concise
Document the patch calling convention once in lib/default.nix; patch headers say what, target, hook, state and contracts. Comments only, no code changes.
This commit is contained in:
1 parent
4bb0fdd863
commit
4c843c3ce4
22 files changed
+346
-553
No files matched your search
@@ -1,51 +1,32 @@
|
||||
// dashboard-windows: resize and grab-distance limits of SteamVR dashboard
|
||||
// windows (Steam, app windows, overlays, the theater screen, the dashboard
|
||||
// itself). Target: SteamVR's dashboard page (vrwebhelper, DevTools
|
||||
// 127.0.0.1:8087, title "systemui").
|
||||
//
|
||||
// This file is a function expression, called by the file lib/default.nix
|
||||
// (mkPatch) generates: (<this file>)(find, sigs, opts, hooks), with find the
|
||||
// finder library (lib/finders.js), sigs this patch's module signatures
|
||||
// (lib/signatures.json, "dashboard-windows"), opts the options from
|
||||
// dashboard-windows.nix (null = stock) and hooks the shared method hooks
|
||||
// (lib/hooks.js), e.g. opts
|
||||
// windows (Steam, app windows, overlays, theater screen, the dashboard).
|
||||
// Target: SteamVR dashboard (vrwebhelper, DevTools 127.0.0.1:8087, title
|
||||
// "systemui"). mkPatch patch (see lib/default.nix); opts (null = stock):
|
||||
// { maxScale: 4.0,
|
||||
// distance: { world: {min: null, max: 10}, theater: {...}, dashboard: {...} } }
|
||||
//
|
||||
// How it works: systemui builds a scene graph from its DOM and sends it to
|
||||
// vrcompositor through its mailbox WebSocket,
|
||||
// systemui sends its scene graph to vrcompositor via
|
||||
// mailbox.SendMessage("vrcompositor_systemlayer", {type: "update_scene_graph", scene_graph})
|
||||
// and vrcompositor enforces the limits it finds there:
|
||||
// - "frame-resize-scale-min"/"-max" on every window frame (stock 0.25 / 2,
|
||||
// relative to the window's default size; the theater screen's default is
|
||||
// 2.8x larger): range of the resize handle;
|
||||
// - "min-distance"/"max-distance" (meters) on grab nodes: how close / far a
|
||||
// grabbed window can be pulled in / pushed back (thumbstick / scroll):
|
||||
// and vrcompositor enforces the limits in it:
|
||||
// - "frame-resize-scale-min"/"-max" on window frames (stock 0.25 / 2, relative
|
||||
// to the default size; the theater's default is 2.8x larger);
|
||||
// - "min-distance"/"max-distance" (m) on grab nodes (pull in / push back):
|
||||
// world "grab-scale" 0.25 / 5 windows placed in the world
|
||||
// theater "grab-transform" 1 / 6 the theater screen
|
||||
// dashboard "grab-transform" 0.3 / 4 the dashboard itself
|
||||
// (the keyboard's grab-transform, 0.2 / 1, is left alone).
|
||||
// These are constants/literals in systemui's bundle (not patchable at the
|
||||
// source), so this patch hooks the mailbox class's SendMessage (on its
|
||||
// prototype, which existing instances use; through lib/hooks.js, which
|
||||
// window-curvature shares) and rewrites them in outgoing scene graphs (a
|
||||
// fresh object per update, so editing it in place is safe).
|
||||
// Grab nodes are identified by node type plus their exact stock values: after
|
||||
// a SteamVR update that changes them, the distance rewrite is a no-op.
|
||||
// Then systemui is asked for one scene-graph resend (its own debounced
|
||||
// scheduler: rebuilds the unchanged graph, no visible UI change), so new
|
||||
// limits apply immediately.
|
||||
// They are literals in systemui's bundle, so SendMessage is hooked on the
|
||||
// mailbox prototype (lib/hooks.js, shared with window-curvature) and outgoing
|
||||
// scene graphs (fresh objects per update) are edited in place. Grab nodes are
|
||||
// matched by type plus exact stock values, so if SteamVR changes them the
|
||||
// distance rewrite becomes a no-op. A scene-graph resend (systemui's debounced
|
||||
// scheduler) applies new limits at once.
|
||||
//
|
||||
// State: window.__sfuiDashboardWindows (mailbox prototype, resend function,
|
||||
// options id, per-kind rewrite counters in .hits). The hook is registered
|
||||
// as NAME; unpatch.js removes it and resends the stock graph. (VERSION 2
|
||||
// wrapped SendMessage itself and read the rewrite from the state's
|
||||
// `rewrite`, which is never set any more, so a leftover wrapper is inert; the
|
||||
// hooks library unwinds one still on top.)
|
||||
// The mailbox class and the scheduler are found by signature, not by
|
||||
// webpack module id or minified export name; if one doesn't match, the patch
|
||||
// returns an error and changes nothing. Idempotent: same VERSION and options
|
||||
// -> "unchanged"; otherwise the rewrite is replaced in place.
|
||||
// State: window.__sfuiDashboardWindows (proto, resend, options id, counters in
|
||||
// .hits); hook name NAME. (VERSION 2 wrapped SendMessage itself and read
|
||||
// state.rewrite, no longer set, so a leftover wrapper is inert; hooks.js
|
||||
// unwinds one on top.) Signature mismatch -> error, nothing changed. Same
|
||||
// VERSION and options -> "unchanged", else the rewrite is replaced in place.
|
||||
((find, sigs, opts, hooks) => {
|
||||
const NAME = 'dashboard-windows';
|
||||
const VERSION = 3;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Reverts dashboard-windows/patch.js: removes its SendMessage hook (the
|
||||
// shared wrapper goes too once no other patch uses it) and resends the scene
|
||||
// graph, so vrcompositor gets the stock limits again right away. Also
|
||||
// restores the mailbox SendMessage if VERSION 2's own wrapper is on top.
|
||||
// Safe when not patched.
|
||||
// Reverts dashboard-windows/patch.js: removes its SendMessage hook (and a
|
||||
// VERSION 2 own wrapper if on top) and resends the scene graph so the stock
|
||||
// limits apply at once. Safe when not patched.
|
||||
(() => {
|
||||
const NAME = 'dashboard-windows';
|
||||
const st = window.__sfuiDashboardWindows;
|
||||
|
||||
@@ -1,81 +1,59 @@
|
||||
// frame-controls: move the control icons under SteamVR dashboard windows
|
||||
// between the window's bottom bar and its More Options (three-dot) menu.
|
||||
// A long press on a bar icon or a menu row (opts.longPressMs; a ring around
|
||||
// the icon shows the progress from half that time, at most after 1 s) opens a
|
||||
// small popup with a "Show in bar" checkbox; toggling it moves that control for all
|
||||
// windows. Short presses stay stock. The three-dot button itself can't be
|
||||
// moved (stock shows it while its menu has entries). opts.floatInTheater
|
||||
// gives theater windows the "Float" control back.
|
||||
// frame-controls: move SteamVR dashboard window controls between the bottom
|
||||
// bar and the More Options (three-dot) menu. A long press (opts.longPressMs;
|
||||
// progress ring from half that time, at most after 1 s) on a bar icon or menu
|
||||
// row opens a "Show in bar" popup; toggling it moves that control in all
|
||||
// windows. Short presses stay stock; the three-dot button can't be moved.
|
||||
// opts.floatInTheater gives theater windows the "Float" control back.
|
||||
//
|
||||
// Target: SteamVR's dashboard page (vrwebhelper, DevTools 127.0.0.1:8087,
|
||||
// title "systemui"). This file is a function expression, called by the file
|
||||
// lib/default.nix (mkPatch) generates: (<this file>)(find, sigs, opts), with
|
||||
// find the finder library (lib/finders.js), sigs this patch's module
|
||||
// signatures (lib/signatures.json, "frame-controls") and opts the options
|
||||
// from frame-controls.nix.
|
||||
// Target: SteamVR dashboard (vrwebhelper, DevTools 127.0.0.1:8087, title
|
||||
// "systemui"). mkPatch patch (see lib/default.nix); opts: { inBar, inMenu,
|
||||
// longPressMs, floatInTheater }.
|
||||
//
|
||||
// Stock frame controls (systemui): each window (Frame) renders its controls
|
||||
// as <FrameControlsItem params={type, action_id}> (type 2 = action button,
|
||||
// 1 = spacer); the items register while rendering and the frame's
|
||||
// <FrameControls> hands the lists to Frame.prototype.SetControlsItems(bottom,
|
||||
// tabHover, additional) (a MobX action). Items inside onlyVisibleIn=
|
||||
// "additional-options" (curvature, dock to a controller) go to `additional`.
|
||||
// systemui draws the controls of every window itself (also the Steam window):
|
||||
// panel vsg-node#legacy-frame-controls-<frameID> from `bottom`, and, while
|
||||
// the menu is open, #legacy-frame-controls-additional-options-<frameID> from
|
||||
// `additional`. Buttons are div.ButtonControl whose React `control` prop is
|
||||
// the item; stock actions run on click (primary button only).
|
||||
// Stock: each Frame renders <FrameControlsItem params={type, action_id}>
|
||||
// (2 = action, 1 = spacer); <FrameControls> passes the lists to the MobX
|
||||
// action Frame.prototype.SetControlsItems(bottom, tabHover, additional)
|
||||
// (onlyVisibleIn="additional-options" items go to `additional`). systemui
|
||||
// draws them as panels vsg-node#legacy-frame-controls-<frameID> (bar) and
|
||||
// #legacy-frame-controls-additional-options-<frameID> (open menu); buttons are
|
||||
// div.ButtonControl with the item as React prop `control`.
|
||||
//
|
||||
// Placement: SetControlsItems is wrapped; the stock lists are remembered per
|
||||
// frame and a re-partitioned copy is stored (stock order kept; a control
|
||||
// moved into the bar joins the group left of the three-dot button, which
|
||||
// keeps a group of its own; adjacent spacer runs are merged so gaps don't
|
||||
// widen). Without a placement differing from stock the stock arrays pass
|
||||
// through unchanged. A change re-applies the remembered lists of every frame
|
||||
// (no React re-render needed). The tab-hover list is untouched.
|
||||
// Placement is global per control type: a control's key is its action icon,
|
||||
// "icon:<enum>" (the same in every window and language; action ids are per
|
||||
// frame instance and app window keys change with every app start). Effective
|
||||
// placement: the popup's choice, else opts.inBar / opts.inMenu, else stock; a
|
||||
// popup choice is dropped when the option for that control changes. Kept in
|
||||
// window.__sfuiFrameControlsState (survives patch restarts and upgrades;
|
||||
// unpatch keeps it) and mirrored to the page's localStorage, so it also
|
||||
// survives SteamVR restarts.
|
||||
// Placement: SetControlsItems is wrapped; stock lists are remembered per frame
|
||||
// and a re-partitioned copy is stored (stock order kept; a control moved to the
|
||||
// bar joins the group left of the three-dot button; adjacent spacer runs are
|
||||
// merged). A change re-applies all remembered lists (no re-render needed).
|
||||
// Keyed per control type by action icon, "icon:<enum>" (action ids are per
|
||||
// frame, app window keys per app start). Effective: popup choice, else
|
||||
// opts.inBar/inMenu, else stock; a popup choice is dropped when that control's
|
||||
// option changes. State in window.__sfuiFrameControlsState (kept across
|
||||
// re-patch and unpatch) and localStorage (survives SteamVR restarts).
|
||||
//
|
||||
// Long press: SteamVR delivers only primary-button laser input to the
|
||||
// dashboard (no right-click; the thumbstick click arrives as nothing), so a
|
||||
// long press is the trigger. Nothing is stopped or restyled while holding;
|
||||
// the timer keeps running when the laser moves or leaves the control and is
|
||||
// cancelled by an early release (or a curvature drag, below). When it completes, the popup opens and
|
||||
// the one click that follows the release on that control is swallowed, so
|
||||
// its stock action doesn't run.
|
||||
// window-curvature (its controls own press-and-drag) contract: its elements
|
||||
// carry the class sfui-curv-ctl; a drag there dispatches a bubbling
|
||||
// Long press: the dashboard only gets primary-button laser input (no right
|
||||
// click; thumbstick click arrives as nothing). Holding changes nothing; the
|
||||
// timer survives the laser leaving the control, an early release cancels it.
|
||||
// On completion the popup opens and the next click on that control is
|
||||
// swallowed so its stock action doesn't run.
|
||||
// Contract with window-curvature (whose controls own press-and-drag): its
|
||||
// elements carry class sfui-curv-ctl; a drag there dispatches a bubbling
|
||||
// CustomEvent 'sfui-curv-dragstart' (cancels the long press before the ring
|
||||
// shows); once the ring shows, the long press takes the press over with
|
||||
// window.__sfuiWindowCurvature.cancelPress(), so drifting doesn't start a
|
||||
// drag. Neither side reads the other's thresholds.
|
||||
// shows); once the ring shows, the long press takes over the press via
|
||||
// window.__sfuiWindowCurvature.cancelPress(). Neither reads the other's
|
||||
// thresholds.
|
||||
//
|
||||
// Popup: in a scene-graph panel of its own, attached like the stock menu, so
|
||||
// neither the bar nor the menu panel changes. The dashboard builds its scene
|
||||
// graph by walking the DOM (vsg-transform elements from their attributes,
|
||||
// other nodes through an element's buildNode()); panel textures are regions
|
||||
// of the page, published per panel through SGApp's embedded-UV table. So:
|
||||
// Popup: its own scene-graph panel so the bar/menu panels don't change. The
|
||||
// dashboard builds its scene graph from the DOM (vsg-transform attributes,
|
||||
// other nodes via element.buildNode()); panel textures are page regions
|
||||
// published through SGApp's embedded-UV table. So, after the bar's transform:
|
||||
// <vsg-transform parent-id=<anchor> translation=...>
|
||||
// <vsg-node> buildNode: a panel with the bar/menu panel's own properties
|
||||
// <vsg-node> buildNode: panel copying the bar/menu panel's properties
|
||||
// (key, meters-per-pixel, curvature origin, laser visibility),
|
||||
// origin bottom centre, own UVs and embedded-UV slot
|
||||
// popup
|
||||
// inserted after the bar's transform. Bar controls: the pressed button's
|
||||
// anchor (the tooltips' anchor), 0.15 up (the tooltips' offset): above the
|
||||
// icon. Menu rows: the three-dot button's popout anchor (from which the stock
|
||||
// menu is placed), centred on the menu and GAP_PX above it. The stock menu
|
||||
// closes when the compositor's input focus leaves the bar and menu panels,
|
||||
// which pointing at the popup does, so while a menu popup is open a close of
|
||||
// that menu is deferred (Frame.prototype.SetControlAdditionalOptionsOpen
|
||||
// wrapped) and settled when the popup closes. The popup closes on a press
|
||||
// outside it, Escape, 1 s after the laser left it and its bar/menu, and after
|
||||
// toggling. Without SGApp/anchors the popup goes into the bar/menu panel.
|
||||
// Anchor: bar -> the button's tooltip anchor, 0.15 up (tooltip offset); menu
|
||||
// -> the three-dot popout anchor, centred on the menu, GAP_PX above it. The
|
||||
// stock menu closes when compositor focus leaves bar and menu, which pointing
|
||||
// at the popup does, so while a menu popup is open that close is deferred
|
||||
// (SetControlAdditionalOptionsOpen wrapped) and settled on popup close. Closes
|
||||
// on a press outside, Escape, 1 s after the laser left popup and bar/menu, and
|
||||
// after toggling. Without SGApp/anchors the popup goes into the bar/menu panel.
|
||||
//
|
||||
// Debugging: window.__sfuiFrameControls: log, dump(), placement(),
|
||||
// setPlacement(name or "icon:N", 'bar' | 'menu' | null), reset().
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
// Reverts frame-controls/patch.js: ends a running long press, closes the
|
||||
// popup (and removes its scene-graph panel), gives every window its stock
|
||||
// control lists back and removes its wrappers (Frame.prototype
|
||||
// SetControlsItems / SetControlAdditionalOptionsOpen; a wrapper something
|
||||
// else wrapped since stays in the chain but is inert), listeners, style and
|
||||
// the Float actions it added to theater windows.
|
||||
// window.__sfuiFrameControlsState (placements, log) and its localStorage
|
||||
// mirror are kept for a re-injection. Safe when not patched.
|
||||
// Reverts frame-controls/patch.js: ends a long press, closes the popup, restores
|
||||
// stock control lists, removes wrappers (a wrapper wrapped by something else
|
||||
// since stays inert in the chain), listeners, style and the theater Float
|
||||
// actions. Keeps window.__sfuiFrameControlsState and its localStorage mirror.
|
||||
// Safe when not patched.
|
||||
(() => {
|
||||
const s = window.__sfuiFrameControls;
|
||||
if (!s) return 'not patched';
|
||||
|
||||
@@ -1,57 +1,26 @@
|
||||
// grid: shows the programs of the VR dashboard's "+" menu (section
|
||||
// #VRDashboard_LaunchNonSteamApp) as a grid of tiles: large icon, name
|
||||
// centred below (up to 2 lines).
|
||||
// grid: shows the programs section of the VR dashboard's "+" menu
|
||||
// (#VRDashboard_LaunchNonSteamApp) as a grid of tiles (icon, name below).
|
||||
// mkPatch patch (see lib/default.nix); opts: { columns, maxRows } (maxRows
|
||||
// null: the stock 600 px max height).
|
||||
//
|
||||
// This file is a function expression, called by the file lib/default.nix
|
||||
// (mkPatch) generates: (<this file>)(find, sigs, opts), with find the finder
|
||||
// library (lib/finders.js) and opts { columns: 4, maxRows: 4 } from
|
||||
// launcher-menu.nix (maxRows null: the list fills up to the menu's stock
|
||||
// 600 px max height).
|
||||
//
|
||||
// Pure restyling, evaluated in SharedJSContext (the bar popups share its
|
||||
// realm): Steam's own items (with their onActivate, focus handling and
|
||||
// sounds) stay where they are, so launching and the other launcher-menu
|
||||
// patches keep working unchanged. A timer attaches a MutationObserver to
|
||||
// every dashboard bar popup document (g_PopupManager); whenever the menu
|
||||
// renders, the programs section is found through React fibers (the section
|
||||
// component, which has a `header` prop, keyed "programs"; the "windows"
|
||||
// section, "add desktop window", stays a list) and its elements get
|
||||
// data-sfui-grid="<role>" markers, which a stylesheet in that document turns
|
||||
// into:
|
||||
// - list: the section (flex column: heading + scroll region) is as wide
|
||||
// as the popup window (100vw, a fixed 300 px);
|
||||
// - panel: the item container becomes a CSS grid of N columns; it still
|
||||
// scrolls inside the stock scroll region, so the heading stays;
|
||||
// - tile internals (wrap, label, iconbox, icon, labelbox, marquee, text)
|
||||
// are laid out as a column;
|
||||
// - scroller: the stock scroll region. With maxRows, its max-height (CSS
|
||||
// variable) is set to the bottom of row maxRows, measured at
|
||||
// layout time (labels have 1 or 2 lines), so the menu shrinks to
|
||||
// that many rows and the rest scrolls;
|
||||
// - fade: the element with Steam's ScrollFade class. Steam computes its
|
||||
// ScrolledToTop/ScrolledToBottom state only on React renders and
|
||||
// scroll events, so it is stale once the grid changes the layout
|
||||
// (e.g. a bottom fade although nothing overflows). The state is
|
||||
// recomputed (same thresholds) on scroll, resize and mutation and
|
||||
// put in data-sfui-fade (none/top/bottom/both), which selects the
|
||||
// same gradients as Steam's stylesheet (var(--scroll-fade-size)).
|
||||
// - shadow: the other stock indicator: the scroller's ::before/::after
|
||||
// shadows (box-shadow in the background colour at the top and
|
||||
// bottom edge), shown by Steam's can-scroll-up/-down classes, which
|
||||
// go stale the same way. The same state goes in data-sfui-shadow
|
||||
// on the scroller and sets the pseudo-elements' opacity. Once
|
||||
// maxRows caps the scroller, the inner panel is what scrolls (as in
|
||||
// Steam's own design; the outer element keeps the shadows fixed),
|
||||
// so the state is read from whichever of the two overflows.
|
||||
// Steam's gamepad navigation derives a panel's layout from its computed
|
||||
// style (display: grid), so up/down/left/right move between tiles.
|
||||
// With the pinned-desktop patch, its pinned row (.sfui-pinned-desktop,
|
||||
// outside the grid) is made slim and its content centred.
|
||||
// Anchors (section keys, `header` prop, ScrollFade classes and gradients)
|
||||
// are checked by scripts/check-signatures.mjs ("launcher-menu-grid" in
|
||||
// lib/signatures.json).
|
||||
// unpatch.js (or a new VERSION or options) calls __sfuiLauncherGrid.stop(),
|
||||
// which removes markers, stylesheets, observers and the timer.
|
||||
// Pure restyling in SharedJSContext (the bar popups share its realm): Steam's
|
||||
// items and handlers stay, so the other launcher-menu patches keep working. A
|
||||
// timer attaches a MutationObserver to every bar popup document
|
||||
// (g_PopupManager); the section is found via React fibers (component with a
|
||||
// `header` prop, key "programs"; "windows" stays a list) and its elements get
|
||||
// data-sfui-grid="<role>" markers that a stylesheet turns into:
|
||||
// - list/panel: full-width section; the item container is an N-column grid
|
||||
// still scrolling inside the stock scroll region (heading stays);
|
||||
// - scroller: with maxRows, max-height = bottom of row maxRows, measured at
|
||||
// layout time (labels have 1 or 2 lines);
|
||||
// - fade/shadow: Steam computes ScrollFade and can-scroll-up/-down only on
|
||||
// renders and scroll events, so they go stale after the grid relayout. The
|
||||
// state is recomputed (Steam's thresholds) into data-sfui-fade/-shadow; once
|
||||
// maxRows caps the scroller, the inner panel is what scrolls.
|
||||
// Gamepad navigation follows the computed display: grid. The pinned-desktop
|
||||
// row (.sfui-pinned-desktop) is made slim and centred.
|
||||
// Anchors: "launcher-menu-grid" in lib/signatures.json.
|
||||
// unpatch.js (or a new VERSION/options) calls __sfuiLauncherGrid.stop().
|
||||
((find, sigs, opts) => {
|
||||
const NAME = 'launcher-menu-grid';
|
||||
const VERSION = 6;
|
||||
@@ -143,9 +112,8 @@ ${PINNED} > [role=button] * { flex-grow: 0 !important; text-align: center !impor
|
||||
const regionsOf = (d) => [...regions].filter(([, r]) => r.d === d).map(([sc]) => sc);
|
||||
const px = (x) => parseFloat(x) || 0;
|
||||
|
||||
// Section of an item: the nearest fiber ancestor that is a function
|
||||
// component with a `header` prop; its key names the section, its first
|
||||
// host descendant is the list container.
|
||||
// Section of an item: nearest function-component fiber with a `header`
|
||||
// prop; its key names the section, its first host node is the list.
|
||||
const sectionOf = (item) => {
|
||||
const f = find.findFiberUp(item, (x) => typeof x.type === 'function' &&
|
||||
x.memoizedProps && typeof x.memoizedProps === 'object' && 'header' in x.memoizedProps, 60);
|
||||
@@ -192,10 +160,8 @@ ${PINNED} > [role=button] * { flex-grow: 0 !important; text-align: center !impor
|
||||
for (let w = label.parentElement; w && w !== item; w = w.parentElement) mark(w, 'wrap');
|
||||
};
|
||||
|
||||
// Scroll indicator state from the actual scroll position (Steam's
|
||||
// thresholds), for both the mask fade and the edge shadows.
|
||||
// The element that actually scrolls: the inner panel once maxRows caps
|
||||
// the scroller, otherwise the scroller.
|
||||
// Scroll state (Steam's thresholds) of the element that actually scrolls:
|
||||
// the inner panel once maxRows caps the scroller, else the scroller.
|
||||
const scrolling = (sc, panel) =>
|
||||
panel && (panel.scrollHeight > panel.clientHeight + 1 || panel.scrollTop > 0) ? panel : sc;
|
||||
const fade = (sc) => {
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
# shellcheck shell=bash
|
||||
# Hicolor fallbacks for desktop entry icons that only Breeze has.
|
||||
#
|
||||
# Usage: steam-frame-icon-fallbacks [EXTRA_NAME...]
|
||||
# BREEZE_APPS=<breeze-icons>/share/icons/breeze/apps: link the Icon= names
|
||||
# of the desktop entries Steam sees (plus EXTRA_NAMEs) that no hicolor
|
||||
# theme dir has but Breeze does, as
|
||||
# $XDG_DATA_HOME/icons/hicolor/scalable/apps/<name>.svg -> Breeze's SVG.
|
||||
# BREEZE_APPS unset: remove all links made before (disabled).
|
||||
# Links made are listed in a manifest; only those, and only while they still
|
||||
# point into a breeze-icons store path, are ever removed. Nothing else in the
|
||||
# icon dir is touched.
|
||||
# BREEZE_APPS=<breeze-icons>/share/icons/breeze/apps: link each Icon= name
|
||||
# (of the entries Steam sees, plus EXTRA_NAMEs) that no hicolor dir has
|
||||
# but Breeze does as $XDG_DATA_HOME/icons/hicolor/scalable/apps/<name>.svg.
|
||||
# BREEZE_APPS unset: remove the links made before.
|
||||
# Only links listed in the manifest and still pointing into breeze-icons are
|
||||
# ever removed.
|
||||
|
||||
data_home=${XDG_DATA_HOME:-$HOME/.local/share}
|
||||
dest=$data_home/icons/hicolor/scalable/apps
|
||||
|
||||
@@ -1,32 +1,20 @@
|
||||
// launch: what happens when a program of the VR dashboard's "+" menu
|
||||
// (#VRDashboard_LaunchNonSteamApp) is activated.
|
||||
// launch: what activating a program in the VR dashboard's "+" menu does.
|
||||
// mkPatch patch (see lib/default.nix); opts: { closeOnLaunch, debounceSeconds }.
|
||||
//
|
||||
// This file is a function expression, called by the file lib/default.nix
|
||||
// (mkPatch) generates: (<this file>)(find, sigs, opts), with find the finder
|
||||
// library (lib/finders.js) and opts the options from launcher-menu.nix, e.g.
|
||||
// { closeOnLaunch: true, debounceSeconds: 10 }.
|
||||
//
|
||||
// Stock, an item's onActivate only calls
|
||||
// SteamClient.Apps.LaunchNonSteamApp(strCmdline) (plus the nav sound); the
|
||||
// "+" popup stays open until the new window takes over, so users click again
|
||||
// and start the program twice. That call is the only use of
|
||||
// LaunchNonSteamApp in Steam's UI and is looked up at call time, so this
|
||||
// patch wraps it in SharedJSContext (the dashboard bar's realm):
|
||||
// - debounceSeconds > 0: a launch of the same command line within that many
|
||||
// seconds of the last accepted one is ignored (console.info, and reported
|
||||
// in the steam-ui-patches journal on the next re-injection);
|
||||
// - closeOnLaunch: after a launch (ignored or not), the "+" popup is closed
|
||||
// through the bar button's own popup handle (closePopup(), what stock does
|
||||
// after adding a desktop window). The handle is found through React props,
|
||||
// not minified names: in any popup document (the dashboard bar's), a
|
||||
// .VRDashboardBarSmallButton element whose fiber ancestors include the bar
|
||||
// button (prop refBarPopopHandle) and, above it, the "+" component (prop
|
||||
// allowLaunchProgram). scripts/check-signatures.mjs checks these names
|
||||
// Stock, an item only calls SteamClient.Apps.LaunchNonSteamApp(cmdline) and
|
||||
// the popup stays open until the new window appears, so users click twice.
|
||||
// That call is its only UI use and is looked up at call time, so it is wrapped
|
||||
// here in SharedJSContext:
|
||||
// - debounceSeconds > 0: the same command line within that many seconds of
|
||||
// the last accepted launch is ignored (console.info; reported in the
|
||||
// steam-ui-patches journal on the next re-injection);
|
||||
// - closeOnLaunch: afterwards the "+" popup is closed via the bar button's
|
||||
// popup handle (closePopup(), as stock does after adding a desktop window),
|
||||
// found through React props: a .VRDashboardBarSmallButton whose fiber
|
||||
// ancestors have refBarPopopHandle and, above, allowLaunchProgram
|
||||
// ("launcher-menu-launch" in lib/signatures.json).
|
||||
// Works for every activation path (pointer, controller, the pinned Desktop
|
||||
// copy, which clicks the stock item). The original is kept as __sfuiOrig
|
||||
// (unpatch.js restores it). Idempotent; bump VERSION when changing the
|
||||
// wrapper.
|
||||
// Covers every activation path (pointer, controller, pinned Desktop copy).
|
||||
// Original kept as __sfuiOrig for unpatch.js; bump VERSION on changes.
|
||||
((find, sigs, opts) => {
|
||||
const VERSION = 2;
|
||||
const NAME = 'launcher-menu-launch';
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
// launcher-menu order: sorts the VR dashboard's "+" menu (non-Steam programs)
|
||||
// alphabetically (case-insensitive), Desktop included.
|
||||
// Steam renders SteamClient.Apps.ScanForInstalledNonSteamApps() in the order
|
||||
// it returns (GLib hash-table order, i.e. random-ish); its hook looks the
|
||||
// function up at call time, so wrapping it here in SharedJSContext is enough.
|
||||
// The original is kept as __sfuiOrig (unpatch.js restores it). Idempotent;
|
||||
// bump VERSION when changing the wrapper.
|
||||
// order: sorts the VR dashboard's "+" menu alphabetically (case-insensitive),
|
||||
// Desktop included. Steam shows ScanForInstalledNonSteamApps() in GLib
|
||||
// hash-table order and looks the function up at call time, so wrapping it in
|
||||
// SharedJSContext suffices. Original kept as __sfuiOrig for unpatch.js; bump
|
||||
// VERSION on changes.
|
||||
(() => {
|
||||
const VERSION = 3;
|
||||
const NAME = 'launcher-menu-order';
|
||||
|
||||
@@ -1,31 +1,16 @@
|
||||
// pinned-desktop: in the VR dashboard's "+" menu (section
|
||||
// #VRDashboard_LaunchNonSteamApp), "Desktop" (the nested Plasma session) is
|
||||
// pinned above or below the scrolling program list instead of scrolling with
|
||||
// it.
|
||||
// pinned-desktop: pins "Desktop" (the nested Plasma session) above or below
|
||||
// the scrolling list of the VR dashboard's "+" menu.
|
||||
// mkPatch patch (see lib/default.nix); opts: { position: "top" | "bottom" }.
|
||||
//
|
||||
// This file is a function expression, called by the file lib/default.nix
|
||||
// (mkPatch) generates: (<this file>)(find, sigs, opts), with find the finder
|
||||
// library (lib/finders.js) and opts { position: "top" } or
|
||||
// { position: "bottom" } from launcher-menu.nix.
|
||||
//
|
||||
// DOM patch, evaluated in SharedJSContext (the bar popups share its realm):
|
||||
// a timer attaches a MutationObserver to every dashboard bar popup document
|
||||
// (g_PopupManager); whenever the menu renders, the stock Desktop item (found
|
||||
// through its React fiber: list key == strExePath "steamos-nested-desktop")
|
||||
// is hidden by CSS and a pinned block is inserted into the list container:
|
||||
// "bottom" appends it after the scroll region (1px separator, then a clone of
|
||||
// the item), "top" inserts it right before the scroll region, i.e. below the
|
||||
// menu heading (clone, then separator). The clone keeps the item's classes,
|
||||
// icon and CSS :hover; clicking it clicks the hidden stock item, so Steam's
|
||||
// own handler runs (nav sound + SteamClient.Apps.LaunchNonSteamApp). The
|
||||
// list container is a flex column with a max-height, so the scroll region
|
||||
// shrinks to make room and the menu keeps its size.
|
||||
// Anchors: React props and keys (the list key, the section's `header` prop)
|
||||
// and the bar popups' window names ("valve.steam.gamepadui.barpopup...");
|
||||
// scripts/check-signatures.mjs checks that Steam's UI still has them
|
||||
// ("launcher-menu-pinned-desktop" in lib/signatures.json).
|
||||
// unpatch.js (or a new VERSION/position) calls __sfuiPinnedDesktop.stop(),
|
||||
// which removes the pinned blocks, CSS, markers, observers and the timer.
|
||||
// DOM patch in SharedJSContext: a timer attaches a MutationObserver to every
|
||||
// bar popup document (g_PopupManager). The stock Desktop item (fiber list key
|
||||
// == strExePath "steamos-nested-desktop") is hidden by CSS and a clone plus a
|
||||
// 1 px separator is inserted before ("top") or after ("bottom") the scroll
|
||||
// region. Clicking the clone clicks the hidden item, so Steam's own handler
|
||||
// runs. The list container is a flex column with a max-height, so the scroll
|
||||
// region shrinks and the menu keeps its size.
|
||||
// Anchors: "launcher-menu-pinned-desktop" in lib/signatures.json.
|
||||
// unpatch.js (or a new VERSION/position) calls __sfuiPinnedDesktop.stop().
|
||||
((find, sigs, opts) => {
|
||||
const NAME = 'launcher-menu-pinned-desktop';
|
||||
const VERSION = 3;
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
// launcher-menu show-all: the VR dashboard's "+" menu lists all programs
|
||||
// without Steam's Developer Mode.
|
||||
// Steam filters the programs of ScanForInstalledNonSteamApps() by the base
|
||||
// name of their executable: some are always hidden (steam, vrurlhandler),
|
||||
// and without Developer Mode (client setting developer_mode_enabled) also a
|
||||
// second list: firewall-config, vlc, dolphin, cmake-gui, plasma-discover,
|
||||
// konsole, systemsettings, qrenderdoc, sh, lxterminal. The menu's filter
|
||||
// spreads that list into its block list only when Developer Mode is off
|
||||
// (`bDevMode || block.push(...devModeOnly)`), and nothing else uses it.
|
||||
// show-all: the VR dashboard's "+" menu lists all programs without Steam's
|
||||
// Developer Mode.
|
||||
// mkPatch patch (see lib/default.nix); no opts; sigs: "launcher-menu-show-all"
|
||||
// (the module exporting the list, plus a check-only anchor for the filter).
|
||||
//
|
||||
// This file is a function expression, called by the file lib/default.nix
|
||||
// (mkPatch) generates: (<this file>)(find, sigs), with find the finder
|
||||
// library (lib/finders.js) and sigs this patch's module signatures
|
||||
// (lib/signatures.json, "launcher-menu-show-all": the module exporting the
|
||||
// list, plus a check-only anchor for the filter). No options.
|
||||
//
|
||||
// The patch gives that one array an own, empty Symbol.iterator, so spreading
|
||||
// it adds nothing; its contents stay as they are (the signature keeps
|
||||
// matching on re-injection) and the Developer Mode setting itself, used
|
||||
// elsewhere (settings pages, controller pairing, ...), is not touched. The
|
||||
// menu re-filters whenever it rescans, i.e. on every open. The array is
|
||||
// remembered as window.__sfuiShowAllApps for unpatch.js. Idempotent.
|
||||
// Steam hides some executables always (steam, vrurlhandler) and, without
|
||||
// Developer Mode, a second list (firewall-config, vlc, dolphin, cmake-gui,
|
||||
// plasma-discover, konsole, systemsettings, qrenderdoc, sh, lxterminal) via
|
||||
// `bDevMode || block.push(...devModeOnly)`; nothing else uses that array.
|
||||
// The patch gives it an own, empty Symbol.iterator, so spreading adds
|
||||
// nothing while its contents (and the signature) stay intact and the setting
|
||||
// itself is untouched. Remembered as window.__sfuiShowAllApps for unpatch.js.
|
||||
((find, sigs) => {
|
||||
const VERSION = 1;
|
||||
let mods;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// Reverts show-all/patch.js: removes the empty iterator from Steam's
|
||||
// Developer Mode app list (remembered as window.__sfuiShowAllApps), so the
|
||||
// "+" menu hides those programs again while Developer Mode is off. Safe when
|
||||
// not patched.
|
||||
// Reverts show-all/patch.js (removes the empty iterator). Safe when not
|
||||
// patched.
|
||||
(() => {
|
||||
const s = window.__sfuiShowAllApps;
|
||||
if (!s) return 'not patched';
|
||||
|
||||
+21
-11
@@ -1,13 +1,23 @@
|
||||
# Shared helpers of the runtime Steam UI patches.
|
||||
# - finders.js: signature-based lookup of webpack modules/exports and React
|
||||
# fibers (no module ids or minified names);
|
||||
# - signatures.json: the signatures the patches use, per patch, also read by
|
||||
# Shared helpers of the runtime Steam UI patches:
|
||||
# - finders.js: signature-based lookup of webpack modules/exports and fibers;
|
||||
# - signatures.json: per-patch signatures, also read by
|
||||
# scripts/check-signatures.mjs (run it after a Steam update);
|
||||
# - hooks.js: shared method hooks (one wrapper per method for all patches,
|
||||
# e.g. the SteamVR dashboard mailbox's SendMessage);
|
||||
# - mkPatch: turns a patch written as a function expression
|
||||
# `(find, sigs, opts, hooks) => …` into the single expression the injectors
|
||||
# evaluate: (<patch>)(<finders.js>, <signatures>, <opts>, <hooks.js>).
|
||||
# - hooks.js: one shared wrapper per hooked method (e.g. SendMessage);
|
||||
# - mkPatch: builds the expression the injector evaluates.
|
||||
#
|
||||
# Patch convention: a patch file is a function expression
|
||||
# (find, sigs, opts, hooks) => <status string or Promise of one>
|
||||
# (trailing parameters may be omitted), called as
|
||||
# (<patch>)(<finders.js>, <signatures>, <opts>, <hooks.js>)
|
||||
# with find = lib/finders.js, sigs = its module signatures, opts = the JSON
|
||||
# options from its module, hooks = lib/hooks.js. The injector re-evaluates it
|
||||
# on every new JS context and periodically, so it must be idempotent: keep
|
||||
# state on a window.__sfui* global (or on the wrapped function), return
|
||||
# "unchanged" when already applied (not logged) and tear down/re-apply when
|
||||
# its VERSION or options differ. Bump VERSION whenever the patch code changes.
|
||||
# A matching unpatch.js (plain expression) reverts it when the service stops.
|
||||
# Patches needing none of the arguments may be plain expressions without
|
||||
# mkPatch (e.g. launcher-menu/order).
|
||||
{ pkgs }:
|
||||
let
|
||||
sigFile = builtins.fromJSON (builtins.readFile ./signatures.json);
|
||||
@@ -17,8 +27,8 @@ in {
|
||||
hooks = ./hooks.js;
|
||||
|
||||
# name: file name (and default signatures entry); src: the patch file;
|
||||
# signatures: module signatures passed as `sigs` (default: the entry `name`
|
||||
# of signatures.json, else none); opts: JSON-serialisable options.
|
||||
# signatures: `sigs` (default: signatures.json entry `name`, else none);
|
||||
# opts: JSON-serialisable options.
|
||||
mkPatch = { name, src, signatures ? sigFile.patches.${name}.modules or { }, opts ? { } }:
|
||||
pkgs.writeText "${name}.js" ''
|
||||
(${builtins.readFile src}
|
||||
|
||||
+10
-14
@@ -1,15 +1,12 @@
|
||||
// finders.js: signature-based lookup of webpack modules, their exports and
|
||||
// React fibers in Steam's (and SteamVR's) web UIs, so runtime patches don't
|
||||
// depend on webpack module ids or minified export names, which change with
|
||||
// every Steam update. In the spirit of Decky Loader's @decky/ui
|
||||
// (findModule, findModuleChild, findInReactTree) and Vencord's `find`.
|
||||
// finders.js: signature-based lookup of webpack modules, exports and React
|
||||
// fibers in Steam's and SteamVR's web UIs, so patches don't depend on module
|
||||
// ids or minified names (which change with every Steam update). Like Decky
|
||||
// Loader's @decky/ui finders and Vencord's `find`.
|
||||
//
|
||||
// This file is a single expression. Evaluated in a page it installs the
|
||||
// library as window.__sfuiFind (unless an equal or newer VERSION is already
|
||||
// there) and evaluates to it; lib/default.nix passes it to the patches that
|
||||
// need it, together with their signatures from signatures.json. The same
|
||||
// file is evaluated by scripts/check-signatures.mjs in Node, so patches and
|
||||
// the offline check match signatures with the same code.
|
||||
// A single expression: installs window.__sfuiFind (unless an equal or newer
|
||||
// VERSION is there) and evaluates to it; mkPatch passes it as `find`.
|
||||
// scripts/check-signatures.mjs evaluates the same file in Node, so the
|
||||
// offline check matches exactly like the patches.
|
||||
//
|
||||
// Signatures (plain JSON, see signatures.json):
|
||||
// text signature, matched against a source text (a module factory's or a
|
||||
@@ -188,9 +185,8 @@
|
||||
}
|
||||
|
||||
// Module signature -> { id, module (exports object), exports: { name: value },
|
||||
// keys: { name: export key } }. Cached per page (this library lives on the
|
||||
// page's window); a cached result is reused while the module factory is
|
||||
// still registered and every export still has the same value.
|
||||
// keys: { name: export key } }. Cached per page while the factory is still
|
||||
// registered and every export keeps its value.
|
||||
const cache = new Map();
|
||||
function resolve(req, sig, name = 'module') {
|
||||
const ck = name + '\n' + JSON.stringify(sig);
|
||||
|
||||
+19
-29
@@ -1,33 +1,23 @@
|
||||
// hooks.js: shared method hooks for runtime patches. Several patches can
|
||||
// intercept the same method (e.g. SendMessage of the SteamVR dashboard's
|
||||
// mailbox, which dashboard-windows and window-curvature both use to rewrite
|
||||
// outgoing scene graphs) through ONE wrapper per method, instead of a chain
|
||||
// of per-patch wrappers: a wrapper can't be taken out of the middle of a
|
||||
// chain, so patches restarted in varying order would pile up inert wrappers
|
||||
// (or run a rewrite twice).
|
||||
// hooks.js: shared method hooks. Patches that intercept the same method (e.g.
|
||||
// the SteamVR dashboard mailbox's SendMessage, used by dashboard-windows and
|
||||
// window-curvature) share ONE wrapper per method: a wrapper can't be removed
|
||||
// from the middle of a chain, so per-patch wrappers restarted in varying
|
||||
// order would pile up (or run a rewrite twice).
|
||||
//
|
||||
// This file is a single expression. Evaluated in a page it installs the
|
||||
// library as window.__sfuiHooks (unless an equal or newer VERSION is already
|
||||
// there; a newer one takes over the registry) and evaluates to it;
|
||||
// lib/default.nix (mkPatch) passes it to every patch as its 4th argument.
|
||||
//
|
||||
// before(obj, method, name, fn) registers (or replaces) hook `name` on
|
||||
// obj[method] (e.g. a class prototype): fn.call(this, args) runs before
|
||||
// the original with the call's arguments array (which it may modify in
|
||||
// place). Installs the wrapper if needed. Idempotent. A hook that
|
||||
// throws is skipped (count and last message in errors[name]).
|
||||
// remove(obj, method, name) unregisters it; once no hook is left the
|
||||
// wrapper is removed too (if nothing wrapped the method since).
|
||||
// has(obj, method, name) registered and the wrapper installed.
|
||||
//
|
||||
// The wrapper (marked fn.__sfuiHooks = method, original in fn.__sfuiOrig)
|
||||
// looks the hooks up per call, so re-registering, removing and upgrading the
|
||||
// library take effect in place. Hooks run once per call, even if a wrapper
|
||||
// ended up in the method's chain twice (then also not for a call the method
|
||||
// makes to itself). Hooks are called in registration order.
|
||||
// Migration: per-patch wrappers from before this library (fn.__sfuiPatch ===
|
||||
// name, original in fn.__sfuiOrig) on top of obj[method] are unwound when
|
||||
// `name` registers.
|
||||
// A single expression: installs window.__sfuiHooks (unless an equal or newer
|
||||
// VERSION is there; a newer one takes over the registry) and evaluates to it;
|
||||
// mkPatch passes it to every patch as `hooks`.
|
||||
// before(obj, method, name, fn) register/replace hook `name`:
|
||||
// fn.call(this, args) runs before the original and may modify the args
|
||||
// array in place. Idempotent. Throwing hooks are skipped and counted in
|
||||
// errors[name].
|
||||
// remove(obj, method, name) unregister; the wrapper goes with the last
|
||||
// hook (if nothing wrapped the method since).
|
||||
// has(obj, method, name) registered and wrapper installed.
|
||||
// The wrapper (fn.__sfuiHooks = method, original in fn.__sfuiOrig) looks hooks
|
||||
// up per call, in registration order, and runs them once per call even if it
|
||||
// is in the chain twice. Legacy per-patch wrappers (fn.__sfuiPatch === name)
|
||||
// on top of obj[method] are unwound when `name` registers.
|
||||
(() => {
|
||||
const VERSION = 1;
|
||||
const G = globalThis;
|
||||
|
||||
@@ -1,65 +1,44 @@
|
||||
// steam-close-button: a Close (X) button on the SteamVR dashboard's main
|
||||
// "Steam" window (Steam's library, overlay valve.steam.gamepadui.main).
|
||||
// Target: SteamVR's dashboard page (vrwebhelper, DevTools 127.0.0.1:8087,
|
||||
// title "systemui").
|
||||
// "Steam" window (overlay valve.steam.gamepadui.main).
|
||||
// Target: SteamVR dashboard (vrwebhelper, DevTools 127.0.0.1:8087, title
|
||||
// "systemui"). mkPatch patch (see lib/default.nix); no options.
|
||||
//
|
||||
// This file is a function expression, called by the file lib/default.nix
|
||||
// (mkPatch) generates: (<this file>)(find, sigs, opts), with find the finder
|
||||
// library (lib/finders.js) and sigs this patch's module signatures
|
||||
// (lib/signatures.json, "steam-close-button": MobX, the dock-location enum,
|
||||
// the frame's `closing` component class, plus check-only anchors for the
|
||||
// dashboard internals used below). No options.
|
||||
//
|
||||
// The button: every dashboard frame has a `closing` component; its X renders
|
||||
// when closing.showCloseButton, i.e. when one of closeMethodPriority's methods
|
||||
// is possible. Method 0 is "componentProps.onCloseRequested exists", and
|
||||
// The button: a frame's `closing` component shows X when a close method is
|
||||
// possible; method 0 is "componentProps.onCloseRequested exists", with
|
||||
// componentProps = {...defaultComponentProps, ...frame.props.componentProps.closing}.
|
||||
// The main frame passes no closing props, so this patch replaces that
|
||||
// instance's plain `defaultComponentProps` field with a copy that adds
|
||||
// onCloseRequested (original kept as the non-enumerable __sfuiOrig), then
|
||||
// re-assigns frame.props (a shallow copy, in a MobX action) so the computed
|
||||
// componentProps re-evaluates and the button appears. React re-assigning
|
||||
// frame.props later keeps it (the defaults are merged every time).
|
||||
// The main frame passes no closing props, so this instance's
|
||||
// defaultComponentProps is replaced by a copy adding onCloseRequested
|
||||
// (original as non-enumerable __sfuiOrig) and frame.props is re-assigned (in a
|
||||
// MobX action) so the computed re-evaluates. Later React props updates keep it.
|
||||
//
|
||||
// Clicking X (RequestClose -> onCloseRequested); the frame can't be
|
||||
// destroyed, so it is put out of view:
|
||||
// Clicking X: the frame can't be destroyed, so it is put out of view:
|
||||
// * undocked (world/theater/hand) -> docked back into the dashboard;
|
||||
// * if it was the active dashboard frame -> the dashboard switches to the
|
||||
// most recently active other frame that is alive, docked in the dashboard
|
||||
// and has a dashboard-bar tab (the call a tab click makes);
|
||||
// * with none -> "bar only": the dashboard stays open with no active frame,
|
||||
// just the dashboard bar (a docked frame renders only while active).
|
||||
// Stock SteamVR has that state (e.g. when a VRLink remote frame vanishes)
|
||||
// but never keeps it, so while bar-only is on and no frame is active:
|
||||
// * if it was active -> switch to the most recently active other frame that
|
||||
// is alive, docked and has a dashboard-bar tab (as a tab click does);
|
||||
// * with none -> "bar only": dashboard open with no active frame.
|
||||
// Stock SteamVR has that state but never keeps it, so while bar-only is on
|
||||
// and no frame is active:
|
||||
// - an instance override of Dashboard.autoSwitchOverlayIfNeeded returns
|
||||
// early (its onDashboardTabsUpdated autorun, and reopening the dashboard
|
||||
// with the Steam button, would otherwise switch right back to Steam);
|
||||
// early (its autorun, and reopening the dashboard, would switch back);
|
||||
// - an instance override of Dashboard.onShowOverlayRequestFromSteam drops
|
||||
// the FIRST ShowOverlay("valve.steam.gamepadui.main") from Steam after each
|
||||
// dashboard open, within STEAM_SHOW_CAP_MS (Steam's main VR window sends
|
||||
// it by itself 50 ms .. 1.7 s after the open). Trade-off: should Steam not
|
||||
// send it, the user's first explicit Steam-menu pick (e.g. Library) in
|
||||
// that session is swallowed once; a second one works.
|
||||
// A Steam tab click (DashboardTabClicked -> switchToFrameInternal) and
|
||||
// SteamVR's own CVRSteamPrivate::SwitchToDashboardOverlay path aren't
|
||||
// filtered. Bar-only ends as soon as any frame becomes active.
|
||||
// the FIRST ShowOverlay(main) from Steam after each dashboard open, within
|
||||
// STEAM_SHOW_CAP_MS (Steam sends it by itself 50 ms .. 1.7 s after open).
|
||||
// Trade-off: if Steam doesn't send it, the user's first explicit Steam-menu
|
||||
// pick in that session is swallowed once.
|
||||
// Steam tab clicks and SteamVR's SwitchToDashboardOverlay aren't filtered.
|
||||
// Bar-only ends as soon as any frame becomes active.
|
||||
//
|
||||
// State that must outlive this instance (bar-only flag, last open time,
|
||||
// pending Steam request, frame history, suppression log) is kept in
|
||||
// window.__sfuiSteamCloseState (schema 1), which upgrades take over.
|
||||
// Teardown (unpatch.js, a newer VERSION) only removes overrides, reactions
|
||||
// and markers: it never switches frames and leaves the state, so a
|
||||
// re-injection (service restart, switch) continues bar-only. It is in-page
|
||||
// only: a dashboard (vrwebhelper) reload starts fresh (see loadState).
|
||||
// State (bar-only flag, open time, pending request, history, log) lives in
|
||||
// window.__sfuiSteamCloseState (schema 1), taken over by upgrades. Teardown
|
||||
// never switches frames and keeps the state, so a re-injection continues
|
||||
// bar-only; a dashboard reload starts fresh.
|
||||
//
|
||||
// Without the Dashboard component, X hides the dashboard. RequestClose shows
|
||||
// a spinner after onCloseRequested (it expects the frame to go away); it is
|
||||
// reset right after. A MobX reaction keeps the history of active frames and
|
||||
// re-applies when the main frame changes (the 15 s re-injection covers the
|
||||
// rest). window.__sfuiSteamClose: plan() (what a click would do now, no side
|
||||
// effects), wouldIgnoreSteamShow(), barOnly, state, teardown().
|
||||
// Results: "patched" / "unchanged", with " (bar-only, N ignored Steam
|
||||
// requests)" while there is something to report, so the injector logs it.
|
||||
// Without the Dashboard component, X hides the dashboard. RequestClose's
|
||||
// spinner (it expects the frame to go away) is reset right after. A MobX
|
||||
// reaction tracks active-frame history and re-applies when the main frame
|
||||
// changes. window.__sfuiSteamClose: plan() (what a click would do),
|
||||
// wouldIgnoreSteamShow(), barOnly, state, teardown(). Result: "patched" /
|
||||
// "unchanged", plus " (bar-only, N ignored Steam requests)" when relevant.
|
||||
((find, sigs, opts) => {
|
||||
const NAME = 'steam-close-button';
|
||||
const VERSION = 5;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
// Reverts steam-close-button/patch.js: removes its overrides, reactions and
|
||||
// markers (the X disappears). Teardown only: it never switches frames and
|
||||
// leaves window.__sfuiSteamCloseState (bar-only flag etc.) in place, so a
|
||||
// re-injection (service restart, switch) continues where it left off; if the
|
||||
// patch is gone for good while bar-only, the dashboard just stays without an
|
||||
// active frame until the next tab click or dashboard open. Safe when not
|
||||
// patched.
|
||||
// Reverts steam-close-button/patch.js (the X disappears). Never switches
|
||||
// frames and keeps window.__sfuiSteamCloseState, so a re-injection continues;
|
||||
// if removed while bar-only, the dashboard stays without an active frame
|
||||
// until the next tab click or open. Safe when not patched.
|
||||
(() => {
|
||||
const s = window.__sfuiSteamClose;
|
||||
if (!s) return 'not patched';
|
||||
|
||||
@@ -12,18 +12,12 @@
|
||||
// keymap -- are typed by the helper instead
|
||||
// - Enter types Return for app windows, even if a Steam search box had focus
|
||||
// before (Steam then labels it "Search" and closes the keyboard instead)
|
||||
// Everything goes through the CDP binding window.__vrkbdKey("<op>:<arg>"),
|
||||
// executed by helper.mjs with xdotool on :0. Every replaced function keeps its
|
||||
// original as __vrkbdOrig (unpatch.js restores them, through the references
|
||||
// this patch remembers in window.__vrkbdRefs).
|
||||
// Key output goes through the CDP binding window.__vrkbdKey("<op>:<arg>"),
|
||||
// run by helper.mjs with xdotool on :0. Replaced functions keep their
|
||||
// original as __vrkbdOrig; window.__vrkbdRefs etc. are for unpatch.js.
|
||||
//
|
||||
// This file is a function expression, called by the file lib/default.nix
|
||||
// (mkPatch) generates: (<this file>)(find, sigs, opts), with find the finder
|
||||
// library (lib/finders.js) and sigs this patch's module signatures
|
||||
// (lib/signatures.json, "steam-keyboard-patch"). Steam's webpack modules are
|
||||
// located by those signatures, not by module id or minified export name; if
|
||||
// one doesn't match, the patch returns an error and changes nothing.
|
||||
// Idempotent: safe to evaluate repeatedly.
|
||||
// mkPatch patch (see lib/default.nix); no options. On a signature mismatch
|
||||
// it returns an error and changes nothing. Idempotent.
|
||||
((find, sigs) => {
|
||||
const VERSION = 10;
|
||||
const send = (msg) => window.__vrkbdKey && window.__vrkbdKey(msg);
|
||||
@@ -51,13 +45,11 @@
|
||||
};
|
||||
|
||||
// ---- bottom row ------------------------------------------------------------
|
||||
// The keyboard window has a fixed height, so no extra row: Esc/Ctrl/Alt go
|
||||
// left of the space bar, and Steam's arrow keys become four separate keys
|
||||
// (stock layouts pair them as [Left, Up-when-shifted], [Right, Down-when-shifted]).
|
||||
// Every entry is a [normal, shifted, altgr] triple: Steam's stock bottom row
|
||||
// lacks explicit variants, so with Shift the close icon jumps and with AltGr
|
||||
// keys turn into empty full-width panels. null as the shifted variant means
|
||||
// "same key, no shift label".
|
||||
// Fixed window height, so no extra row: Esc/Ctrl/Alt go left of the space
|
||||
// bar and the stock paired arrows ([Left, Up-shifted], [Right, Down-shifted])
|
||||
// become four keys. Entries are explicit [normal, shifted, altgr] triples:
|
||||
// without them Shift moves the close icon and AltGr yields empty
|
||||
// full-width keys. null shifted = same key, no shift label.
|
||||
const HALF = 2; // key type "Half"; widths set by the stylesheet
|
||||
const k = (key, label) => ({ key, label, type: HALF });
|
||||
const same = (x) => [x, null, x];
|
||||
@@ -89,11 +81,10 @@
|
||||
}
|
||||
|
||||
// ---- text: characters Steam's key emulation can't produce --------------------
|
||||
// All keyboard text for gamescope windows ends up in
|
||||
// SteamClient.Input.ControllerKeyboardSendText (via several paths, incl. the
|
||||
// VR text override), which only maps plain ASCII on the base/shift levels;
|
||||
// everything else comes out as "1". Hand those characters to the helper, in
|
||||
// order; the rest passes through unchanged. (helper.mjs checks the same set.)
|
||||
// All text for gamescope windows ends in ControllerKeyboardSendText, which
|
||||
// only maps plain ASCII on the base/shift levels (else "1"). Those
|
||||
// characters go to the helper, in order; the rest passes through.
|
||||
// (helper.mjs checks the same set.)
|
||||
const viaHelper = (c) => c.codePointAt(0) > 127 || '|@{[]}\\~^`'.includes(c);
|
||||
wrap(SteamClient.Input, 'ControllerKeyboardSendText', (orig) => function (text, ...rest) {
|
||||
if (typeof text !== 'string') return orig.call(this, text, ...rest);
|
||||
@@ -108,16 +99,13 @@
|
||||
});
|
||||
|
||||
// ---- Enter for app windows ------------------------------------------------------
|
||||
// The manager keeps the props of the last focused Steam text field
|
||||
// (m_ActiveElementProps, set on DOM focus) until that field gets a gamepad
|
||||
// blur, which doesn't happen when you leave the dashboard. Search boxes
|
||||
// carry strEnterKeyLabel "#SearchEnterKeyLabel" ("Suchen") and an
|
||||
// onEnterKeyPress returning "VKClose", so when the keyboard is then opened
|
||||
// for an app window, Enter shows "Suchen", runs the Steam search and hides
|
||||
// the keyboard instead of typing Return. While the keyboard serves
|
||||
// something other than this Steam UI (the same test Steam uses for its
|
||||
// text dispatch, in the VirtualKeyboardManager's module), ignore those
|
||||
// props and the dismiss-on-Enter flag.
|
||||
// The manager keeps the last focused Steam text field's props
|
||||
// (m_ActiveElementProps) until a gamepad blur, which leaving the dashboard
|
||||
// doesn't send. A search box's props (Enter label "Search", onEnterKeyPress
|
||||
// -> "VKClose") then make Enter search and hide the keyboard in app windows.
|
||||
// While the keyboard serves something other than this Steam UI (Steam's own
|
||||
// test, VirtualKeyboardManager module), ignore those props and the
|
||||
// dismiss-on-Enter flag.
|
||||
const forOther = (m) => {
|
||||
const s = Status.VRKeyboardStatus, ui = m.m_Instance;
|
||||
return !!s?.bIsOpen && !(s.sOverlayKey && s.sOverlayKey === ui?.GetVROverlayKey?.()) &&
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Reverts patch.js in Steam's SharedJSContext; evaluated by helper.mjs
|
||||
// when it stops (service stopped, module disabled). Every patched function
|
||||
// keeps its original as __vrkbdOrig; the patched objects Steam doesn't expose
|
||||
// globally were remembered by patch.js (window.__vrkbdRefs, __vrkbdLayouts,
|
||||
// __vrkbdProto, __vrkbdInst). Safe to run when nothing is patched.
|
||||
// Reverts patch.js in Steam's SharedJSContext (run by helper.mjs on stop).
|
||||
// Patched functions keep their original as __vrkbdOrig; objects Steam doesn't
|
||||
// expose are in window.__vrkbdRefs/__vrkbdLayouts/__vrkbdProto/__vrkbdInst.
|
||||
// Safe when not patched.
|
||||
(() => {
|
||||
const refs = window.__vrkbdRefs;
|
||||
if (!refs && !window.__vrkbdProto && !window.__vrkbdLayouts) return 'not patched';
|
||||
|
||||
@@ -1,83 +1,62 @@
|
||||
// window-curvature: adjustable curvature per SteamVR dashboard window. The
|
||||
// stock "Toggle Curvature" control becomes a wheel wherever it is shown: the
|
||||
// row of a window's More Options (three-dot) menu (with the value on the
|
||||
// right of the row) and, when the control sits in the window's bottom bar
|
||||
// (e.g. moved there by frame-controls), that bar button (no value shown:
|
||||
// the steps and snap points are felt as controller haptics). Click toggles
|
||||
// (curved -> flat, flat -> stock curve), dragging up/down with the laser sets
|
||||
// the curvature live.
|
||||
// stock "Toggle Curvature" control becomes a wheel: in the More Options menu
|
||||
// row (value shown on the right) and, if the control is in the bottom bar
|
||||
// (e.g. via frame-controls), on that bar button (no value; steps and snap
|
||||
// points are felt as haptics). Click toggles (curved -> flat, flat -> stock
|
||||
// curve); dragging up/down with the laser sets the curvature live.
|
||||
//
|
||||
// Target: SteamVR's dashboard page (vrwebhelper, DevTools 127.0.0.1:8087,
|
||||
// title "systemui"). This file is a function expression, called by the file
|
||||
// lib/default.nix (mkPatch) generates: (<this file>)(find, sigs, opts, hooks),
|
||||
// with find the finder library (lib/finders.js), sigs this patch's module
|
||||
// signatures (lib/signatures.json, "window-curvature"), opts the options from
|
||||
// window-curvature.nix and hooks the shared method hooks (lib/hooks.js).
|
||||
// Target: SteamVR dashboard (vrwebhelper, DevTools 127.0.0.1:8087, title
|
||||
// "systemui"). mkPatch patch (see lib/default.nix); opts: { default, max,
|
||||
// step, snap, snapPoints, dragThreshold, dragPixelsPerUnit,
|
||||
// barDragPixelsPerUnit, barDragRoom, haptics }.
|
||||
//
|
||||
// How stock curvature works (systemui's `curvature` frame component, found
|
||||
// by signature; frame.curvature):
|
||||
// shouldCurve on/off: m_bCurveOverride (set by ToggleCurvature(), cleared
|
||||
// when the dock location changes), else the stock default:
|
||||
// curved when docked in the dashboard, theater per setting,
|
||||
// flat elsewhere (world, hands).
|
||||
// curvatureOriginDistance = shouldCurve ? DashboardStore.curvatureDistance
|
||||
// (dashboard distance + 1.8 m, e.g. 2.95) : 1000.
|
||||
// The frame renders a transform node with id curvatureTransformOriginID
|
||||
// ("frame:<id>:curvature-origin") at translation z = that distance, and
|
||||
// every panel of the frame references it as "curvature-origin-id";
|
||||
// vrcompositor bends the panels onto a cylinder around that point. So
|
||||
// curvature = 1 / radius, and "flat" is just a very distant origin (1000).
|
||||
// Those computed properties can't be overridden (non-configurable MobX
|
||||
// instance properties), so this patch hooks the mailbox's SendMessage
|
||||
// (lib/hooks.js, shared with dashboard-windows) and rewrites the origin's
|
||||
// translation in outgoing scene graphs: distance = stock distance / value,
|
||||
// value 1 = SteamVR's stock curve, 2 = twice as curved (half the radius),
|
||||
// 0 = flat (the stock toggle off). On/off stays the stock state
|
||||
// (ToggleCurvature), so the stock toggle and this control always agree.
|
||||
// Stock curvature (frame.curvature, systemui's `curvature` component):
|
||||
// shouldCurve = m_bCurveOverride (set by ToggleCurvature(), cleared on dock
|
||||
// change) else curved when docked in the dashboard, theater per setting, flat
|
||||
// elsewhere. The frame renders transform "frame:<id>:curvature-origin" at
|
||||
// z = shouldCurve ? DashboardStore.curvatureDistance (dashboard distance +
|
||||
// 1.8 m) : 1000; its panels reference it as "curvature-origin-id" and
|
||||
// vrcompositor bends them onto a cylinder around it (curvature = 1/radius).
|
||||
// Those MobX properties are non-configurable, so this patch hooks the mailbox
|
||||
// SendMessage (lib/hooks.js, shared with dashboard-windows) and rewrites the
|
||||
// origin's z in outgoing scene graphs to stock / value (1 = stock, 2 = half
|
||||
// the radius, 0 = flat = stock toggle off). On/off stays the stock state, so
|
||||
// stock toggle and wheel always agree.
|
||||
//
|
||||
// Values are kept per window (key: the frame's first overlay key) in
|
||||
// window.__sfuiWindowCurvatureState (schema 1: { values, log }), which
|
||||
// unpatch and upgrades leave in place (not persisted across a dashboard
|
||||
// reload / SteamVR restart). A window without its own value uses
|
||||
// opts.default when placed in the world or on a hand, and 1 (stock) when
|
||||
// docked in the dashboard or the theater, so the docked Steam window stays
|
||||
// concentric with the dashboard bar.
|
||||
// Values per window (key: first overlay key) in
|
||||
// window.__sfuiWindowCurvatureState (schema 1: { values, log }); kept across
|
||||
// re-patch and unpatch, not across a dashboard reload. Without a stored value:
|
||||
// opts.default in the world or on a hand, 1 when docked in the dashboard or
|
||||
// theater (so the Steam window stays concentric with the dashboard bar).
|
||||
//
|
||||
// Bar button: the bar panel (vsg-node#legacy-frame-controls-<frameID>,
|
||||
// origin TopCenter) is one button high, and the laser's position stops at
|
||||
// the edge of the pressed panel. While a drag on the bar button runs, the
|
||||
// panel gets opts.barDragRoom px of transparent padding above and below
|
||||
// (stock window.forceLayoutUpdate() re-measures it), and the panel's origin
|
||||
// is moved by the same amount in outgoing scene graphs, so the bar stays
|
||||
// where it is in VR. The padding moves the bar down in page coordinates;
|
||||
// laser events the compositor still maps with the old layout are recognised
|
||||
// (the laser moves continuously) and shifted. opts.barDragPixelsPerUnit
|
||||
// sets the drag speed there.
|
||||
// Bar button: the bar panel (#legacy-frame-controls-<frameID>, origin
|
||||
// TopCenter) is one button high and the laser stops at the pressed panel's
|
||||
// edge. During a bar drag the panel gets opts.barDragRoom px of transparent
|
||||
// padding above and below (window.forceLayoutUpdate() re-measures) and its
|
||||
// origin is compensated in outgoing scene graphs so the bar stays put in VR.
|
||||
// Laser events still mapped with the old layout are detected and shifted.
|
||||
//
|
||||
// Haptics (opts.haptics): VRHTML.VROverlay.TriggerOverlayHapticEffect with
|
||||
// the stock EOverlayHapticEffect values (sigs.hapticEffects): SlidingEdge at
|
||||
// 0 and max, Snap at a snap point, Sliding for other steps (at most every
|
||||
// 30 ms).
|
||||
// Haptics (opts.haptics): VROverlay.TriggerOverlayHapticEffect with stock
|
||||
// EOverlayHapticEffect values: SlidingEdge at 0/max, Snap at snap points,
|
||||
// Sliding otherwise (at most every 30 ms).
|
||||
//
|
||||
// Contract with other patches (e.g. a long press on frame controls): this
|
||||
// patch owns press-and-drag on its controls.
|
||||
// Contract with other patches (e.g. frame-controls' long press); this patch
|
||||
// owns press-and-drag on its controls:
|
||||
// - Every element it drives has the class `sfui-curv-ctl`.
|
||||
// - When a press on one becomes a drag (opts.dragThreshold px vertical),
|
||||
// it dispatches a bubbling CustomEvent `sfui-curv-dragstart` on the
|
||||
// element (detail { frameID, where: 'menu' | 'bar' }); when that drag
|
||||
// ends (release or cancelPress), `sfui-curv-dragend`.
|
||||
// - window.__sfuiWindowCurvature.cancelPress() ends the current press on
|
||||
// any of its controls without its click (a drag stays at its value);
|
||||
// returns whether a press was active.
|
||||
// A patch with its own gesture on these elements lets mousemove through
|
||||
// while that gesture is undecided, drops it on `sfui-curv-dragstart`, and
|
||||
// calls cancelPress() when it takes the press over. Neither side reads the
|
||||
// other's thresholds or restores the other's state afterwards.
|
||||
// - When a press becomes a drag (opts.dragThreshold px vertical) it
|
||||
// dispatches a bubbling CustomEvent `sfui-curv-dragstart` on the element
|
||||
// (detail { frameID, where: 'menu' | 'bar' }); when that drag ends
|
||||
// (release or cancelPress), `sfui-curv-dragend`.
|
||||
// - window.__sfuiWindowCurvature.cancelPress() ends the current press
|
||||
// without its click (a drag keeps its value); returns whether a press
|
||||
// was active.
|
||||
// A patch with its own gesture on these elements lets mousemove through while
|
||||
// undecided, drops its gesture on `sfui-curv-dragstart`, and calls
|
||||
// cancelPress() when it takes the press over. Neither side reads the other's
|
||||
// thresholds or restores the other's state.
|
||||
//
|
||||
// Debugging: window.__sfuiWindowCurvature: dump() (per frame: dock, on/off,
|
||||
// stored/applied value, distances, controls), hits (scene-graph rewrite and
|
||||
// haptic counters), log (last events, also in the state), setValue(frameID,
|
||||
// v), controls(), cancelPress().
|
||||
// Debugging: window.__sfuiWindowCurvature: dump(), hits (rewrite/haptic
|
||||
// counters), log, setValue(frameID, v), controls(), cancelPress().
|
||||
((find, sigs, opts, hooks) => {
|
||||
const NAME = 'window-curvature';
|
||||
const VERSION = 11;
|
||||
@@ -230,11 +209,8 @@
|
||||
resend();
|
||||
};
|
||||
|
||||
// Sets the shown value of a frame: 0 = curvature off, > 0 = on with that
|
||||
// strength. On/off goes through the stock ToggleCurvature (what the
|
||||
// control's stock click invokes), so Steam's state and the icon match.
|
||||
// Going to 0 forgets the window's value (turning it on again, e.g. by
|
||||
// docking it in the dashboard, uses the default).
|
||||
// 0 = off, > 0 = on with that strength. On/off via stock ToggleCurvature so
|
||||
// Steam's state and icon match. 0 forgets the stored value.
|
||||
const setValue = (f, v, why) => {
|
||||
const c = curvatureOf(f);
|
||||
if (!c) return;
|
||||
@@ -283,10 +259,9 @@ ${ROW}:last-child > .sfui-curv-ind { margin-bottom: -14px; }
|
||||
document.head.appendChild(s);
|
||||
};
|
||||
|
||||
// The frame's Toggle Curvature action: a toggle (invocation 2) with the
|
||||
// curvature icons. Its menu row is found by position among the menu's
|
||||
// rows, checked against (else looked up by) the action's label; its bar
|
||||
// button by the React `control` prop of the bar's buttons.
|
||||
// Toggle Curvature action: invocation 2 with the curvature icons. Menu row:
|
||||
// by position, verified (else found) by label; bar button: by React
|
||||
// `control` prop.
|
||||
const isCurvatureAction = (a) => {
|
||||
const p = a?.partialParams;
|
||||
return p?.invocation === 2 && p?.icon?.enum === ICON_OFF && p?.icon_active?.enum === ICON_ON;
|
||||
@@ -315,11 +290,9 @@ ${ROW}:last-child > .sfui-curv-ind { margin-bottom: -14px; }
|
||||
return null;
|
||||
};
|
||||
|
||||
// Digits have no descenders, so their ink sits above the centre of the line
|
||||
// box that translateY(-50%) centres. The ink box is measured once (canvas
|
||||
// measureText: font metrics vs. actual digit bounds) and the number shifted
|
||||
// by the difference, so it is optically centred between the arrows.
|
||||
// (CSS text-box-trim would do this; Steam's Chromium lacks it.)
|
||||
// Digits have no descenders, so translateY(-50%) centres them too high;
|
||||
// shift by the measured ink offset (canvas measureText) to centre them
|
||||
// optically between the arrows. (Steam's Chromium lacks text-box-trim.)
|
||||
let ctx2d;
|
||||
const inkShift = (el) => {
|
||||
const cs = getComputedStyle(el);
|
||||
@@ -356,15 +329,11 @@ ${ROW}:last-child > .sfui-curv-ind { margin-bottom: -14px; }
|
||||
}
|
||||
|
||||
// ---- input -----------------------------------------------------------------------------
|
||||
// Every press/click on a control is stopped at the element, so React
|
||||
// (listening at the root) never runs the stock onClick; the click is
|
||||
// re-implemented on mouseup when the press never became a drag. Drag:
|
||||
// vertical and relative (the cylinder bends horizontally, so the pointer's
|
||||
// y on the panel barely moves while the curvature changes). A press
|
||||
// becomes a drag after dragThreshold px (then re-based, so there is no
|
||||
// jump); the value follows at dragPixelsPerUnit (bar: barDragPixelsPerUnit)
|
||||
// px per 1.0, see dragValue. One press at a time, window capture listeners
|
||||
// while it lasts.
|
||||
// Presses/clicks are stopped at the element so React (root listener) never
|
||||
// runs the stock onClick; a press that never became a drag is a click on
|
||||
// mouseup. Drag is vertical and relative (the cylinder bends horizontally,
|
||||
// so y barely moves as curvature changes); after dragThreshold px it
|
||||
// re-bases (no jump). One press at a time, window capture listeners.
|
||||
const stop = (e) => e.stopPropagation();
|
||||
const STOPPED = ['click', 'dblclick', 'mouseup', 'pointerdown', 'pointerup', 'contextmenu'];
|
||||
let press = null; // { c, y0, v0, last, moved, shift, stale }
|
||||
@@ -518,9 +487,9 @@ ${ROW}:last-child > .sfui-curv-ind { margin-bottom: -14px; }
|
||||
return 'ok';
|
||||
};
|
||||
|
||||
// Wanted controls: open menus, and bars that hold the curvature action;
|
||||
// injected with retries until React has rendered them (bars only while
|
||||
// the frame is visible: a hidden frame has no bar panel).
|
||||
// Inject into open menus and bars holding the curvature action, retrying
|
||||
// until React rendered them (bars only while visible: hidden frames have no
|
||||
// bar panel).
|
||||
let syncTimer = null, retries = 0;
|
||||
const failed = new Map(); // key -> last reason logged
|
||||
const sync = () => {
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
// Reverts window-curvature/patch.js: ends a running press or drag, removes
|
||||
// the control from open menus and bar buttons (class, listeners, value
|
||||
// indicator, a bar's drag room), its style, reactions and SendMessage hook
|
||||
// (the shared wrapper goes too once no other patch uses it) and resends the
|
||||
// scene graph, so windows get the stock curve radius back. Curvature on/off
|
||||
// is stock state and stays as it is. window.__sfuiWindowCurvatureState
|
||||
// (per-window values) is kept for a re-injection. Safe when not patched.
|
||||
// Reverts window-curvature/patch.js: ends a press, removes controls, style,
|
||||
// reactions and the SendMessage hook, and resends the scene graph (stock
|
||||
// radius back). Curvature on/off is stock state and stays.
|
||||
// window.__sfuiWindowCurvatureState is kept. Safe when not patched.
|
||||
(() => {
|
||||
const s = window.__sfuiWindowCurvature;
|
||||
if (!s) return 'not patched';
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
// check-signatures.mjs: checks, without Steam running and without a browser,
|
||||
// that every finder signature the runtime UI patches use (modules/lib/
|
||||
// signatures.json) still matches the installed Steam / SteamVR web UI
|
||||
// bundles: each module signature must match exactly one webpack module, each
|
||||
// export signature exactly one export of it, and the strings a patch relies
|
||||
// on ("expects") should still be there; a "stylesheet" signature (CSS a patch
|
||||
// relies on, e.g. a variable) must match exactly one stylesheet of the
|
||||
// bundle's "styles" directory. Run it after a Steam update:
|
||||
// check-signatures.mjs: checks offline (no Steam, no browser) that every
|
||||
// signature in modules/lib/signatures.json still matches the installed Steam /
|
||||
// SteamVR web UI bundles: module and export signatures exactly once,
|
||||
// "expects" strings still present (warnings), "stylesheet" signatures exactly
|
||||
// one file of the bundle's "styles" directory. Run it after a Steam update:
|
||||
//
|
||||
// nix shell nixpkgs#nodejs -c node scripts/check-signatures.mjs
|
||||
//
|
||||
@@ -20,12 +17,10 @@
|
||||
// Exit status: 0 all found, 1 something missing/ambiguous (or a warning with
|
||||
// --strict), 2 usage/IO error.
|
||||
//
|
||||
// Modules are extracted by webpack-modules.mjs (factory sources exactly as
|
||||
// Function.prototype.toString sees them in the page). To check export
|
||||
// signatures, the matched module's factory is run in a throwaway VM context
|
||||
// in which every import and unknown global is an inert stub, so top-level
|
||||
// definitions (objects, functions, classes, singletons) exist and are
|
||||
// matched with the same code the patches use (modules/lib/finders.js).
|
||||
// Modules come from webpack-modules.mjs (factory sources as the page's
|
||||
// Function.prototype.toString sees them). For export signatures the matched
|
||||
// factory runs in a throwaway VM context where imports and unknown globals
|
||||
// are inert stubs, and is matched with modules/lib/finders.js.
|
||||
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
@@ -5,14 +5,11 @@
|
||||
// const mods = loadBundles('/home/deck/.local/share/Steam/steamui');
|
||||
// // Map "<id>" -> { id, file, factory, source }
|
||||
//
|
||||
// Module maps are object literals of `<id>: <factory>` entries, found at
|
||||
// - `.push([[<chunk ids>],{…}` (chunk files: webpackChunk<name>.push), and
|
||||
// - `<x>={<id>:…` (the runtime's own modules, e.g. library.js).
|
||||
// A small tokenizer (strings, templates, comments, regex literals) finds the
|
||||
// literal's closing brace; the literal alone is then evaluated in a fresh VM
|
||||
// context, which only creates the factory functions (nothing runs), so
|
||||
// `source` is exactly Function.prototype.toString of the factory, as a
|
||||
// finder sees it in the running page (require.m[id]).
|
||||
// Module maps are object literals of `<id>: <factory>`, found at
|
||||
// `.push([[<chunk ids>],{…}` (chunk files) and `<x>={<id>:…` (the runtime's
|
||||
// own modules). A small tokenizer finds the closing brace; the literal alone
|
||||
// is evaluated in a fresh VM context (defines factories, runs nothing), so
|
||||
// `source` equals Function.prototype.toString as a finder sees it.
|
||||
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import vm from 'node:vm';
|
||||
|
||||
Reference in new issue
Block a user