dsh-clean-desktop-shell 0.1.11 → 0.1.12
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/CONTRIBUTORS.md +19 -19
- package/README.en.md +341 -333
- package/README.md +286 -278
- package/electron/aumid.js +17 -17
- package/electron/crashGuard.js +60 -60
- package/electron/error.html +113 -113
- package/electron/main.js +203 -187
- package/electron/preload.js +49 -49
- package/electron/progress-preload.js +11 -11
- package/electron/progress.html +79 -79
- package/electron/progress.js +56 -56
- package/electron/service.js +521 -458
- package/electron/shortcut.js +122 -122
- package/electron/tray.js +232 -232
- package/electron/window.js +380 -264
- package/lib/common.js +74 -74
- package/lib/icon.js +51 -51
- package/lib/index.js +57 -13
- package/lib/runtime.js +423 -423
- package/package.json +1 -1
- package/scripts/check-syntax.mjs +54 -54
- package/scripts/selftest-runtime.mjs +90 -90
- package/src/host/common.js +74 -74
- package/src/host/icon.js +51 -51
- package/src/host/index.js +57 -13
- package/src/host/runtime.js +423 -423
- package/version.txt +1 -1
package/electron/main.js
CHANGED
|
@@ -1,187 +1,203 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-clean-desktop-shell — Electron main process entry.
|
|
3
|
-
*
|
|
4
|
-
* Shell/core decoupling:
|
|
5
|
-
* - default target: http://127.0.0.1:3080 (existing web profile, zero migration)
|
|
6
|
-
* - configurable remote target (settings file)
|
|
7
|
-
* - local backend auto-start on launch; full manual control from the tray
|
|
8
|
-
*
|
|
9
|
-
* The main window is a pure shell — all backend controls live in the tray.
|
|
10
|
-
*/
|
|
11
|
-
import { app, BrowserWindow, dialog, nativeImage } from 'electron'
|
|
12
|
-
import { fileURLToPath } from 'node:url'
|
|
13
|
-
import { createMainWindow, reloadWindow } from './window.js'
|
|
14
|
-
import { createTray, refreshTrayMenu } from './tray.js'
|
|
15
|
-
import { loadConfig, saveConfig, DEFAULT_TARGET_URL } from './config.js'
|
|
16
|
-
import { detect } from './service.js'
|
|
17
|
-
import { setupAutoUpdater } from './update.js'
|
|
18
|
-
import { shortcutSupported, hasDesktopShortcut, createDesktopShortcut, ensureStartMenuShortcut } from './shortcut.js'
|
|
19
|
-
import { APP_USER_MODEL_ID } from './aumid.js'
|
|
20
|
-
import { setupCrashGuard } from './crashGuard.js'
|
|
21
|
-
|
|
22
|
-
const isMac = process.platform === 'darwin'
|
|
23
|
-
|
|
24
|
-
// Last-resort error capture (userData/shell-crash.log) — install before
|
|
25
|
-
// anything else so even early startup failures leave a trace.
|
|
26
|
-
setupCrashGuard()
|
|
27
|
-
|
|
28
|
-
// Windows: pin the AppUserModelId so the taskbar shows our whale icon
|
|
29
|
-
// instead of the generic Electron icon. Plugin mode uses a distinct ID
|
|
30
|
-
// (see aumid.js) so Windows re-reads the icon instead of serving a
|
|
31
|
-
// stale per-AUMID cached one.
|
|
32
|
-
if (process.platform === 'win32') {
|
|
33
|
-
app.setAppUserModelId(APP_USER_MODEL_ID)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// macOS: a bare runtime has no .app bundle (no icon resource), so set the
|
|
37
|
-
// Dock icon at runtime. Unlike Windows there is no per-AUMID taskbar cache
|
|
38
|
-
// here — app.dock.setIcon applies directly. Packaged builds already carry
|
|
39
|
-
// the icon in their bundle, so skip those.
|
|
40
|
-
if (isMac && !app.isPackaged) {
|
|
41
|
-
try {
|
|
42
|
-
const dockIcon = nativeImage.createFromPath(
|
|
43
|
-
fileURLToPath(new URL('../build/icon.png', import.meta.url)),
|
|
44
|
-
)
|
|
45
|
-
if (!dockIcon.isEmpty()) app.dock.setIcon(dockIcon)
|
|
46
|
-
} catch {
|
|
47
|
-
// non-fatal: keep the default icon
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Uniform userData across both distribution branches (installer vs
|
|
52
|
-
// plugin-market), so config (target URL, backend path, ...) is shared
|
|
53
|
-
// no matter how the shell was launched.
|
|
54
|
-
app.setName('DSH Clean Desktop Shell')
|
|
55
|
-
|
|
56
|
-
/** Single instance: a second launch just focuses the existing window. */
|
|
57
|
-
const gotLock = app.requestSingleInstanceLock()
|
|
58
|
-
if (!gotLock) {
|
|
59
|
-
app.quit()
|
|
60
|
-
} else {
|
|
61
|
-
let mainWindow = null
|
|
62
|
-
let tray = null
|
|
63
|
-
|
|
64
|
-
app.on('second-instance', () => {
|
|
65
|
-
if (mainWindow) {
|
|
66
|
-
if (mainWindow.isMinimized()) mainWindow.restore()
|
|
67
|
-
mainWindow.focus()
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
async function createWindow() {
|
|
72
|
-
const config = loadConfig()
|
|
73
|
-
const target = config.targetUrl || DEFAULT_TARGET_URL
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
1
|
+
/**
|
|
2
|
+
* dsh-clean-desktop-shell — Electron main process entry.
|
|
3
|
+
*
|
|
4
|
+
* Shell/core decoupling:
|
|
5
|
+
* - default target: http://127.0.0.1:3080 (existing web profile, zero migration)
|
|
6
|
+
* - configurable remote target (settings file)
|
|
7
|
+
* - local backend auto-start on launch; full manual control from the tray
|
|
8
|
+
*
|
|
9
|
+
* The main window is a pure shell — all backend controls live in the tray.
|
|
10
|
+
*/
|
|
11
|
+
import { app, BrowserWindow, dialog, nativeImage } from 'electron'
|
|
12
|
+
import { fileURLToPath } from 'node:url'
|
|
13
|
+
import { createMainWindow, reloadWindow } from './window.js'
|
|
14
|
+
import { createTray, refreshTrayMenu } from './tray.js'
|
|
15
|
+
import { loadConfig, saveConfig, DEFAULT_TARGET_URL } from './config.js'
|
|
16
|
+
import { detect, getAuthenticatedUrl, getStatus } from './service.js'
|
|
17
|
+
import { setupAutoUpdater } from './update.js'
|
|
18
|
+
import { shortcutSupported, hasDesktopShortcut, createDesktopShortcut, ensureStartMenuShortcut } from './shortcut.js'
|
|
19
|
+
import { APP_USER_MODEL_ID } from './aumid.js'
|
|
20
|
+
import { setupCrashGuard } from './crashGuard.js'
|
|
21
|
+
|
|
22
|
+
const isMac = process.platform === 'darwin'
|
|
23
|
+
|
|
24
|
+
// Last-resort error capture (userData/shell-crash.log) — install before
|
|
25
|
+
// anything else so even early startup failures leave a trace.
|
|
26
|
+
setupCrashGuard()
|
|
27
|
+
|
|
28
|
+
// Windows: pin the AppUserModelId so the taskbar shows our whale icon
|
|
29
|
+
// instead of the generic Electron icon. Plugin mode uses a distinct ID
|
|
30
|
+
// (see aumid.js) so Windows re-reads the icon instead of serving a
|
|
31
|
+
// stale per-AUMID cached one.
|
|
32
|
+
if (process.platform === 'win32') {
|
|
33
|
+
app.setAppUserModelId(APP_USER_MODEL_ID)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// macOS: a bare runtime has no .app bundle (no icon resource), so set the
|
|
37
|
+
// Dock icon at runtime. Unlike Windows there is no per-AUMID taskbar cache
|
|
38
|
+
// here — app.dock.setIcon applies directly. Packaged builds already carry
|
|
39
|
+
// the icon in their bundle, so skip those.
|
|
40
|
+
if (isMac && !app.isPackaged) {
|
|
41
|
+
try {
|
|
42
|
+
const dockIcon = nativeImage.createFromPath(
|
|
43
|
+
fileURLToPath(new URL('../build/icon.png', import.meta.url)),
|
|
44
|
+
)
|
|
45
|
+
if (!dockIcon.isEmpty()) app.dock.setIcon(dockIcon)
|
|
46
|
+
} catch {
|
|
47
|
+
// non-fatal: keep the default icon
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Uniform userData across both distribution branches (installer vs
|
|
52
|
+
// plugin-market), so config (target URL, backend path, ...) is shared
|
|
53
|
+
// no matter how the shell was launched.
|
|
54
|
+
app.setName('DSH Clean Desktop Shell')
|
|
55
|
+
|
|
56
|
+
/** Single instance: a second launch just focuses the existing window. */
|
|
57
|
+
const gotLock = app.requestSingleInstanceLock()
|
|
58
|
+
if (!gotLock) {
|
|
59
|
+
app.quit()
|
|
60
|
+
} else {
|
|
61
|
+
let mainWindow = null
|
|
62
|
+
let tray = null
|
|
63
|
+
|
|
64
|
+
app.on('second-instance', () => {
|
|
65
|
+
if (mainWindow) {
|
|
66
|
+
if (mainWindow.isMinimized()) mainWindow.restore()
|
|
67
|
+
mainWindow.focus()
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
async function createWindow() {
|
|
72
|
+
const config = loadConfig()
|
|
73
|
+
const target = config.targetUrl || DEFAULT_TARGET_URL
|
|
74
|
+
// In plugin mode the host half (same dsh web process) supplies the real
|
|
75
|
+
// per-process launch URL through DSH_WEB_LAUNCH_URL. In tray-start mode
|
|
76
|
+
// service.start() will surface a fresh one through status changes.
|
|
77
|
+
// Only apply BrowserAuth bootstrap to the local default; remote targets
|
|
78
|
+
// remain the user's own externally-managed surface.
|
|
79
|
+
let isLocalDefault = false
|
|
80
|
+
try {
|
|
81
|
+
isLocalDefault = new URL(target).origin === new URL(DEFAULT_TARGET_URL).origin
|
|
82
|
+
} catch {
|
|
83
|
+
// A malformed configured target must not break window creation, and it
|
|
84
|
+
// must never receive the local launch credential — stay non-local.
|
|
85
|
+
}
|
|
86
|
+
const launchUrl = isLocalDefault ? (getStatus().launchUrl || null) : null
|
|
87
|
+
|
|
88
|
+
mainWindow = createMainWindow({ target, launchUrl })
|
|
89
|
+
mainWindow.on('closed', () => {
|
|
90
|
+
mainWindow = null
|
|
91
|
+
})
|
|
92
|
+
mainWindow.on('close', (event) => {
|
|
93
|
+
// Close hides to tray unless we are actually quitting. Read the
|
|
94
|
+
// config fresh so a runtime change to closeToTray takes effect.
|
|
95
|
+
if (!app.isQuitting && loadConfig().closeToTray !== false) {
|
|
96
|
+
event.preventDefault()
|
|
97
|
+
mainWindow?.hide()
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
return mainWindow
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Launch-time backend detection. Detects whether a local dsh web is
|
|
106
|
+
* already running so the tray and window reflect the real state, but
|
|
107
|
+
* never auto-starts it — starting is a manual action (tray menu or the
|
|
108
|
+
* offline screen button) so it cannot fight an explicit "stop".
|
|
109
|
+
*/
|
|
110
|
+
async function bootstrapBackend() {
|
|
111
|
+
const config = loadConfig()
|
|
112
|
+
const target = config.targetUrl || DEFAULT_TARGET_URL
|
|
113
|
+
// Remote targets are the user's own business — never probe local.
|
|
114
|
+
// Origin comparison (not string equality) tolerates trailing slashes
|
|
115
|
+
// and an explicitly written default.
|
|
116
|
+
if (new URL(target).origin !== new URL(DEFAULT_TARGET_URL).origin) return
|
|
117
|
+
await detect()
|
|
118
|
+
// Reflect the post-detect state in the tray menu.
|
|
119
|
+
refreshTrayMenu()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* First-run desktop shortcut prompt (Windows packaged apps only).
|
|
124
|
+
* Asks once; the tray "create desktop shortcut" item stays available
|
|
125
|
+
* forever, so saying no is never a dead end.
|
|
126
|
+
*/
|
|
127
|
+
async function ensureShortcut() {
|
|
128
|
+
if (!shortcutSupported() || loadConfig().shortcutAsked) return
|
|
129
|
+
saveConfig({ ...loadConfig(), shortcutAsked: true })
|
|
130
|
+
if (await hasDesktopShortcut()) return
|
|
131
|
+
const choice = dialog.showMessageBoxSync({
|
|
132
|
+
type: 'question',
|
|
133
|
+
title: '创建桌面快捷方式?',
|
|
134
|
+
message: '是否在桌面创建「DSH Clean Desktop Shell」快捷方式?',
|
|
135
|
+
detail: '选择「跳过」也不影响使用——之后可随时在托盘右键菜单中一键添加。',
|
|
136
|
+
buttons: ['创建', '跳过'],
|
|
137
|
+
defaultId: 0,
|
|
138
|
+
cancelId: 1,
|
|
139
|
+
})
|
|
140
|
+
if (choice === 0) {
|
|
141
|
+
const ok = await createDesktopShortcut()
|
|
142
|
+
if (!ok) {
|
|
143
|
+
dialog.showErrorBox('创建快捷方式失败', '无法在桌面创建快捷方式。可稍后在托盘右键菜单中重试。')
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
app.whenReady().then(async () => {
|
|
149
|
+
await createWindow()
|
|
150
|
+
tray = createTray({
|
|
151
|
+
onShow: () => {
|
|
152
|
+
if (!mainWindow) return createWindow()
|
|
153
|
+
mainWindow.show()
|
|
154
|
+
mainWindow.focus()
|
|
155
|
+
},
|
|
156
|
+
onReload: () => {
|
|
157
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
158
|
+
// Prefer the live launch-token URL from the last shell-started
|
|
159
|
+
// backend; a manual refresh is exactly when a stale config target
|
|
160
|
+
// (or a bare pre-0.1.2 loopback URL) would 401.
|
|
161
|
+
reloadWindow(mainWindow, getAuthenticatedUrl() || loadConfig().targetUrl || DEFAULT_TARGET_URL)
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
onQuit: () => {
|
|
165
|
+
app.isQuitting = true
|
|
166
|
+
app.quit()
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
// Backend bootstrap in background (never delays the window).
|
|
171
|
+
bootstrapBackend().catch(() => {})
|
|
172
|
+
|
|
173
|
+
// Windows: wire the auto-updater (downloads new installers silently).
|
|
174
|
+
setupAutoUpdater()
|
|
175
|
+
|
|
176
|
+
// Windows: ensure the AUMID-carrying Start-menu shortcut exists so the
|
|
177
|
+
// taskbar button shows our icon (see shortcut.js). Best-effort.
|
|
178
|
+
try {
|
|
179
|
+
ensureStartMenuShortcut()
|
|
180
|
+
} catch {
|
|
181
|
+
// non-fatal
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// First run: offer a desktop shortcut (never nags twice).
|
|
185
|
+
ensureShortcut().catch(() => {})
|
|
186
|
+
|
|
187
|
+
app.on('activate', () => {
|
|
188
|
+
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
|
189
|
+
else mainWindow?.show()
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
app.on('window-all-closed', () => {
|
|
194
|
+
// Keep the app alive in the tray (like a normal desktop utility).
|
|
195
|
+
if (!isMac && !app.isQuitting) {
|
|
196
|
+
// do not quit — tray keeps running
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
app.on('before-quit', () => {
|
|
201
|
+
app.isQuitting = true
|
|
202
|
+
})
|
|
203
|
+
}
|
package/electron/preload.js
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Preload: makes the frameless window draggable + exposes the minimal
|
|
3
|
-
* shell API to the loaded page (sandbox-safe contextBridge).
|
|
4
|
-
*
|
|
5
|
-
* A frameless window has no title bar, so dragging is provided by a
|
|
6
|
-
* `-webkit-app-region: drag` strip along the top of the loaded page.
|
|
7
|
-
* The right side is left for the native window-controls overlay.
|
|
8
|
-
*
|
|
9
|
-
* The strip is a transparent overlay, so it never changes page layout —
|
|
10
|
-
* but it sits above the page top edge, so page top-bar buttons can be
|
|
11
|
-
* reached by the strip being only 1px tall at the very edge... Instead we
|
|
12
|
-
* use a pragmatic height and rely on the page's own top padding for the
|
|
13
|
-
* DSH top bar area. Overlap is acceptable: the strip is click-through for
|
|
14
|
-
* everything except dragging (app-region drag areas swallow mouse events).
|
|
15
|
-
*/
|
|
16
|
-
const { contextBridge, ipcRenderer } = require('electron')
|
|
17
|
-
|
|
18
|
-
contextBridge.exposeInMainWorld('shellAPI', {
|
|
19
|
-
// Ask the main process to (re)load the real target URL. Used by the
|
|
20
|
-
// offline screen's retry button.
|
|
21
|
-
reload: () => ipcRenderer.send('shell:reload'),
|
|
22
|
-
// Offline-screen quick actions (mirror the tray backend controls).
|
|
23
|
-
startBackend: () => ipcRenderer.send('shell:start-backend'),
|
|
24
|
-
detectBackend: () => ipcRenderer.send('shell:detect-backend'),
|
|
25
|
-
chooseBackendFolder: () => ipcRenderer.send('shell:choose-backend-folder'),
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
window.addEventListener('DOMContentLoaded', () => {
|
|
29
|
-
const platform = process.platform
|
|
30
|
-
const isWin = platform === 'win32'
|
|
31
|
-
const dragHeight = isWin ? 32 : 28
|
|
32
|
-
// Width reserved for native window controls (Win caption buttons / mac
|
|
33
|
-
// traffic lights live at the top-right / top-left).
|
|
34
|
-
const rightReserve = isWin ? 138 : 80
|
|
35
|
-
const leftReserve = isWin ? 0 : 80
|
|
36
|
-
|
|
37
|
-
const strip = document.createElement('div')
|
|
38
|
-
strip.id = 'dsh-clean-shell-drag'
|
|
39
|
-
strip.style.cssText = `
|
|
40
|
-
position: fixed;
|
|
41
|
-
top: 0;
|
|
42
|
-
left: ${leftReserve}px;
|
|
43
|
-
right: ${rightReserve}px;
|
|
44
|
-
height: ${dragHeight}px;
|
|
45
|
-
-webkit-app-region: drag;
|
|
46
|
-
z-index: 2147483647;
|
|
47
|
-
`
|
|
48
|
-
document.body.appendChild(strip)
|
|
49
|
-
})
|
|
1
|
+
/**
|
|
2
|
+
* Preload: makes the frameless window draggable + exposes the minimal
|
|
3
|
+
* shell API to the loaded page (sandbox-safe contextBridge).
|
|
4
|
+
*
|
|
5
|
+
* A frameless window has no title bar, so dragging is provided by a
|
|
6
|
+
* `-webkit-app-region: drag` strip along the top of the loaded page.
|
|
7
|
+
* The right side is left for the native window-controls overlay.
|
|
8
|
+
*
|
|
9
|
+
* The strip is a transparent overlay, so it never changes page layout —
|
|
10
|
+
* but it sits above the page top edge, so page top-bar buttons can be
|
|
11
|
+
* reached by the strip being only 1px tall at the very edge... Instead we
|
|
12
|
+
* use a pragmatic height and rely on the page's own top padding for the
|
|
13
|
+
* DSH top bar area. Overlap is acceptable: the strip is click-through for
|
|
14
|
+
* everything except dragging (app-region drag areas swallow mouse events).
|
|
15
|
+
*/
|
|
16
|
+
const { contextBridge, ipcRenderer } = require('electron')
|
|
17
|
+
|
|
18
|
+
contextBridge.exposeInMainWorld('shellAPI', {
|
|
19
|
+
// Ask the main process to (re)load the real target URL. Used by the
|
|
20
|
+
// offline screen's retry button.
|
|
21
|
+
reload: () => ipcRenderer.send('shell:reload'),
|
|
22
|
+
// Offline-screen quick actions (mirror the tray backend controls).
|
|
23
|
+
startBackend: () => ipcRenderer.send('shell:start-backend'),
|
|
24
|
+
detectBackend: () => ipcRenderer.send('shell:detect-backend'),
|
|
25
|
+
chooseBackendFolder: () => ipcRenderer.send('shell:choose-backend-folder'),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
29
|
+
const platform = process.platform
|
|
30
|
+
const isWin = platform === 'win32'
|
|
31
|
+
const dragHeight = isWin ? 32 : 28
|
|
32
|
+
// Width reserved for native window controls (Win caption buttons / mac
|
|
33
|
+
// traffic lights live at the top-right / top-left).
|
|
34
|
+
const rightReserve = isWin ? 138 : 80
|
|
35
|
+
const leftReserve = isWin ? 0 : 80
|
|
36
|
+
|
|
37
|
+
const strip = document.createElement('div')
|
|
38
|
+
strip.id = 'dsh-clean-shell-drag'
|
|
39
|
+
strip.style.cssText = `
|
|
40
|
+
position: fixed;
|
|
41
|
+
top: 0;
|
|
42
|
+
left: ${leftReserve}px;
|
|
43
|
+
right: ${rightReserve}px;
|
|
44
|
+
height: ${dragHeight}px;
|
|
45
|
+
-webkit-app-region: drag;
|
|
46
|
+
z-index: 2147483647;
|
|
47
|
+
`
|
|
48
|
+
document.body.appendChild(strip)
|
|
49
|
+
})
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Preload for the progress window — exposes a sandbox-safe listener for
|
|
3
|
-
* status updates pushed from the main process.
|
|
4
|
-
*/
|
|
5
|
-
const { contextBridge, ipcRenderer } = require('electron')
|
|
6
|
-
|
|
7
|
-
contextBridge.exposeInMainWorld('progressAPI', {
|
|
8
|
-
onSet: (cb) => {
|
|
9
|
-
ipcRenderer.on('progress:set', (_event, data) => cb(data))
|
|
10
|
-
},
|
|
11
|
-
})
|
|
1
|
+
/**
|
|
2
|
+
* Preload for the progress window — exposes a sandbox-safe listener for
|
|
3
|
+
* status updates pushed from the main process.
|
|
4
|
+
*/
|
|
5
|
+
const { contextBridge, ipcRenderer } = require('electron')
|
|
6
|
+
|
|
7
|
+
contextBridge.exposeInMainWorld('progressAPI', {
|
|
8
|
+
onSet: (cb) => {
|
|
9
|
+
ipcRenderer.on('progress:set', (_event, data) => cb(data))
|
|
10
|
+
},
|
|
11
|
+
})
|