diff --git a/README.md b/README.md
index 8ccb440..664162b 100644
--- a/README.md
+++ b/README.md
@@ -617,6 +617,7 @@ npm test # 200+ tests: config and data migration, poses, intera
# audio, focus and quiet hours, mail and calendar, site drift
npm run lint # eslint over src, tests, scripts and site (what CI runs)
npm run test:boot # launch the real app and assert it renders a frame
+npm run check:hook # does the global input hook load on this Electron?
npm run poses:cat # previews/cat-poses.png (every activity x every coat)
npm run poses:dog # the same for the Black Lab
npm run frames:import --
# import painted PNGs as baked poses
diff --git a/package-lock.json b/package-lock.json
index dbde660..2e3ea23 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,13 +16,23 @@
"devDependencies": {
"@eslint/js": "^10.0.1",
"@ffmpeg-installer/ffmpeg": "^1.1.0",
- "electron": "^33.0.0",
+ "electron": "^44.0.0",
"electron-builder": "^26.15.3",
"eslint": "^10.9.1",
"gifenc": "^1.0.3",
"globals": "^17.11.0"
}
},
+ "node_modules/@electron-internal/extract-zip": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.5.tgz",
+ "integrity": "sha512-+bqFCP98pLI0Tt0XQo1TmlXtwjWchISndDOxCkEcIuUgXWpBnLyRI+2DU+mesvnMMX6L1XDqYNA0lXNDHd/yiA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=22.12.0"
+ }
+ },
"node_modules/@electron/asar": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz",
@@ -127,25 +137,48 @@
}
},
"node_modules/@electron/get": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz",
- "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.1.0.tgz",
+ "integrity": "sha512-3kSBtG8ObcTVfXanm5vVJ6UnBLEVmVsRk1M+vGqCuMBV+XLCbJYuWQful+yIy0GQDsSlK0kHEriEHn7SPk4EnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.1.1",
- "env-paths": "^2.2.0",
- "fs-extra": "^8.1.0",
- "got": "^11.8.5",
+ "env-paths": "^3.0.0",
+ "graceful-fs": "^4.2.11",
"progress": "^2.0.3",
- "semver": "^6.2.0",
+ "semver": "^7.6.3",
"sumchecker": "^3.0.1"
},
"engines": {
- "node": ">=12"
+ "node": ">=22.12.0"
},
"optionalDependencies": {
- "global-agent": "^3.0.0"
+ "undici": "^7.24.4"
+ }
+ },
+ "node_modules/@electron/get/node_modules/env-paths": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
+ "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@electron/get/node_modules/undici": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
+ "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=20.18.1"
}
},
"node_modules/@electron/notarize": {
@@ -1051,13 +1084,13 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "20.19.41",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz",
- "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==",
+ "version": "24.13.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
+ "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~6.21.0"
+ "undici-types": "~7.18.0"
}
},
"node_modules/@types/responselike": {
@@ -1070,17 +1103,6 @@
"@types/node": "*"
}
},
- "node_modules/@types/yauzl": {
- "version": "2.10.3",
- "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
- "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@types/node": "*"
- }
- },
"node_modules/@xmldom/xmldom": {
"version": "0.8.15",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.15.tgz",
@@ -1541,16 +1563,6 @@
"node": "20 || >=22"
}
},
- "node_modules/buffer-crc32": {
- "version": "0.2.13",
- "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
- "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "*"
- }
- },
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -2142,22 +2154,22 @@
}
},
"node_modules/electron": {
- "version": "33.4.11",
- "resolved": "https://registry.npmjs.org/electron/-/electron-33.4.11.tgz",
- "integrity": "sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==",
+ "version": "44.0.0",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-44.0.0.tgz",
+ "integrity": "sha512-FkTqPrFPZYljdPI5b7KORGsJTd6FgUQDefl5MrU3Xz9R87pAj9JLreIjDqcRN8hJIkFHIou0o8kKzvcpT9qiRQ==",
"dev": true,
- "hasInstallScript": true,
"license": "MIT",
"dependencies": {
- "@electron/get": "^2.0.0",
- "@types/node": "^20.9.0",
- "extract-zip": "^2.0.1"
+ "@electron-internal/extract-zip": "^1.0.1",
+ "@electron/get": "^5.0.0",
+ "@types/node": "^24.9.0"
},
"bin": {
- "electron": "cli.js"
+ "electron": "cli.js",
+ "install-electron": "install.js"
},
"engines": {
- "node": ">= 12.20.55"
+ "node": ">= 22.12.0"
}
},
"node_modules/electron-builder": {
@@ -2616,27 +2628,6 @@
"dev": true,
"license": "Apache-2.0"
},
- "node_modules/extract-zip": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
- "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "debug": "^4.1.1",
- "get-stream": "^5.1.0",
- "yauzl": "^2.10.0"
- },
- "bin": {
- "extract-zip": "cli.js"
- },
- "engines": {
- "node": ">= 10.17.0"
- },
- "optionalDependencies": {
- "@types/yauzl": "^2.9.1"
- }
- },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -2675,16 +2666,6 @@
],
"license": "BSD-3-Clause"
},
- "node_modules/fd-slicer": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
- "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "pend": "~1.2.0"
- }
- },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -2811,21 +2792,6 @@
"node": ">= 6"
}
},
- "node_modules/fs-extra": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
- "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.0",
- "jsonfile": "^4.0.0",
- "universalify": "^0.1.0"
- },
- "engines": {
- "node": ">=6 <7 || >=8"
- }
- },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -3000,20 +2966,6 @@
"node": ">=10.0"
}
},
- "node_modules/global-agent/node_modules/semver": {
- "version": "7.8.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz",
- "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==",
- "dev": true,
- "license": "ISC",
- "optional": true,
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/globals": {
"version": "17.11.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz",
@@ -3739,19 +3691,6 @@
"node": ">=22.12.0"
}
},
- "node_modules/node-abi/node_modules/semver": {
- "version": "7.8.5",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
- "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/node-api-version": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz",
@@ -3762,19 +3701,6 @@
"semver": "^7.3.5"
}
},
- "node_modules/node-api-version/node_modules/semver": {
- "version": "7.8.5",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
- "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/node-gyp": {
"version": "12.4.0",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz",
@@ -3821,19 +3747,6 @@
"node": ">=20"
}
},
- "node_modules/node-gyp/node_modules/semver": {
- "version": "7.8.5",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
- "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/node-gyp/node_modules/which": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz",
@@ -4034,13 +3947,6 @@
"url": "https://github.com/sponsors/jet2jet"
}
},
- "node_modules/pend": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
- "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -4505,13 +4411,16 @@
}
},
"node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
}
},
"node_modules/semver-compare": {
@@ -4582,19 +4491,6 @@
"node": ">=10"
}
},
- "node_modules/simple-update-notifier/node_modules/semver": {
- "version": "7.8.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz",
- "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
@@ -4996,9 +4892,9 @@
}
},
"node_modules/undici-types": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
- "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"dev": true,
"license": "MIT"
},
@@ -5209,17 +5105,6 @@
"node": ">=12"
}
},
- "node_modules/yauzl": {
- "version": "2.10.0",
- "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
- "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "buffer-crc32": "~0.2.3",
- "fd-slicer": "~1.1.0"
- }
- },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/package.json b/package.json
index 6469179..16f1aed 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
"ipad": "node tools/ipad-terminal/server.js",
"ipad:lan": "node tools/ipad-terminal/server.js --lan",
"test:boot": "node scripts/bootcheck.js",
+ "check:hook": "electron scripts/hook-check.js",
"demo": "node scripts/make-demo-gif.js mp4",
"demo:hero": "node scripts/make-demo-gif.js hero mp4",
"demo:gallery": "node scripts/make-demo-gif.js gallery",
@@ -40,7 +41,7 @@
"devDependencies": {
"@eslint/js": "^10.0.1",
"@ffmpeg-installer/ffmpeg": "^1.1.0",
- "electron": "^33.0.0",
+ "electron": "^44.0.0",
"electron-builder": "^26.15.3",
"eslint": "^10.9.1",
"gifenc": "^1.0.3",
diff --git a/scripts/hook-check.js b/scripts/hook-check.js
new file mode 100644
index 0000000..71075e4
--- /dev/null
+++ b/scripts/hook-check.js
@@ -0,0 +1,54 @@
+// Does the global input hook actually work on this Electron?
+//
+// src/main.js wraps `require('uiohook-napi')` in a try/catch with a retry, which
+// is right at runtime (a machine without Accessibility permission should still
+// get a pet) and misleading during an upgrade: a native module that fails to load
+// under a new Electron ABI leaves an app that boots, renders, passes CI, and has
+// quietly stopped reacting to typing and scrolling. `npm run test:boot` cannot see
+// that, because a pet that never kneads still draws a perfectly good frame.
+//
+// So this runs inside the main process, which is the only place the answer counts,
+// and asks the three questions in order: does it load, does the API look right,
+// does start() survive contact with the OS.
+//
+// npm run check:hook
+//
+// Not wired into CI on purpose. A headless Linux runner has no input to hook, and
+// macOS refuses the hook without Accessibility permission a runner cannot grant,
+// so it would fail for reasons that say nothing about the upgrade. Run it locally
+// when you change the Electron major or touch uiohook-napi.
+const { app } = require('electron');
+
+app.disableHardwareAcceleration();
+
+const done = (report, code) => {
+ console.log(JSON.stringify(report, null, 2));
+ console.log(code === 0 ? '\ninput hook OK' : '\ninput hook FAILED');
+ app.exit(code);
+};
+
+app.whenReady().then(() => {
+ const versions = { electron: process.versions.electron, node: process.versions.node, abi: process.versions.modules };
+ let uIOhook;
+ try {
+ ({ uIOhook } = require('uiohook-napi'));
+ } catch (e) {
+ return done({ ...versions, loaded: false, error: e.message }, 1);
+ }
+ if (typeof uIOhook.on !== 'function' || typeof uIOhook.start !== 'function') {
+ return done({ ...versions, loaded: true, api: false }, 1);
+ }
+ try {
+ uIOhook.on('keydown', () => {});
+ uIOhook.on('wheel', () => {});
+ uIOhook.start();
+ } catch (e) {
+ return done({ ...versions, loaded: true, api: true, started: false, error: e.message }, 1);
+ }
+ // Give the hook thread a moment to fall over on its own before calling it a pass.
+ setTimeout(() => {
+ let stopError = null;
+ try { uIOhook.stop(); } catch (e) { stopError = e.message; }
+ done({ ...versions, loaded: true, api: true, started: true, stopped: !stopError, stopError }, stopError ? 1 : 0);
+ }, 700);
+});