termdock 1.4.108 → 1.4.110
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/.desktop-dist/main.js +483 -24
- package/dist/client/assets/{ModelPreview-By3cPzJ9.js → ModelPreview-B40SLVdu.js} +1 -1
- package/dist/client/assets/{abnfDiagram-VRR7QNED-Dm9g0Pi3.js → abnfDiagram-VRR7QNED-D7p11Ssd.js} +1 -1
- package/dist/client/assets/{arc-XntYYR9t.js → arc-CsmLIp7i.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-CW0kZlVv.js → architectureDiagram-ZJ3FMSHR-BjYttOye.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-DgMdh19W.js → blockDiagram-677ZJIJ3-BnWi8IVO.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-DJC4m2Y6.js → c4Diagram-LMCZKHZV-BFm2RcKX.js} +1 -1
- package/dist/client/assets/channel-ifcSKqN3.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-CgE5oTjY.js → chunk-2Q5K7J3B-D_OKw60X.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-B50lSI4-.js → chunk-32BRIVSS-D6k1lZV9.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-C984gGSO.js → chunk-5VM5RSS4-CEqvSDkW.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-C1jPbFiq.js → chunk-EX3LRPZG-Ca2Gmbvd.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-D4U9D5Kv.js → chunk-JWPE2WC7-CRRvRUQB.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-C4rLPnf_.js → chunk-MOJQB5TN-CeZ5qCFm.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-CnSX68rt.js → chunk-RYQCIY6F-Cx7sW5Rr.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-LTTPDSzs.js → chunk-V7JOEXUC-B3_grzN8.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-dCZ9yGOH.js → chunk-VR4S4FIN-g73M8USU.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-DiUZr3na.js → chunk-XXDRQBXY-9i-NV13r.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-CVu2gyed.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-CVu2gyed.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-Dw73llr7.js → cose-bilkent-JH36ORCC-DGKauMxg.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-CsB9ZT7a.js → cynefin-VYW2F7L2-DKvEKvGd.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-NinMHYPB.js → cynefinDiagram-TSTJHNR4-CLXYMIpB.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-CmFL5D4c.js → dagre-VKFMJZFB-DFAR5nAp.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-D_C52SB6.js → diagram-FQU43EPY-DeRBoyeq.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-BLlpvNO5.js → diagram-G47NLZAW-CDEAgMuP.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-BNT2NQnR.js → diagram-NH7WQ7WH-4VFR2ZsJ.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-DlJ-T7tl.js → diagram-OA4YK3LP-DMKIgZlI.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-D7s_sZnL.js → diagram-WEI45ONY-zEjxP4io.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-PGksmUKG.js → ebnfDiagram-CCIWWBDH-BuErqR1l.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-CbfrT-bt.js → erDiagram-Q63AITRT-D4nbc2T3.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U-CqUGdXdQ.js → flowDiagram-23GEKE2U-B2MiQAI4.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-DpH0ybM6.js → ganttDiagram-NO4QXBWP-hGTX0vsS.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-Cmc3LsBg.js → gitGraphDiagram-IHSO6WYX-BHHSVtvH.js} +1 -1
- package/dist/client/assets/index-CFXqRKcJ.css +1 -0
- package/dist/client/assets/index-D3kro0zp.js +456 -0
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-CHzl_SDV.js → infoDiagram-FWYZ7A6U-BZuNpsCG.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-BUVwtCaN.js → ishikawaDiagram-FXEZZL3T-C0KwjZIv.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-DrmO4THP.js → journeyDiagram-5HDEW3XC-mxy7-Irj.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-DTZoUd1n.js → kanban-definition-HUTT4EX6-DslZKv6b.js} +1 -1
- package/dist/client/assets/{linear-DgCNjduP.js → linear-BJXz3Iqq.js} +1 -1
- package/dist/client/assets/{mermaid.core-DLidsBlx.js → mermaid.core-Btv5hFOv.js} +4 -4
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-srFG-Dae.js → mindmap-definition-LN4V7U3C-BSbqyDyq.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR-Qary-lxo.js → pegDiagram-2B236MQR-L2cCu4W1.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-D924FFIQ.js → pieDiagram-ENE6RG2P-CXdawbl4.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-YFBMJKUv.js → quadrantDiagram-ABIIQ3AL-CwKwBctE.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-C__7fBPw.js → railroadDiagram-RFXS5EU6-B6MBOnSp.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-BtAq5f69.js → requirementDiagram-TGXJPOKE-CR9lns0Z.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-BGULYk91.js → sankeyDiagram-HTMAVEWB-DLuhNvTd.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-BBjCjIFm.js → sequenceDiagram-DBY2YBRQ-BorPhsA5.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-DjP0UZy6.js → sizeCapture-X5ZJPWSS-DPXho3_K.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-BTqu5zVt.js → stateDiagram-2N3HPSRC-DSbrRqVk.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB--bVRWeRL.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-ClySuTz2.js → swimlanes-5IMT3BWC-D4A4axIk.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-CqWtgk0W.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-DHURyh7H.js → timeline-definition-FHXFAJF6-D4ObRKT8.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-BFdn5KML.js → vennDiagram-L72KCM5P-Bj_4-RtP.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-DKEHwCwO.js → wardleyDiagram-EHGQE667-BNvcAUoQ.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-CI5HzOMq.js → xychartDiagram-FW5EYKEG-BfGV9sTq.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/server/agent/automationStore.js +12 -7
- package/dist/server/agent/sessionSearchStore.js +135 -5
- package/dist/server/entry.js +24 -1
- package/dist/server/routes/terminal.js +6 -2
- package/dist/server/utils/runtimeMonitor.js +287 -0
- package/dist/server/utils/settings.js +9 -0
- package/package.json +1 -1
- package/runtime-manifest.json +2 -2
- package/dist/client/assets/channel-D1KecDCp.js +0 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-BYJ3p4nE.js +0 -1
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-BYJ3p4nE.js +0 -1
- package/dist/client/assets/index-BwMQSNQq.js +0 -456
- package/dist/client/assets/index-RGd2Qu1X.css +0 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-D5FsphfH.js +0 -1
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-C1_CRJSE.js +0 -8
package/.desktop-dist/main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { app, BrowserWindow, dialog, ipcMain, Menu, net, Notification, session, shell, } from 'electron';
|
|
1
|
+
import { app, BrowserWindow, dialog, ipcMain, Menu, net, nativeImage, Notification, screen, session, shell, Tray, } from 'electron';
|
|
2
2
|
import { execFile, spawn } from 'node:child_process';
|
|
3
3
|
import crypto from 'node:crypto';
|
|
4
4
|
import dgram from 'node:dgram';
|
|
@@ -8,6 +8,7 @@ import os from 'node:os';
|
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { promisify } from 'node:util';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { desktopStatusTooltip, formatCompactDesktopStatus, mergeServiceActivity, nextServiceOrigin, normalizeServiceActivity, summarizeServiceActivity, } from './activityStatus.js';
|
|
11
12
|
import { checkForDesktopUpdates, configureDesktopUpdater, ensureLatestRuntime, getDesktopUpdateState, installDownloadedDesktopUpdate, subscribeDesktopUpdateState, } from './updater.js';
|
|
12
13
|
import { resolvePackagedRuntime, rollbackDownloadedRuntime, } from './runtime.js';
|
|
13
14
|
import { isExternalLinkStagingUrl, isSafeExternalUrl } from './externalLinks.js';
|
|
@@ -52,9 +53,17 @@ function triggerLocalNetworkPermission() {
|
|
|
52
53
|
let mainWindow = null;
|
|
53
54
|
const serviceWindows = new Map();
|
|
54
55
|
const windowServiceOrigins = new WeakMap();
|
|
55
|
-
const
|
|
56
|
+
const restorableServiceWindows = new WeakSet();
|
|
57
|
+
const reportedServiceActivity = new Map();
|
|
58
|
+
const observedServiceActivity = new Map();
|
|
56
59
|
let lastFocusedServiceWindow = null;
|
|
60
|
+
let menuBarStatus = null;
|
|
61
|
+
let menuBarStatusWidth = 0;
|
|
62
|
+
let floatingWidgetWindow = null;
|
|
63
|
+
let floatingPositionTimer = null;
|
|
57
64
|
let isQuitting = false;
|
|
65
|
+
const FLOATING_WIDGET_WIDTHS = [64, 108, 152];
|
|
66
|
+
const FLOATING_WIDGET_HEIGHT = 40;
|
|
58
67
|
function focusedWorkspaceWindow() {
|
|
59
68
|
const focused = BrowserWindow.getFocusedWindow();
|
|
60
69
|
if (focused && windowServiceOrigins.has(focused))
|
|
@@ -77,7 +86,7 @@ function serviceActivitySnapshot(targetWindow) {
|
|
|
77
86
|
return [...serviceWindows.entries()].flatMap(([origin, window]) => {
|
|
78
87
|
if (window.isDestroyed())
|
|
79
88
|
return [];
|
|
80
|
-
const activity =
|
|
89
|
+
const activity = mergeServiceActivity(reportedServiceActivity.get(origin), observedServiceActivity.get(origin));
|
|
81
90
|
return [{
|
|
82
91
|
origin,
|
|
83
92
|
label: serviceLabel(origin),
|
|
@@ -93,6 +102,7 @@ function broadcastServiceActivity() {
|
|
|
93
102
|
continue;
|
|
94
103
|
window.webContents.send('desktop:service-activity-changed', serviceActivitySnapshot(window));
|
|
95
104
|
}
|
|
105
|
+
refreshDesktopStatusSurfaces();
|
|
96
106
|
}
|
|
97
107
|
function serviceLabel(url) {
|
|
98
108
|
const normalized = normalizeServiceUrl(url);
|
|
@@ -104,6 +114,311 @@ function serviceLabel(url) {
|
|
|
104
114
|
? '本机'
|
|
105
115
|
: parsed.host;
|
|
106
116
|
}
|
|
117
|
+
function allServiceActivity() {
|
|
118
|
+
const focused = BrowserWindow.getFocusedWindow();
|
|
119
|
+
return [...serviceWindows.entries()].flatMap(([origin, window]) => {
|
|
120
|
+
if (window.isDestroyed())
|
|
121
|
+
return [];
|
|
122
|
+
return [{
|
|
123
|
+
origin,
|
|
124
|
+
label: serviceLabel(origin),
|
|
125
|
+
current: window === lastFocusedServiceWindow,
|
|
126
|
+
focused: window === focused,
|
|
127
|
+
...mergeServiceActivity(reportedServiceActivity.get(origin), observedServiceActivity.get(origin)),
|
|
128
|
+
}];
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function desktopStatusSnapshot() {
|
|
132
|
+
const services = allServiceActivity();
|
|
133
|
+
const summary = summarizeServiceActivity(services);
|
|
134
|
+
return {
|
|
135
|
+
...summary,
|
|
136
|
+
text: formatCompactDesktopStatus(summary),
|
|
137
|
+
tooltip: desktopStatusTooltip(summary),
|
|
138
|
+
services,
|
|
139
|
+
preferences: readDesktopConfig().desktopPreferences,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function nextServiceWindow(scope) {
|
|
143
|
+
const currentOrigin = lastFocusedServiceWindow && !lastFocusedServiceWindow.isDestroyed()
|
|
144
|
+
? windowServiceOrigins.get(lastFocusedServiceWindow) ?? null
|
|
145
|
+
: null;
|
|
146
|
+
const origin = nextServiceOrigin(allServiceActivity(), currentOrigin, scope);
|
|
147
|
+
return origin ? serviceWindows.get(origin) ?? null : focusedWorkspaceWindow();
|
|
148
|
+
}
|
|
149
|
+
function focusNextService(scope = 'attention') {
|
|
150
|
+
const target = nextServiceWindow(scope);
|
|
151
|
+
if (target && !target.isDestroyed()) {
|
|
152
|
+
showAndFocusWindow(target);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
void showConnectionCenter();
|
|
156
|
+
}
|
|
157
|
+
function menuBarContextMenu() {
|
|
158
|
+
const status = desktopStatusSnapshot();
|
|
159
|
+
const serviceItems = status.services.length > 0
|
|
160
|
+
? status.services
|
|
161
|
+
.sort((left, right) => right.reviewCount - left.reviewCount
|
|
162
|
+
|| right.runningCount - left.runningCount
|
|
163
|
+
|| left.label.localeCompare(right.label))
|
|
164
|
+
.map((service) => ({
|
|
165
|
+
label: `${service.label}${service.runningCount > 0 ? ` 运行 ${service.runningCount}` : ''}${service.reviewCount > 0 ? ` 待办 ${service.reviewCount}` : ''}`,
|
|
166
|
+
click: () => {
|
|
167
|
+
const target = serviceWindows.get(service.origin);
|
|
168
|
+
if (target && !target.isDestroyed())
|
|
169
|
+
showAndFocusWindow(target);
|
|
170
|
+
},
|
|
171
|
+
}))
|
|
172
|
+
: [{ label: '暂无已打开服务', enabled: false }];
|
|
173
|
+
return Menu.buildFromTemplate([
|
|
174
|
+
{ label: status.tooltip, enabled: false },
|
|
175
|
+
{ type: 'separator' },
|
|
176
|
+
...serviceItems,
|
|
177
|
+
{ type: 'separator' },
|
|
178
|
+
{ label: '连接中心…', click: () => void showConnectionCenter() },
|
|
179
|
+
{
|
|
180
|
+
label: '显示悬浮状态',
|
|
181
|
+
type: 'checkbox',
|
|
182
|
+
checked: status.preferences.floatingWidgetEnabled,
|
|
183
|
+
click: (item) => updateDesktopPreferences({ floatingWidgetEnabled: item.checked }),
|
|
184
|
+
},
|
|
185
|
+
{ type: 'separator' },
|
|
186
|
+
{ role: 'quit' },
|
|
187
|
+
]);
|
|
188
|
+
}
|
|
189
|
+
function menuBarStatusImage(status) {
|
|
190
|
+
const size = 18;
|
|
191
|
+
const scale = 2;
|
|
192
|
+
const bitmap = Buffer.alloc(size * scale * size * scale * 4);
|
|
193
|
+
const glyphs = {
|
|
194
|
+
'0': ['111', '101', '101', '101', '111'],
|
|
195
|
+
'1': ['010', '110', '010', '010', '111'],
|
|
196
|
+
'2': ['111', '001', '111', '100', '111'],
|
|
197
|
+
'3': ['111', '001', '111', '001', '111'],
|
|
198
|
+
'4': ['101', '101', '111', '001', '001'],
|
|
199
|
+
'5': ['111', '100', '111', '001', '111'],
|
|
200
|
+
'6': ['111', '100', '111', '101', '111'],
|
|
201
|
+
'7': ['111', '001', '010', '010', '010'],
|
|
202
|
+
'8': ['111', '101', '111', '101', '111'],
|
|
203
|
+
'9': ['111', '101', '111', '001', '111'],
|
|
204
|
+
'+': ['000', '010', '111', '010', '000'],
|
|
205
|
+
};
|
|
206
|
+
const drawPixel = (x, y, alpha = 255) => {
|
|
207
|
+
for (let offsetY = 0; offsetY < scale; offsetY += 1) {
|
|
208
|
+
for (let offsetX = 0; offsetX < scale; offsetX += 1) {
|
|
209
|
+
const pixelX = x * scale + offsetX;
|
|
210
|
+
const pixelY = y * scale + offsetY;
|
|
211
|
+
if (pixelX < 0 || pixelX >= size * scale || pixelY < 0 || pixelY >= size * scale)
|
|
212
|
+
continue;
|
|
213
|
+
bitmap[(pixelY * size * scale + pixelX) * 4 + 3] = alpha;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
const drawGlyph = (value, x, y, alpha) => {
|
|
218
|
+
const glyph = glyphs[value > 9 ? '+' : String(value)] ?? glyphs['0'];
|
|
219
|
+
glyph.forEach((row, rowIndex) => {
|
|
220
|
+
[...row].forEach((cell, columnIndex) => {
|
|
221
|
+
if (cell === '1')
|
|
222
|
+
drawPixel(x + columnIndex, y + rowIndex, alpha);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
// Three quiet, consistently aligned rows read much better at menu-bar size
|
|
227
|
+
// than squeezing two metrics beside each other. The ordering mirrors the
|
|
228
|
+
// floating widget: running, review, then connected services.
|
|
229
|
+
const rows = [
|
|
230
|
+
{ value: status.runningCount, y: 0, icon: [[0, 0], [0, 1], [1, 1], [0, 2], [1, 2], [2, 2], [3, 2], [0, 3], [1, 3], [0, 4]] },
|
|
231
|
+
{ value: status.reviewCount, y: 6, icon: [[2, 0], [1, 1], [3, 1], [0, 2], [4, 2], [1, 3], [3, 3], [2, 4]] },
|
|
232
|
+
{ value: status.serviceCount, y: 12, icon: [[0, 0], [1, 0], [3, 0], [4, 0], [1, 1], [3, 1], [2, 2], [2, 3], [1, 4], [2, 4], [3, 4]] },
|
|
233
|
+
];
|
|
234
|
+
rows.forEach(({ value, y, icon }) => {
|
|
235
|
+
const alpha = value > 0 ? 255 : 96;
|
|
236
|
+
icon.forEach(([offsetX, offsetY]) => drawPixel(1 + offsetX, y + offsetY, alpha));
|
|
237
|
+
drawGlyph(value, 11, y, alpha);
|
|
238
|
+
});
|
|
239
|
+
// Template images are intentionally monochrome; macOS supplies the correct
|
|
240
|
+
// menu-bar color for the active appearance and accessibility state.
|
|
241
|
+
const image = nativeImage.createFromBitmap(bitmap, {
|
|
242
|
+
width: size * scale,
|
|
243
|
+
height: size * scale,
|
|
244
|
+
scaleFactor: scale,
|
|
245
|
+
});
|
|
246
|
+
image.setTemplateImage(true);
|
|
247
|
+
return image;
|
|
248
|
+
}
|
|
249
|
+
function refreshMenuBarStatus(status) {
|
|
250
|
+
if (process.platform !== 'darwin' || !status.preferences.menuBarStatusEnabled) {
|
|
251
|
+
menuBarStatus?.destroy();
|
|
252
|
+
menuBarStatus = null;
|
|
253
|
+
menuBarStatusWidth = 0;
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const image = menuBarStatusImage(status);
|
|
257
|
+
const imageWidth = image.getSize().width;
|
|
258
|
+
if (!menuBarStatus || menuBarStatus.isDestroyed() || menuBarStatusWidth !== imageWidth) {
|
|
259
|
+
menuBarStatus?.destroy();
|
|
260
|
+
menuBarStatus = new Tray(image);
|
|
261
|
+
menuBarStatusWidth = imageWidth;
|
|
262
|
+
menuBarStatus.on('click', () => focusNextService('attention'));
|
|
263
|
+
menuBarStatus.on('right-click', () => menuBarStatus?.popUpContextMenu(menuBarContextMenu()));
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
menuBarStatus.setImage(image);
|
|
267
|
+
}
|
|
268
|
+
menuBarStatus.setTitle('');
|
|
269
|
+
menuBarStatus.setToolTip(`${status.tooltip}\n点击循环切换需关注的服务`);
|
|
270
|
+
}
|
|
271
|
+
function constrainFloatingPosition(position, width = FLOATING_WIDGET_WIDTHS[2]) {
|
|
272
|
+
const height = FLOATING_WIDGET_HEIGHT;
|
|
273
|
+
const display = position
|
|
274
|
+
? screen.getDisplayMatching({ x: position.x, y: position.y, width, height })
|
|
275
|
+
: screen.getPrimaryDisplay();
|
|
276
|
+
const area = display.workArea;
|
|
277
|
+
const preferred = position ?? {
|
|
278
|
+
x: area.x + area.width - width - 18,
|
|
279
|
+
y: area.y + 18,
|
|
280
|
+
};
|
|
281
|
+
return {
|
|
282
|
+
x: Math.min(area.x + area.width - width, Math.max(area.x, Math.round(preferred.x))),
|
|
283
|
+
y: Math.min(area.y + area.height - height, Math.max(area.y, Math.round(preferred.y))),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function createFloatingWidget(position) {
|
|
287
|
+
const bounds = constrainFloatingPosition(position);
|
|
288
|
+
const window = new BrowserWindow({
|
|
289
|
+
title: 'Termdock Status',
|
|
290
|
+
type: 'panel',
|
|
291
|
+
width: FLOATING_WIDGET_WIDTHS[2],
|
|
292
|
+
height: FLOATING_WIDGET_HEIGHT,
|
|
293
|
+
x: bounds.x,
|
|
294
|
+
y: bounds.y,
|
|
295
|
+
show: false,
|
|
296
|
+
frame: false,
|
|
297
|
+
transparent: true,
|
|
298
|
+
backgroundColor: 'rgba(0, 0, 0, 0)',
|
|
299
|
+
vibrancy: 'popover',
|
|
300
|
+
visualEffectState: 'active',
|
|
301
|
+
resizable: false,
|
|
302
|
+
maximizable: false,
|
|
303
|
+
minimizable: false,
|
|
304
|
+
fullscreenable: false,
|
|
305
|
+
focusable: false,
|
|
306
|
+
skipTaskbar: true,
|
|
307
|
+
alwaysOnTop: true,
|
|
308
|
+
acceptFirstMouse: true,
|
|
309
|
+
hasShadow: true,
|
|
310
|
+
webPreferences: {
|
|
311
|
+
preload: path.join(currentDir, 'preload.cjs'),
|
|
312
|
+
nodeIntegration: false,
|
|
313
|
+
contextIsolation: true,
|
|
314
|
+
sandbox: true,
|
|
315
|
+
backgroundThrottling: false,
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
window.setAlwaysOnTop(true, 'floating');
|
|
319
|
+
window.setVisibleOnAllWorkspaces(true, {
|
|
320
|
+
visibleOnFullScreen: true,
|
|
321
|
+
// Electron otherwise temporarily transforms the whole process into a UI
|
|
322
|
+
// element, which can hide the app menu and remove normal windows from App
|
|
323
|
+
// Expose while this panel exists.
|
|
324
|
+
skipTransformProcessType: true,
|
|
325
|
+
});
|
|
326
|
+
// Keep the auxiliary status panel out of App Expose / Mission Control so it
|
|
327
|
+
// cannot disrupt macOS's three-finger application-window gesture.
|
|
328
|
+
window.setHiddenInMissionControl(true);
|
|
329
|
+
let constrainingMove = false;
|
|
330
|
+
window.on('move', () => {
|
|
331
|
+
if (constrainingMove || window.isDestroyed())
|
|
332
|
+
return;
|
|
333
|
+
const [x, y] = window.getPosition();
|
|
334
|
+
const [width] = window.getSize();
|
|
335
|
+
const constrained = constrainFloatingPosition({ x, y }, width);
|
|
336
|
+
if (constrained.x === x && constrained.y === y)
|
|
337
|
+
return;
|
|
338
|
+
constrainingMove = true;
|
|
339
|
+
window.setPosition(constrained.x, constrained.y, false);
|
|
340
|
+
constrainingMove = false;
|
|
341
|
+
});
|
|
342
|
+
window.on('moved', () => {
|
|
343
|
+
if (floatingPositionTimer)
|
|
344
|
+
clearTimeout(floatingPositionTimer);
|
|
345
|
+
floatingPositionTimer = setTimeout(() => {
|
|
346
|
+
floatingPositionTimer = null;
|
|
347
|
+
if (window.isDestroyed())
|
|
348
|
+
return;
|
|
349
|
+
const [x, y] = window.getPosition();
|
|
350
|
+
const config = readDesktopConfig();
|
|
351
|
+
config.desktopPreferences.floatingWidgetPosition = { x, y };
|
|
352
|
+
writeDesktopConfig(config);
|
|
353
|
+
}, 250);
|
|
354
|
+
});
|
|
355
|
+
window.on('closed', () => {
|
|
356
|
+
if (floatingWidgetWindow === window)
|
|
357
|
+
floatingWidgetWindow = null;
|
|
358
|
+
});
|
|
359
|
+
const rendererPath = app.isPackaged
|
|
360
|
+
? path.join(process.resourcesPath, 'renderer', 'floating.html')
|
|
361
|
+
: path.join(projectRoot, 'desktop', 'renderer', 'floating.html');
|
|
362
|
+
void window.loadFile(rendererPath).then(() => window.showInactive());
|
|
363
|
+
return window;
|
|
364
|
+
}
|
|
365
|
+
function resizeFloatingWidget(metricCount) {
|
|
366
|
+
const window = floatingWidgetWindow;
|
|
367
|
+
if (!window || window.isDestroyed())
|
|
368
|
+
return;
|
|
369
|
+
const safeCount = Math.min(3, Math.max(1, Math.round(metricCount)));
|
|
370
|
+
const width = FLOATING_WIDGET_WIDTHS[safeCount - 1];
|
|
371
|
+
const [currentWidth] = window.getSize();
|
|
372
|
+
if (currentWidth === width)
|
|
373
|
+
return;
|
|
374
|
+
const [x, y] = window.getPosition();
|
|
375
|
+
window.setSize(width, FLOATING_WIDGET_HEIGHT, false);
|
|
376
|
+
const constrained = constrainFloatingPosition({ x: x + currentWidth - width, y }, width);
|
|
377
|
+
window.setPosition(constrained.x, constrained.y, false);
|
|
378
|
+
}
|
|
379
|
+
function refreshFloatingWidget(status) {
|
|
380
|
+
if (process.platform !== 'darwin' || !status.preferences.floatingWidgetEnabled) {
|
|
381
|
+
if (floatingWidgetWindow && !floatingWidgetWindow.isDestroyed())
|
|
382
|
+
floatingWidgetWindow.destroy();
|
|
383
|
+
floatingWidgetWindow = null;
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (!floatingWidgetWindow || floatingWidgetWindow.isDestroyed()) {
|
|
387
|
+
floatingWidgetWindow = createFloatingWidget(status.preferences.floatingWidgetPosition);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function keepFloatingWidgetOnScreen() {
|
|
391
|
+
if (!floatingWidgetWindow || floatingWidgetWindow.isDestroyed())
|
|
392
|
+
return;
|
|
393
|
+
const [x, y] = floatingWidgetWindow.getPosition();
|
|
394
|
+
const [width] = floatingWidgetWindow.getSize();
|
|
395
|
+
const constrained = constrainFloatingPosition({ x, y }, width);
|
|
396
|
+
if (constrained.x !== x || constrained.y !== y) {
|
|
397
|
+
floatingWidgetWindow.setPosition(constrained.x, constrained.y, false);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function refreshDesktopStatusSurfaces() {
|
|
401
|
+
if (!app.isReady())
|
|
402
|
+
return;
|
|
403
|
+
const status = desktopStatusSnapshot();
|
|
404
|
+
refreshMenuBarStatus(status);
|
|
405
|
+
refreshFloatingWidget(status);
|
|
406
|
+
for (const window of BrowserWindow.getAllWindows()) {
|
|
407
|
+
if (!window.isDestroyed())
|
|
408
|
+
window.webContents.send('desktop:status-changed', status);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function updateDesktopPreferences(patch) {
|
|
412
|
+
const config = readDesktopConfig();
|
|
413
|
+
if (typeof patch.menuBarStatusEnabled === 'boolean') {
|
|
414
|
+
config.desktopPreferences.menuBarStatusEnabled = patch.menuBarStatusEnabled;
|
|
415
|
+
}
|
|
416
|
+
if (typeof patch.floatingWidgetEnabled === 'boolean') {
|
|
417
|
+
config.desktopPreferences.floatingWidgetEnabled = patch.floatingWidgetEnabled;
|
|
418
|
+
}
|
|
419
|
+
writeDesktopConfig(config);
|
|
420
|
+
refreshDesktopStatusSurfaces();
|
|
421
|
+
}
|
|
107
422
|
function showDesktopMessageBox(options) {
|
|
108
423
|
const parent = BrowserWindow.getFocusedWindow() ?? mainWindow;
|
|
109
424
|
return parent
|
|
@@ -156,7 +471,30 @@ function defaultConfig() {
|
|
|
156
471
|
version: 1,
|
|
157
472
|
connections: [],
|
|
158
473
|
lastConnectionUrl: null,
|
|
474
|
+
openConnectionUrls: [],
|
|
159
475
|
trustedCertificateAuthorities: [],
|
|
476
|
+
desktopPreferences: {
|
|
477
|
+
menuBarStatusEnabled: true,
|
|
478
|
+
floatingWidgetEnabled: false,
|
|
479
|
+
floatingWidgetPosition: null,
|
|
480
|
+
},
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
function normalizeDesktopPreferences(value) {
|
|
484
|
+
const input = value && typeof value === 'object'
|
|
485
|
+
? value
|
|
486
|
+
: {};
|
|
487
|
+
const position = input.floatingWidgetPosition;
|
|
488
|
+
return {
|
|
489
|
+
// The compact, text-only status is the useful default on macOS. Existing
|
|
490
|
+
// desktop.json files therefore gain the feature without a migration step.
|
|
491
|
+
menuBarStatusEnabled: input.menuBarStatusEnabled !== false,
|
|
492
|
+
floatingWidgetEnabled: input.floatingWidgetEnabled === true,
|
|
493
|
+
floatingWidgetPosition: position
|
|
494
|
+
&& Number.isFinite(position.x)
|
|
495
|
+
&& Number.isFinite(position.y)
|
|
496
|
+
? { x: Math.round(position.x), y: Math.round(position.y) }
|
|
497
|
+
: null,
|
|
160
498
|
};
|
|
161
499
|
}
|
|
162
500
|
function normalizeServiceUrl(raw) {
|
|
@@ -272,13 +610,20 @@ function readDesktopConfig() {
|
|
|
272
610
|
const parsed = JSON.parse(fs.readFileSync(desktopConfigPath, 'utf8'));
|
|
273
611
|
if (parsed.version !== 1 || !Array.isArray(parsed.connections))
|
|
274
612
|
return defaultConfig();
|
|
613
|
+
const lastConnectionUrl = typeof parsed.lastConnectionUrl === 'string' ? parsed.lastConnectionUrl : null;
|
|
275
614
|
return {
|
|
276
615
|
version: 1,
|
|
277
616
|
connections: parsed.connections.filter((entry) => Boolean(entry)
|
|
278
617
|
&& typeof entry.id === 'string'
|
|
279
618
|
&& typeof entry.label === 'string'
|
|
280
619
|
&& typeof entry.url === 'string'),
|
|
281
|
-
lastConnectionUrl
|
|
620
|
+
lastConnectionUrl,
|
|
621
|
+
// Older desktop.json files only remembered the most recent window. Use
|
|
622
|
+
// that value once as their restore list, while preserving an explicit
|
|
623
|
+
// empty list when the user intentionally closed every service window.
|
|
624
|
+
openConnectionUrls: Array.isArray(parsed.openConnectionUrls)
|
|
625
|
+
? parsed.openConnectionUrls.filter((url) => typeof url === 'string')
|
|
626
|
+
: lastConnectionUrl ? [lastConnectionUrl] : [],
|
|
282
627
|
trustedCertificateAuthorities: Array.isArray(parsed.trustedCertificateAuthorities)
|
|
283
628
|
? parsed.trustedCertificateAuthorities.filter((entry) => Boolean(entry)
|
|
284
629
|
&& typeof entry.origin === 'string'
|
|
@@ -286,6 +631,7 @@ function readDesktopConfig() {
|
|
|
286
631
|
&& typeof entry.subject === 'string'
|
|
287
632
|
&& typeof entry.trustedAt === 'number')
|
|
288
633
|
: [],
|
|
634
|
+
desktopPreferences: normalizeDesktopPreferences(parsed.desktopPreferences),
|
|
289
635
|
};
|
|
290
636
|
}
|
|
291
637
|
catch {
|
|
@@ -616,6 +962,7 @@ async function snapshot() {
|
|
|
616
962
|
localService,
|
|
617
963
|
connections: config.connections,
|
|
618
964
|
lastConnectionUrl: config.lastConnectionUrl,
|
|
965
|
+
desktopPreferences: config.desktopPreferences,
|
|
619
966
|
};
|
|
620
967
|
}
|
|
621
968
|
function desktopRuntimeEnv(runtime = runtimePaths()) {
|
|
@@ -810,7 +1157,7 @@ async function installCli() {
|
|
|
810
1157
|
], { timeout: 120_000, maxBuffer: 256 * 1024 });
|
|
811
1158
|
return snapshot();
|
|
812
1159
|
}
|
|
813
|
-
async function connectWindow(rawUrl) {
|
|
1160
|
+
async function connectWindow(rawUrl, options = {}) {
|
|
814
1161
|
let probe = await probeServiceWithLocalNetworkPermission(rawUrl);
|
|
815
1162
|
if (!probe.ok)
|
|
816
1163
|
return probe;
|
|
@@ -819,28 +1166,67 @@ async function connectWindow(rawUrl) {
|
|
|
819
1166
|
const existingWindow = serviceWindows.get(key);
|
|
820
1167
|
if (existingWindow && !existingWindow.isDestroyed()) {
|
|
821
1168
|
const config = readDesktopConfig();
|
|
822
|
-
|
|
1169
|
+
if (options.updateLastConnection !== false)
|
|
1170
|
+
config.lastConnectionUrl = probe.url;
|
|
1171
|
+
config.openConnectionUrls = [
|
|
1172
|
+
...config.openConnectionUrls.filter((url) => {
|
|
1173
|
+
try {
|
|
1174
|
+
return new URL(url).origin !== key;
|
|
1175
|
+
}
|
|
1176
|
+
catch {
|
|
1177
|
+
return false;
|
|
1178
|
+
}
|
|
1179
|
+
}),
|
|
1180
|
+
probe.url,
|
|
1181
|
+
];
|
|
823
1182
|
const existing = config.connections.find((entry) => entry.url === probe.url);
|
|
824
1183
|
if (existing)
|
|
825
1184
|
existing.lastConnectedAt = Date.now();
|
|
826
1185
|
writeDesktopConfig(config);
|
|
827
|
-
|
|
828
|
-
|
|
1186
|
+
if (options.focus !== false) {
|
|
1187
|
+
mainWindow?.hide();
|
|
1188
|
+
showAndFocusWindow(existingWindow);
|
|
1189
|
+
}
|
|
1190
|
+
else {
|
|
1191
|
+
existingWindow.showInactive();
|
|
1192
|
+
}
|
|
829
1193
|
return probe;
|
|
830
1194
|
}
|
|
831
1195
|
const workspaceWindow = createDesktopWindow({ serviceOrigin: key, label: serviceLabel(probe.url) });
|
|
832
1196
|
serviceWindows.set(key, workspaceWindow);
|
|
1197
|
+
broadcastServiceActivity();
|
|
833
1198
|
while (!workspaceWindow.isDestroyed()) {
|
|
834
1199
|
try {
|
|
835
1200
|
await workspaceWindow.loadURL(probe.url);
|
|
836
1201
|
const config = readDesktopConfig();
|
|
837
|
-
|
|
1202
|
+
if (options.updateLastConnection !== false)
|
|
1203
|
+
config.lastConnectionUrl = probe.url;
|
|
1204
|
+
config.openConnectionUrls = [
|
|
1205
|
+
...config.openConnectionUrls.filter((url) => {
|
|
1206
|
+
try {
|
|
1207
|
+
return new URL(url).origin !== key;
|
|
1208
|
+
}
|
|
1209
|
+
catch {
|
|
1210
|
+
return false;
|
|
1211
|
+
}
|
|
1212
|
+
}),
|
|
1213
|
+
probe.url,
|
|
1214
|
+
];
|
|
838
1215
|
const existing = config.connections.find((entry) => entry.url === probe.url);
|
|
839
1216
|
if (existing)
|
|
840
1217
|
existing.lastConnectedAt = Date.now();
|
|
841
1218
|
writeDesktopConfig(config);
|
|
842
|
-
|
|
843
|
-
|
|
1219
|
+
restorableServiceWindows.add(workspaceWindow);
|
|
1220
|
+
if (options.focus !== false) {
|
|
1221
|
+
mainWindow?.hide();
|
|
1222
|
+
showAndFocusWindow(workspaceWindow);
|
|
1223
|
+
}
|
|
1224
|
+
else {
|
|
1225
|
+
// Restored workspaces must be real visible macOS windows so App Expose
|
|
1226
|
+
// can select them, but opening several at launch must not steal focus
|
|
1227
|
+
// from each other.
|
|
1228
|
+
workspaceWindow.showInactive();
|
|
1229
|
+
}
|
|
844
1230
|
return probe;
|
|
845
1231
|
}
|
|
846
1232
|
catch (error) {
|
|
@@ -1030,7 +1416,8 @@ function createDesktopWindow(options) {
|
|
|
1030
1416
|
const serviceOrigin = windowServiceOrigins.get(window);
|
|
1031
1417
|
if (serviceOrigin && serviceWindows.get(serviceOrigin) === window) {
|
|
1032
1418
|
serviceWindows.delete(serviceOrigin);
|
|
1033
|
-
|
|
1419
|
+
reportedServiceActivity.delete(serviceOrigin);
|
|
1420
|
+
observedServiceActivity.delete(serviceOrigin);
|
|
1034
1421
|
broadcastServiceActivity();
|
|
1035
1422
|
}
|
|
1036
1423
|
});
|
|
@@ -1042,6 +1429,19 @@ function createDesktopWindow(options) {
|
|
|
1042
1429
|
clearUnreadNotifications();
|
|
1043
1430
|
});
|
|
1044
1431
|
window.on('close', (event) => {
|
|
1432
|
+
const serviceOrigin = windowServiceOrigins.get(window);
|
|
1433
|
+
if (serviceOrigin && !isQuitting && restorableServiceWindows.has(window)) {
|
|
1434
|
+
const config = readDesktopConfig();
|
|
1435
|
+
config.openConnectionUrls = config.openConnectionUrls.filter((url) => {
|
|
1436
|
+
try {
|
|
1437
|
+
return new URL(url).origin !== serviceOrigin;
|
|
1438
|
+
}
|
|
1439
|
+
catch {
|
|
1440
|
+
return false;
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
writeDesktopConfig(config);
|
|
1444
|
+
}
|
|
1045
1445
|
if (window === mainWindow && process.platform === 'darwin' && !isQuitting) {
|
|
1046
1446
|
event.preventDefault();
|
|
1047
1447
|
window.hide();
|
|
@@ -1085,18 +1485,28 @@ function installIpcHandlers() {
|
|
|
1085
1485
|
const origin = windowServiceOrigins.get(sourceWindow);
|
|
1086
1486
|
if (!origin)
|
|
1087
1487
|
return;
|
|
1088
|
-
const
|
|
1089
|
-
const
|
|
1090
|
-
runningCount: normalizeCount(activity?.runningCount),
|
|
1091
|
-
reviewCount: normalizeCount(activity?.reviewCount),
|
|
1092
|
-
};
|
|
1093
|
-
const current = serviceActivity.get(origin);
|
|
1488
|
+
const next = normalizeServiceActivity(activity);
|
|
1489
|
+
const current = reportedServiceActivity.get(origin);
|
|
1094
1490
|
if (current?.runningCount === next.runningCount && current.reviewCount === next.reviewCount) {
|
|
1095
1491
|
// The sender may have just reloaded and still needs the complete roster.
|
|
1096
1492
|
sourceWindow.webContents.send('desktop:service-activity-changed', serviceActivitySnapshot(sourceWindow));
|
|
1097
1493
|
return;
|
|
1098
1494
|
}
|
|
1099
|
-
|
|
1495
|
+
reportedServiceActivity.set(origin, next);
|
|
1496
|
+
broadcastServiceActivity();
|
|
1497
|
+
});
|
|
1498
|
+
ipcMain.on('desktop:observe-service-activity', (event, activity) => {
|
|
1499
|
+
const sourceWindow = BrowserWindow.fromWebContents(event.sender);
|
|
1500
|
+
if (!sourceWindow)
|
|
1501
|
+
return;
|
|
1502
|
+
const origin = windowServiceOrigins.get(sourceWindow);
|
|
1503
|
+
if (!origin)
|
|
1504
|
+
return;
|
|
1505
|
+
const next = normalizeServiceActivity(activity);
|
|
1506
|
+
const current = observedServiceActivity.get(origin);
|
|
1507
|
+
if (current?.runningCount === next.runningCount && current.reviewCount === next.reviewCount)
|
|
1508
|
+
return;
|
|
1509
|
+
observedServiceActivity.set(origin, next);
|
|
1100
1510
|
broadcastServiceActivity();
|
|
1101
1511
|
});
|
|
1102
1512
|
ipcMain.handle('desktop:focus-service', (event, origin) => {
|
|
@@ -1111,6 +1521,36 @@ function installIpcHandlers() {
|
|
|
1111
1521
|
return true;
|
|
1112
1522
|
});
|
|
1113
1523
|
ipcMain.handle('desktop:start-local', () => startLocalService());
|
|
1524
|
+
ipcMain.handle('desktop:update-preferences', async (_event, preferences) => {
|
|
1525
|
+
updateDesktopPreferences({
|
|
1526
|
+
...(typeof preferences?.menuBarStatusEnabled === 'boolean'
|
|
1527
|
+
? { menuBarStatusEnabled: preferences.menuBarStatusEnabled }
|
|
1528
|
+
: {}),
|
|
1529
|
+
...(typeof preferences?.floatingWidgetEnabled === 'boolean'
|
|
1530
|
+
? { floatingWidgetEnabled: preferences.floatingWidgetEnabled }
|
|
1531
|
+
: {}),
|
|
1532
|
+
});
|
|
1533
|
+
return snapshot();
|
|
1534
|
+
});
|
|
1535
|
+
ipcMain.handle('desktop:status-snapshot', () => desktopStatusSnapshot());
|
|
1536
|
+
ipcMain.handle('desktop:focus-next-service', (_event, scope) => {
|
|
1537
|
+
const normalized = scope === 'running'
|
|
1538
|
+
|| scope === 'review'
|
|
1539
|
+
|| scope === 'all'
|
|
1540
|
+
? scope
|
|
1541
|
+
: 'attention';
|
|
1542
|
+
focusNextService(normalized);
|
|
1543
|
+
});
|
|
1544
|
+
ipcMain.handle('desktop:set-floating-metric-count', (event, count) => {
|
|
1545
|
+
const sourceWindow = BrowserWindow.fromWebContents(event.sender);
|
|
1546
|
+
if (sourceWindow !== floatingWidgetWindow || typeof count !== 'number' || !Number.isFinite(count))
|
|
1547
|
+
return;
|
|
1548
|
+
resizeFloatingWidget(count);
|
|
1549
|
+
});
|
|
1550
|
+
ipcMain.handle('desktop:disable-floating-widget', async () => {
|
|
1551
|
+
updateDesktopPreferences({ floatingWidgetEnabled: false });
|
|
1552
|
+
return snapshot();
|
|
1553
|
+
});
|
|
1114
1554
|
ipcMain.handle('desktop:install-cli', () => installCli());
|
|
1115
1555
|
ipcMain.handle('desktop:update-state', () => getDesktopUpdateState());
|
|
1116
1556
|
ipcMain.handle('desktop:check-update', () => checkForDesktopUpdates());
|
|
@@ -1388,6 +1828,9 @@ app.whenReady().then(async () => {
|
|
|
1388
1828
|
configureLocalServiceCertificateTrust();
|
|
1389
1829
|
installIpcHandlers();
|
|
1390
1830
|
installMenu();
|
|
1831
|
+
refreshDesktopStatusSurfaces();
|
|
1832
|
+
screen.on('display-removed', keepFloatingWidgetOnScreen);
|
|
1833
|
+
screen.on('display-metrics-changed', keepFloatingWidgetOnScreen);
|
|
1391
1834
|
configureDesktopUpdater(showDesktopMessageBox);
|
|
1392
1835
|
subscribeDesktopUpdateState((state) => {
|
|
1393
1836
|
for (const window of BrowserWindow.getAllWindows()) {
|
|
@@ -1395,11 +1838,27 @@ app.whenReady().then(async () => {
|
|
|
1395
1838
|
window.webContents.send('desktop:update-state-changed', state);
|
|
1396
1839
|
}
|
|
1397
1840
|
});
|
|
1398
|
-
const
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1841
|
+
const startupConfig = readDesktopConfig();
|
|
1842
|
+
const restoreUrls = startupConfig.openConnectionUrls;
|
|
1843
|
+
let restoredWindowCount = 0;
|
|
1844
|
+
for (const url of restoreUrls) {
|
|
1845
|
+
const probe = await connectWindow(url, { focus: false, updateLastConnection: false });
|
|
1846
|
+
if (probe.ok)
|
|
1847
|
+
restoredWindowCount += 1;
|
|
1848
|
+
}
|
|
1849
|
+
if (restoredWindowCount > 0) {
|
|
1850
|
+
let preferredOrigin = null;
|
|
1851
|
+
try {
|
|
1852
|
+
preferredOrigin = startupConfig.lastConnectionUrl
|
|
1853
|
+
? new URL(startupConfig.lastConnectionUrl).origin
|
|
1854
|
+
: null;
|
|
1855
|
+
}
|
|
1856
|
+
catch {
|
|
1857
|
+
// A malformed legacy value must not prevent the other saved windows
|
|
1858
|
+
// from being restored.
|
|
1859
|
+
}
|
|
1860
|
+
const preferredWindow = preferredOrigin ? serviceWindows.get(preferredOrigin) : null;
|
|
1861
|
+
showAndFocusWindow(preferredWindow ?? [...serviceWindows.values()][0]);
|
|
1403
1862
|
}
|
|
1404
1863
|
else {
|
|
1405
1864
|
await showConnectionCenter();
|