@pointylab/dsh 0.0.0-stage → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ASSETS.md +20 -0
- package/LICENSE +21 -0
- package/README.md +85 -2
- package/cordis.patch.yml +13 -0
- package/lib/client.js +33 -0
- package/package.json +38 -4
- package/pet/app/card/card.css +365 -0
- package/pet/app/card/card.html +68 -0
- package/pet/app/card/card.js +896 -0
- package/pet/app/card/markdown.js +64 -0
- package/pet/app/card/mascot/blink.png +0 -0
- package/pet/app/card/mascot/done.png +0 -0
- package/pet/app/card/mascot/error.png +0 -0
- package/pet/app/card/mascot/idle.png +0 -0
- package/pet/app/card/mascot/peek.png +0 -0
- package/pet/app/card/mascot/think.png +0 -0
- package/pet/app/card/preload.js +21 -0
- package/pet/app/fullscreen.js +131 -0
- package/pet/app/main.js +1284 -0
- package/pet/app/native/uia-selection.ps1 +158 -0
- package/pet/app/notice/notice.html +37 -0
- package/pet/app/notice/notice.js +31 -0
- package/pet/app/notice/preload.js +7 -0
- package/pet/app/overlay/overlay.html +30 -0
- package/pet/app/overlay/overlay.js +42 -0
- package/pet/app/overlay/preload.js +7 -0
- package/pet/app/pill/pill.css +73 -0
- package/pet/app/pill/pill.html +11 -0
- package/pet/app/pill/pill.js +35 -0
- package/pet/app/pill/preload.js +8 -0
- package/pet/app/preload.js +16 -0
- package/pet/app/renderer/index.html +24 -0
- package/pet/app/renderer/lines.js +25 -0
- package/pet/app/renderer/pet.js +292 -0
- package/pet/app/renderer/renderer2d.js +71 -0
- package/pet/app/renderer/style.css +157 -0
- package/pet/app/selection.js +246 -0
- package/pet/app/shake.js +76 -0
- package/pet/app/uipick.js +314 -0
- package/pet/assets/cursor/cursor-32.cur +0 -0
- package/pet/assets/cursor/cursor-32.png +0 -0
- package/pet/assets/cursor/cursor-48.cur +0 -0
- package/pet/assets/cursor/cursor-48.png +0 -0
- package/pet/assets/cursor/cursor-64.cur +0 -0
- package/pet/assets/cursor/cursor-64.png +0 -0
- package/pet/assets/cursor/cursor-clean.png +0 -0
- package/pet/assets/cursor/make_cursor.py +85 -0
- package/pet/assets/cursor/source.png +0 -0
- package/pet/assets/pointy.ico +0 -0
- package/pet/assets/tray.png +0 -0
- package/pet/assets/tray@2x.png +0 -0
- package/pet/boot.js +6 -0
- package/pet/extension/background.js +252 -0
- package/pet/extension/cursor-32.png +0 -0
- package/pet/extension/cursor-48.png +0 -0
- package/pet/extension/cursor-64.png +0 -0
- package/pet/extension/icon.png +0 -0
- package/pet/extension/manifest.json +12 -0
- package/pet/extension/pick.js +391 -0
- package/pet/extension/popup.html +29 -0
- package/pet/extension/popup.js +30 -0
- package/pet/package.json +11 -0
- package/pet/vendor/koffi/LICENSE.txt +22 -0
- package/pet/vendor/koffi/build/koffi/win32_x64/koffi.exp +0 -0
- package/pet/vendor/koffi/build/koffi/win32_x64/koffi.lib +0 -0
- package/pet/vendor/koffi/build/koffi/win32_x64/koffi.node +0 -0
- package/pet/vendor/koffi/index.js +634 -0
- package/pet/vendor/koffi/indirect.js +533 -0
- package/pet/vendor/koffi/package.json +35 -0
- package/pet/vendor/ws/LICENSE +20 -0
- package/pet/vendor/ws/index.js +22 -0
- package/pet/vendor/ws/lib/buffer-util.js +142 -0
- package/pet/vendor/ws/lib/constants.js +19 -0
- package/pet/vendor/ws/lib/event-target.js +292 -0
- package/pet/vendor/ws/lib/extension.js +203 -0
- package/pet/vendor/ws/lib/limiter.js +55 -0
- package/pet/vendor/ws/lib/permessage-deflate.js +530 -0
- package/pet/vendor/ws/lib/receiver.js +743 -0
- package/pet/vendor/ws/lib/sender.js +610 -0
- package/pet/vendor/ws/lib/stream.js +161 -0
- package/pet/vendor/ws/lib/subprotocol.js +62 -0
- package/pet/vendor/ws/lib/validation.js +152 -0
- package/pet/vendor/ws/lib/websocket-server.js +562 -0
- package/pet/vendor/ws/lib/websocket.js +1426 -0
- package/pet/vendor/ws/package.json +74 -0
- package/pet/vendor/ws/wrapper.mjs +21 -0
- package/src/node.js +732 -0
- package/src/runtime.js +178 -0
package/src/runtime.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pointy needs a full Electron runtime (windows, tray, screenshots), which the
|
|
3
|
+
* DSH plugin host does not have: it runs DSH's own binary in node mode. So the
|
|
4
|
+
* plugin carries Pointy's code in ../pet and, the first time, downloads the
|
|
5
|
+
* stock Electron build that Pointy was tested with into ~/.pointy/runtime.
|
|
6
|
+
*
|
|
7
|
+
* The zip is checked against the SHA-256 pinned in pet/package.json at pack
|
|
8
|
+
* time, so a mirror can only be slow, never substitute a different binary.
|
|
9
|
+
*/
|
|
10
|
+
import { spawn } from 'node:child_process'
|
|
11
|
+
import crypto from 'node:crypto'
|
|
12
|
+
import fs from 'node:fs'
|
|
13
|
+
import os from 'node:os'
|
|
14
|
+
import path from 'node:path'
|
|
15
|
+
import { fileURLToPath } from 'node:url'
|
|
16
|
+
|
|
17
|
+
export const PET_APP_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'pet')
|
|
18
|
+
const RUNTIME_DIR = path.join(os.homedir(), '.pointy', 'runtime')
|
|
19
|
+
|
|
20
|
+
// npmmirror first: GitHub release downloads are often slow or blocked in mainland China.
|
|
21
|
+
const MIRRORS = [
|
|
22
|
+
'https://npmmirror.com/mirrors/electron/',
|
|
23
|
+
'https://github.com/electron/electron/releases/download/',
|
|
24
|
+
]
|
|
25
|
+
const DOWNLOAD_TIMEOUT_MS = 10 * 60_000
|
|
26
|
+
/** electron.exe is renamed and rebranded so Task Manager shows "Pointy", not "Electron". */
|
|
27
|
+
const EXE_NAME = 'Pointy.exe'
|
|
28
|
+
|
|
29
|
+
/** `{ version, sha256 }` of the Electron zip pinned by tools/pack-plugin.mjs, or undefined in a dev checkout. */
|
|
30
|
+
export function pinnedRuntime() {
|
|
31
|
+
try {
|
|
32
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PET_APP_DIR, 'package.json'), 'utf8'))
|
|
33
|
+
const { version, sha256 } = pkg.pointy?.electron ?? {}
|
|
34
|
+
if (typeof version === 'string' && /^[0-9a-f]{64}$/.test(sha256 ?? '')) return { version, sha256 }
|
|
35
|
+
} catch {}
|
|
36
|
+
return undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const zipName = (version) => `electron-v${version}-win32-x64.zip`
|
|
40
|
+
const runtimeDir = (version) => path.join(RUNTIME_DIR, `electron-v${version}-win32-x64`)
|
|
41
|
+
|
|
42
|
+
/** electron.exe for the pinned version if it is already unpacked. */
|
|
43
|
+
export function installedRuntime(pin = pinnedRuntime()) {
|
|
44
|
+
if (!pin) return undefined
|
|
45
|
+
const exe = path.join(runtimeDir(pin.version), EXE_NAME)
|
|
46
|
+
return fs.existsSync(exe) ? exe : undefined
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function download(url, file, logger) {
|
|
50
|
+
const res = await fetch(url, { redirect: 'follow', signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) })
|
|
51
|
+
if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`)
|
|
52
|
+
const total = Number(res.headers.get('content-length')) || 0
|
|
53
|
+
const hash = crypto.createHash('sha256')
|
|
54
|
+
const out = fs.createWriteStream(file)
|
|
55
|
+
let got = 0
|
|
56
|
+
let nextLog = 0.25
|
|
57
|
+
try {
|
|
58
|
+
for await (const chunk of res.body) {
|
|
59
|
+
hash.update(chunk)
|
|
60
|
+
got += chunk.length
|
|
61
|
+
if (!out.write(chunk)) await new Promise((r) => out.once('drain', r))
|
|
62
|
+
if (total && got / total >= nextLog) {
|
|
63
|
+
logger.info(`pointy: downloading Electron ${Math.round((got / total) * 100)}%`)
|
|
64
|
+
nextLog += 0.25
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} finally {
|
|
68
|
+
await new Promise((r) => out.end(r))
|
|
69
|
+
}
|
|
70
|
+
return hash.digest('hex')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function unzip(zip, dir) {
|
|
74
|
+
// Windows 10+ ships bsdtar, which reads zip archives.
|
|
75
|
+
const tar = path.join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'tar.exe')
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const child = spawn(tar, ['-xf', zip, '-C', dir], { stdio: 'ignore', windowsHide: true })
|
|
78
|
+
child.on('error', reject)
|
|
79
|
+
child.on('exit', (code) => (code === 0 ? resolve() : reject(new Error(`tar exited with ${code}`))))
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Rename electron.exe to Pointy.exe and rewrite its version strings and icon.
|
|
85
|
+
* Task Manager's process list shows the FileDescription, its details tab the
|
|
86
|
+
* file name. The official signature no longer matches afterwards, so it is
|
|
87
|
+
* dropped; Windows does not check signatures of programs started by another
|
|
88
|
+
* program. If the resource edit fails, the renamed stock binary still runs.
|
|
89
|
+
*/
|
|
90
|
+
async function brand(dir, logger) {
|
|
91
|
+
const from = path.join(dir, 'electron.exe')
|
|
92
|
+
const to = path.join(dir, EXE_NAME)
|
|
93
|
+
try {
|
|
94
|
+
const ResEdit = await import('resedit')
|
|
95
|
+
const exe = ResEdit.NtExecutable.from(fs.readFileSync(from), { ignoreCert: true })
|
|
96
|
+
const res = ResEdit.NtExecutableResource.from(exe)
|
|
97
|
+
const icoFile = path.join(PET_APP_DIR, 'assets', 'pointy.ico')
|
|
98
|
+
if (fs.existsSync(icoFile)) {
|
|
99
|
+
const icons = ResEdit.Data.IconFile.from(fs.readFileSync(icoFile)).icons.map((icon) => icon.data)
|
|
100
|
+
for (const group of ResEdit.Resource.IconGroupEntry.fromEntries(res.entries)) {
|
|
101
|
+
ResEdit.Resource.IconGroupEntry.replaceIconsForResource(res.entries, group.id, group.lang, icons)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const vi of ResEdit.Resource.VersionInfo.fromEntries(res.entries)) {
|
|
105
|
+
for (const lang of vi.getAllLanguagesForStringValues()) {
|
|
106
|
+
vi.setStringValues(lang, { FileDescription: 'Pointy', ProductName: 'Pointy', InternalName: 'Pointy', OriginalFilename: EXE_NAME })
|
|
107
|
+
}
|
|
108
|
+
vi.outputToResourceEntries(res.entries)
|
|
109
|
+
}
|
|
110
|
+
res.outputResource(exe)
|
|
111
|
+
fs.writeFileSync(to, Buffer.from(exe.generate()))
|
|
112
|
+
fs.rmSync(from)
|
|
113
|
+
} catch (error) {
|
|
114
|
+
logger.warn(`pointy: could not rebrand electron.exe (${error.message}); using it renamed`)
|
|
115
|
+
fs.rmSync(to, { force: true })
|
|
116
|
+
fs.renameSync(from, to)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let installing
|
|
121
|
+
/**
|
|
122
|
+
* Make sure the pinned Electron is unpacked; returns the path of electron.exe.
|
|
123
|
+
* One download at a time per DSH run; older runtimes are removed afterwards.
|
|
124
|
+
*/
|
|
125
|
+
export function ensureRuntime(logger) {
|
|
126
|
+
const pin = pinnedRuntime()
|
|
127
|
+
if (!pin) return Promise.reject(new Error(`no pinned Electron in ${PET_APP_DIR}`))
|
|
128
|
+
if (process.platform !== 'win32' || process.arch !== 'x64') return Promise.reject(new Error('Pointy only supports Windows x64'))
|
|
129
|
+
const ready = installedRuntime(pin)
|
|
130
|
+
if (ready) return Promise.resolve(ready)
|
|
131
|
+
installing ??= install(pin, logger).finally(() => { installing = undefined })
|
|
132
|
+
return installing
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function install(pin, logger) {
|
|
136
|
+
fs.mkdirSync(RUNTIME_DIR, { recursive: true })
|
|
137
|
+
// Leftovers of a download that was cut off (DSH closed midway).
|
|
138
|
+
for (const name of fs.readdirSync(RUNTIME_DIR)) {
|
|
139
|
+
const full = path.join(RUNTIME_DIR, name)
|
|
140
|
+
if (name.startsWith('tmp-') && Date.now() - fs.statSync(full).mtimeMs > 3600_000) fs.rmSync(full, { recursive: true, force: true })
|
|
141
|
+
}
|
|
142
|
+
const tmp = fs.mkdtempSync(path.join(RUNTIME_DIR, 'tmp-'))
|
|
143
|
+
try {
|
|
144
|
+
const zip = path.join(tmp, zipName(pin.version))
|
|
145
|
+
let lastError
|
|
146
|
+
for (const mirror of MIRRORS) {
|
|
147
|
+
const url = `${mirror}v${pin.version}/${zipName(pin.version)}`
|
|
148
|
+
try {
|
|
149
|
+
logger.info(`pointy: downloading Electron ${pin.version} from ${url}`)
|
|
150
|
+
const sha = await download(url, zip, logger)
|
|
151
|
+
if (sha !== pin.sha256) throw new Error(`checksum mismatch (${sha})`)
|
|
152
|
+
lastError = undefined
|
|
153
|
+
break
|
|
154
|
+
} catch (error) {
|
|
155
|
+
lastError = error
|
|
156
|
+
logger.warn(`pointy: ${url} failed: ${error.message}`)
|
|
157
|
+
fs.rmSync(zip, { force: true })
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (lastError) throw lastError
|
|
161
|
+
const unpacked = path.join(tmp, 'unpacked')
|
|
162
|
+
fs.mkdirSync(unpacked)
|
|
163
|
+
await unzip(zip, unpacked)
|
|
164
|
+
if (!fs.existsSync(path.join(unpacked, 'electron.exe'))) throw new Error('electron.exe missing from the zip')
|
|
165
|
+
await brand(unpacked, logger)
|
|
166
|
+
const dest = runtimeDir(pin.version)
|
|
167
|
+
fs.rmSync(dest, { recursive: true, force: true })
|
|
168
|
+
fs.renameSync(unpacked, dest)
|
|
169
|
+
logger.info(`pointy: Electron ${pin.version} ready in ${dest}`)
|
|
170
|
+
for (const name of fs.readdirSync(RUNTIME_DIR)) {
|
|
171
|
+
const full = path.join(RUNTIME_DIR, name)
|
|
172
|
+
if (full !== dest && /^electron-v/.test(name)) fs.rmSync(full, { recursive: true, force: true })
|
|
173
|
+
}
|
|
174
|
+
return path.join(dest, EXE_NAME)
|
|
175
|
+
} finally {
|
|
176
|
+
fs.rmSync(tmp, { recursive: true, force: true })
|
|
177
|
+
}
|
|
178
|
+
}
|