termdock 1.4.108 → 1.4.109
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 +297 -11
- package/dist/client/assets/{ModelPreview-By3cPzJ9.js → ModelPreview-Cxk4IZLv.js} +1 -1
- package/dist/client/assets/{abnfDiagram-VRR7QNED-Dm9g0Pi3.js → abnfDiagram-VRR7QNED-Bz2HWRRX.js} +1 -1
- package/dist/client/assets/{arc-XntYYR9t.js → arc-D3vf35f_.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-CW0kZlVv.js → architectureDiagram-ZJ3FMSHR-LI-OXsDp.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-DgMdh19W.js → blockDiagram-677ZJIJ3-Dm1JtLIo.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-DJC4m2Y6.js → c4Diagram-LMCZKHZV-DSkxlBnU.js} +1 -1
- package/dist/client/assets/channel-qZtjoGxW.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-CgE5oTjY.js → chunk-2Q5K7J3B-Wd2AO6yQ.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-B50lSI4-.js → chunk-32BRIVSS-CIBkFIAg.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-C984gGSO.js → chunk-5VM5RSS4-9veDV0NJ.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-C1jPbFiq.js → chunk-EX3LRPZG-DVi5mqrV.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-D4U9D5Kv.js → chunk-JWPE2WC7-DZc_3_5i.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-C4rLPnf_.js → chunk-MOJQB5TN-Czq1YGEA.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-CnSX68rt.js → chunk-RYQCIY6F-BN3UqH_7.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-LTTPDSzs.js → chunk-V7JOEXUC-BgDCEp_z.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-dCZ9yGOH.js → chunk-VR4S4FIN-BnyRVZSn.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-DiUZr3na.js → chunk-XXDRQBXY-KOa0rQIz.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-CnWXW1tk.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-CnWXW1tk.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-Dw73llr7.js → cose-bilkent-JH36ORCC-B-P2BstB.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-CsB9ZT7a.js → cynefin-VYW2F7L2-DOSg0Q6x.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-NinMHYPB.js → cynefinDiagram-TSTJHNR4-Bm8Ct13U.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-CmFL5D4c.js → dagre-VKFMJZFB-Cnw99n0y.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-D_C52SB6.js → diagram-FQU43EPY-B742HZhK.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-BLlpvNO5.js → diagram-G47NLZAW-BGXKr2u9.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-BNT2NQnR.js → diagram-NH7WQ7WH-DIyEYN64.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-DlJ-T7tl.js → diagram-OA4YK3LP-BdRMWU80.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-D7s_sZnL.js → diagram-WEI45ONY-Bu-180k8.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-PGksmUKG.js → ebnfDiagram-CCIWWBDH-Djcej7Pq.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-CbfrT-bt.js → erDiagram-Q63AITRT-D70JxkGr.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U-CqUGdXdQ.js → flowDiagram-23GEKE2U-DovWUM1i.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-DpH0ybM6.js → ganttDiagram-NO4QXBWP-Izsfqq5i.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-Cmc3LsBg.js → gitGraphDiagram-IHSO6WYX-BTZo0N08.js} +1 -1
- package/dist/client/assets/index-BE4Js_ss.js +456 -0
- package/dist/client/assets/index-CRIZk117.css +1 -0
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-CHzl_SDV.js → infoDiagram-FWYZ7A6U-Ina5uVvZ.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-BUVwtCaN.js → ishikawaDiagram-FXEZZL3T-iUdzuACl.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-DrmO4THP.js → journeyDiagram-5HDEW3XC-lh15NShH.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-DTZoUd1n.js → kanban-definition-HUTT4EX6-CKZooxMa.js} +1 -1
- package/dist/client/assets/{linear-DgCNjduP.js → linear-BrmrQdSX.js} +1 -1
- package/dist/client/assets/{mermaid.core-DLidsBlx.js → mermaid.core-fbY4nFyi.js} +4 -4
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-srFG-Dae.js → mindmap-definition-LN4V7U3C-BHUhjMUf.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR-Qary-lxo.js → pegDiagram-2B236MQR-8qcHqxsE.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-D924FFIQ.js → pieDiagram-ENE6RG2P-CpIkkExH.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-YFBMJKUv.js → quadrantDiagram-ABIIQ3AL-hWzvYsWG.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-C__7fBPw.js → railroadDiagram-RFXS5EU6-2nhrE9bK.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-BtAq5f69.js → requirementDiagram-TGXJPOKE-B05mEN7w.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-BGULYk91.js → sankeyDiagram-HTMAVEWB-CXHkBpDN.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-BBjCjIFm.js → sequenceDiagram-DBY2YBRQ-C4wG1r_I.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-DjP0UZy6.js → sizeCapture-X5ZJPWSS-D_3i42tQ.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-BTqu5zVt.js → stateDiagram-2N3HPSRC-DhEgczRA.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-D9gqRttI.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-ClySuTz2.js → swimlanes-5IMT3BWC-DTrpFviv.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-CNOSFfLB.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-DHURyh7H.js → timeline-definition-FHXFAJF6-DLpl_8Za.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-BFdn5KML.js → vennDiagram-L72KCM5P-CgnCFHj7.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-DKEHwCwO.js → wardleyDiagram-EHGQE667-BwNfD-cz.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-CI5HzOMq.js → xychartDiagram-FW5EYKEG--sZgo_Fw.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,8 +53,12 @@ function triggerLocalNetworkPermission() {
|
|
|
52
53
|
let mainWindow = null;
|
|
53
54
|
const serviceWindows = new Map();
|
|
54
55
|
const windowServiceOrigins = new WeakMap();
|
|
55
|
-
const
|
|
56
|
+
const reportedServiceActivity = new Map();
|
|
57
|
+
const observedServiceActivity = new Map();
|
|
56
58
|
let lastFocusedServiceWindow = null;
|
|
59
|
+
let menuBarStatus = null;
|
|
60
|
+
let floatingWidgetWindow = null;
|
|
61
|
+
let floatingPositionTimer = null;
|
|
57
62
|
let isQuitting = false;
|
|
58
63
|
function focusedWorkspaceWindow() {
|
|
59
64
|
const focused = BrowserWindow.getFocusedWindow();
|
|
@@ -77,7 +82,7 @@ function serviceActivitySnapshot(targetWindow) {
|
|
|
77
82
|
return [...serviceWindows.entries()].flatMap(([origin, window]) => {
|
|
78
83
|
if (window.isDestroyed())
|
|
79
84
|
return [];
|
|
80
|
-
const activity =
|
|
85
|
+
const activity = mergeServiceActivity(reportedServiceActivity.get(origin), observedServiceActivity.get(origin));
|
|
81
86
|
return [{
|
|
82
87
|
origin,
|
|
83
88
|
label: serviceLabel(origin),
|
|
@@ -93,6 +98,7 @@ function broadcastServiceActivity() {
|
|
|
93
98
|
continue;
|
|
94
99
|
window.webContents.send('desktop:service-activity-changed', serviceActivitySnapshot(window));
|
|
95
100
|
}
|
|
101
|
+
refreshDesktopStatusSurfaces();
|
|
96
102
|
}
|
|
97
103
|
function serviceLabel(url) {
|
|
98
104
|
const normalized = normalizeServiceUrl(url);
|
|
@@ -104,6 +110,223 @@ function serviceLabel(url) {
|
|
|
104
110
|
? '本机'
|
|
105
111
|
: parsed.host;
|
|
106
112
|
}
|
|
113
|
+
function allServiceActivity() {
|
|
114
|
+
const focused = BrowserWindow.getFocusedWindow();
|
|
115
|
+
return [...serviceWindows.entries()].flatMap(([origin, window]) => {
|
|
116
|
+
if (window.isDestroyed())
|
|
117
|
+
return [];
|
|
118
|
+
return [{
|
|
119
|
+
origin,
|
|
120
|
+
label: serviceLabel(origin),
|
|
121
|
+
current: window === lastFocusedServiceWindow,
|
|
122
|
+
focused: window === focused,
|
|
123
|
+
...mergeServiceActivity(reportedServiceActivity.get(origin), observedServiceActivity.get(origin)),
|
|
124
|
+
}];
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function desktopStatusSnapshot() {
|
|
128
|
+
const services = allServiceActivity();
|
|
129
|
+
const summary = summarizeServiceActivity(services);
|
|
130
|
+
return {
|
|
131
|
+
...summary,
|
|
132
|
+
text: formatCompactDesktopStatus(summary),
|
|
133
|
+
tooltip: desktopStatusTooltip(summary),
|
|
134
|
+
services,
|
|
135
|
+
preferences: readDesktopConfig().desktopPreferences,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function nextServiceWindow(scope) {
|
|
139
|
+
const currentOrigin = lastFocusedServiceWindow && !lastFocusedServiceWindow.isDestroyed()
|
|
140
|
+
? windowServiceOrigins.get(lastFocusedServiceWindow) ?? null
|
|
141
|
+
: null;
|
|
142
|
+
const origin = nextServiceOrigin(allServiceActivity(), currentOrigin, scope);
|
|
143
|
+
return origin ? serviceWindows.get(origin) ?? null : focusedWorkspaceWindow();
|
|
144
|
+
}
|
|
145
|
+
function focusNextService(scope = 'attention') {
|
|
146
|
+
const target = nextServiceWindow(scope);
|
|
147
|
+
if (target && !target.isDestroyed()) {
|
|
148
|
+
showAndFocusWindow(target);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
void showConnectionCenter();
|
|
152
|
+
}
|
|
153
|
+
function menuBarContextMenu() {
|
|
154
|
+
const status = desktopStatusSnapshot();
|
|
155
|
+
const serviceItems = status.services.length > 0
|
|
156
|
+
? status.services
|
|
157
|
+
.sort((left, right) => right.reviewCount - left.reviewCount
|
|
158
|
+
|| right.runningCount - left.runningCount
|
|
159
|
+
|| left.label.localeCompare(right.label))
|
|
160
|
+
.map((service) => ({
|
|
161
|
+
label: `${service.label}${service.runningCount > 0 ? ` 运行 ${service.runningCount}` : ''}${service.reviewCount > 0 ? ` 待办 ${service.reviewCount}` : ''}`,
|
|
162
|
+
click: () => {
|
|
163
|
+
const target = serviceWindows.get(service.origin);
|
|
164
|
+
if (target && !target.isDestroyed())
|
|
165
|
+
showAndFocusWindow(target);
|
|
166
|
+
},
|
|
167
|
+
}))
|
|
168
|
+
: [{ label: '暂无已打开服务', enabled: false }];
|
|
169
|
+
return Menu.buildFromTemplate([
|
|
170
|
+
{ label: status.tooltip, enabled: false },
|
|
171
|
+
{ type: 'separator' },
|
|
172
|
+
...serviceItems,
|
|
173
|
+
{ type: 'separator' },
|
|
174
|
+
{ label: '连接中心…', click: () => void showConnectionCenter() },
|
|
175
|
+
{
|
|
176
|
+
label: '显示悬浮状态',
|
|
177
|
+
type: 'checkbox',
|
|
178
|
+
checked: status.preferences.floatingWidgetEnabled,
|
|
179
|
+
click: (item) => updateDesktopPreferences({ floatingWidgetEnabled: item.checked }),
|
|
180
|
+
},
|
|
181
|
+
{ type: 'separator' },
|
|
182
|
+
{ role: 'quit' },
|
|
183
|
+
]);
|
|
184
|
+
}
|
|
185
|
+
function refreshMenuBarStatus(status) {
|
|
186
|
+
if (process.platform !== 'darwin' || !status.preferences.menuBarStatusEnabled) {
|
|
187
|
+
menuBarStatus?.destroy();
|
|
188
|
+
menuBarStatus = null;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (!menuBarStatus || menuBarStatus.isDestroyed()) {
|
|
192
|
+
// An empty NativeImage creates a title-only NSStatusItem: no app icon and
|
|
193
|
+
// no icon-width spacer, which is the compact mode requested by users.
|
|
194
|
+
menuBarStatus = new Tray(nativeImage.createEmpty());
|
|
195
|
+
menuBarStatus.on('click', () => focusNextService('attention'));
|
|
196
|
+
menuBarStatus.on('right-click', () => menuBarStatus?.popUpContextMenu(menuBarContextMenu()));
|
|
197
|
+
}
|
|
198
|
+
menuBarStatus.setTitle(status.text);
|
|
199
|
+
menuBarStatus.setToolTip(`${status.tooltip}\n点击循环切换需关注的服务`);
|
|
200
|
+
}
|
|
201
|
+
function constrainFloatingPosition(position) {
|
|
202
|
+
const width = 212;
|
|
203
|
+
const height = 50;
|
|
204
|
+
const display = position
|
|
205
|
+
? screen.getDisplayMatching({ x: position.x, y: position.y, width, height })
|
|
206
|
+
: screen.getPrimaryDisplay();
|
|
207
|
+
const area = display.workArea;
|
|
208
|
+
const preferred = position ?? {
|
|
209
|
+
x: area.x + area.width - width - 18,
|
|
210
|
+
y: area.y + 18,
|
|
211
|
+
};
|
|
212
|
+
return {
|
|
213
|
+
x: Math.min(area.x + area.width - width, Math.max(area.x, Math.round(preferred.x))),
|
|
214
|
+
y: Math.min(area.y + area.height - height, Math.max(area.y, Math.round(preferred.y))),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function createFloatingWidget(position) {
|
|
218
|
+
const bounds = constrainFloatingPosition(position);
|
|
219
|
+
const window = new BrowserWindow({
|
|
220
|
+
title: 'Termdock Status',
|
|
221
|
+
type: 'panel',
|
|
222
|
+
width: 212,
|
|
223
|
+
height: 50,
|
|
224
|
+
x: bounds.x,
|
|
225
|
+
y: bounds.y,
|
|
226
|
+
show: false,
|
|
227
|
+
frame: false,
|
|
228
|
+
transparent: true,
|
|
229
|
+
backgroundColor: 'rgba(0, 0, 0, 0)',
|
|
230
|
+
vibrancy: 'hud',
|
|
231
|
+
visualEffectState: 'active',
|
|
232
|
+
resizable: false,
|
|
233
|
+
maximizable: false,
|
|
234
|
+
minimizable: false,
|
|
235
|
+
fullscreenable: false,
|
|
236
|
+
skipTaskbar: true,
|
|
237
|
+
alwaysOnTop: true,
|
|
238
|
+
acceptFirstMouse: true,
|
|
239
|
+
hasShadow: true,
|
|
240
|
+
webPreferences: {
|
|
241
|
+
preload: path.join(currentDir, 'preload.cjs'),
|
|
242
|
+
nodeIntegration: false,
|
|
243
|
+
contextIsolation: true,
|
|
244
|
+
sandbox: true,
|
|
245
|
+
backgroundThrottling: false,
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
window.setAlwaysOnTop(true, 'floating');
|
|
249
|
+
window.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
|
|
250
|
+
// Keep the auxiliary status panel out of App Expose / Mission Control so it
|
|
251
|
+
// cannot disrupt macOS's three-finger application-window gesture.
|
|
252
|
+
window.setHiddenInMissionControl(true);
|
|
253
|
+
let constrainingMove = false;
|
|
254
|
+
window.on('move', () => {
|
|
255
|
+
if (constrainingMove || window.isDestroyed())
|
|
256
|
+
return;
|
|
257
|
+
const [x, y] = window.getPosition();
|
|
258
|
+
const constrained = constrainFloatingPosition({ x, y });
|
|
259
|
+
if (constrained.x === x && constrained.y === y)
|
|
260
|
+
return;
|
|
261
|
+
constrainingMove = true;
|
|
262
|
+
window.setPosition(constrained.x, constrained.y, false);
|
|
263
|
+
constrainingMove = false;
|
|
264
|
+
});
|
|
265
|
+
window.on('moved', () => {
|
|
266
|
+
if (floatingPositionTimer)
|
|
267
|
+
clearTimeout(floatingPositionTimer);
|
|
268
|
+
floatingPositionTimer = setTimeout(() => {
|
|
269
|
+
floatingPositionTimer = null;
|
|
270
|
+
if (window.isDestroyed())
|
|
271
|
+
return;
|
|
272
|
+
const [x, y] = window.getPosition();
|
|
273
|
+
const config = readDesktopConfig();
|
|
274
|
+
config.desktopPreferences.floatingWidgetPosition = { x, y };
|
|
275
|
+
writeDesktopConfig(config);
|
|
276
|
+
}, 250);
|
|
277
|
+
});
|
|
278
|
+
window.on('closed', () => {
|
|
279
|
+
if (floatingWidgetWindow === window)
|
|
280
|
+
floatingWidgetWindow = null;
|
|
281
|
+
});
|
|
282
|
+
const rendererPath = app.isPackaged
|
|
283
|
+
? path.join(process.resourcesPath, 'renderer', 'floating.html')
|
|
284
|
+
: path.join(projectRoot, 'desktop', 'renderer', 'floating.html');
|
|
285
|
+
void window.loadFile(rendererPath).then(() => window.showInactive());
|
|
286
|
+
return window;
|
|
287
|
+
}
|
|
288
|
+
function refreshFloatingWidget(status) {
|
|
289
|
+
if (process.platform !== 'darwin' || !status.preferences.floatingWidgetEnabled) {
|
|
290
|
+
if (floatingWidgetWindow && !floatingWidgetWindow.isDestroyed())
|
|
291
|
+
floatingWidgetWindow.destroy();
|
|
292
|
+
floatingWidgetWindow = null;
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (!floatingWidgetWindow || floatingWidgetWindow.isDestroyed()) {
|
|
296
|
+
floatingWidgetWindow = createFloatingWidget(status.preferences.floatingWidgetPosition);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function keepFloatingWidgetOnScreen() {
|
|
300
|
+
if (!floatingWidgetWindow || floatingWidgetWindow.isDestroyed())
|
|
301
|
+
return;
|
|
302
|
+
const [x, y] = floatingWidgetWindow.getPosition();
|
|
303
|
+
const constrained = constrainFloatingPosition({ x, y });
|
|
304
|
+
if (constrained.x !== x || constrained.y !== y) {
|
|
305
|
+
floatingWidgetWindow.setPosition(constrained.x, constrained.y, false);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function refreshDesktopStatusSurfaces() {
|
|
309
|
+
if (!app.isReady())
|
|
310
|
+
return;
|
|
311
|
+
const status = desktopStatusSnapshot();
|
|
312
|
+
refreshMenuBarStatus(status);
|
|
313
|
+
refreshFloatingWidget(status);
|
|
314
|
+
for (const window of BrowserWindow.getAllWindows()) {
|
|
315
|
+
if (!window.isDestroyed())
|
|
316
|
+
window.webContents.send('desktop:status-changed', status);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function updateDesktopPreferences(patch) {
|
|
320
|
+
const config = readDesktopConfig();
|
|
321
|
+
if (typeof patch.menuBarStatusEnabled === 'boolean') {
|
|
322
|
+
config.desktopPreferences.menuBarStatusEnabled = patch.menuBarStatusEnabled;
|
|
323
|
+
}
|
|
324
|
+
if (typeof patch.floatingWidgetEnabled === 'boolean') {
|
|
325
|
+
config.desktopPreferences.floatingWidgetEnabled = patch.floatingWidgetEnabled;
|
|
326
|
+
}
|
|
327
|
+
writeDesktopConfig(config);
|
|
328
|
+
refreshDesktopStatusSurfaces();
|
|
329
|
+
}
|
|
107
330
|
function showDesktopMessageBox(options) {
|
|
108
331
|
const parent = BrowserWindow.getFocusedWindow() ?? mainWindow;
|
|
109
332
|
return parent
|
|
@@ -157,6 +380,28 @@ function defaultConfig() {
|
|
|
157
380
|
connections: [],
|
|
158
381
|
lastConnectionUrl: null,
|
|
159
382
|
trustedCertificateAuthorities: [],
|
|
383
|
+
desktopPreferences: {
|
|
384
|
+
menuBarStatusEnabled: true,
|
|
385
|
+
floatingWidgetEnabled: false,
|
|
386
|
+
floatingWidgetPosition: null,
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function normalizeDesktopPreferences(value) {
|
|
391
|
+
const input = value && typeof value === 'object'
|
|
392
|
+
? value
|
|
393
|
+
: {};
|
|
394
|
+
const position = input.floatingWidgetPosition;
|
|
395
|
+
return {
|
|
396
|
+
// The compact, text-only status is the useful default on macOS. Existing
|
|
397
|
+
// desktop.json files therefore gain the feature without a migration step.
|
|
398
|
+
menuBarStatusEnabled: input.menuBarStatusEnabled !== false,
|
|
399
|
+
floatingWidgetEnabled: input.floatingWidgetEnabled === true,
|
|
400
|
+
floatingWidgetPosition: position
|
|
401
|
+
&& Number.isFinite(position.x)
|
|
402
|
+
&& Number.isFinite(position.y)
|
|
403
|
+
? { x: Math.round(position.x), y: Math.round(position.y) }
|
|
404
|
+
: null,
|
|
160
405
|
};
|
|
161
406
|
}
|
|
162
407
|
function normalizeServiceUrl(raw) {
|
|
@@ -286,6 +531,7 @@ function readDesktopConfig() {
|
|
|
286
531
|
&& typeof entry.subject === 'string'
|
|
287
532
|
&& typeof entry.trustedAt === 'number')
|
|
288
533
|
: [],
|
|
534
|
+
desktopPreferences: normalizeDesktopPreferences(parsed.desktopPreferences),
|
|
289
535
|
};
|
|
290
536
|
}
|
|
291
537
|
catch {
|
|
@@ -616,6 +862,7 @@ async function snapshot() {
|
|
|
616
862
|
localService,
|
|
617
863
|
connections: config.connections,
|
|
618
864
|
lastConnectionUrl: config.lastConnectionUrl,
|
|
865
|
+
desktopPreferences: config.desktopPreferences,
|
|
619
866
|
};
|
|
620
867
|
}
|
|
621
868
|
function desktopRuntimeEnv(runtime = runtimePaths()) {
|
|
@@ -830,6 +1077,7 @@ async function connectWindow(rawUrl) {
|
|
|
830
1077
|
}
|
|
831
1078
|
const workspaceWindow = createDesktopWindow({ serviceOrigin: key, label: serviceLabel(probe.url) });
|
|
832
1079
|
serviceWindows.set(key, workspaceWindow);
|
|
1080
|
+
broadcastServiceActivity();
|
|
833
1081
|
while (!workspaceWindow.isDestroyed()) {
|
|
834
1082
|
try {
|
|
835
1083
|
await workspaceWindow.loadURL(probe.url);
|
|
@@ -1030,7 +1278,8 @@ function createDesktopWindow(options) {
|
|
|
1030
1278
|
const serviceOrigin = windowServiceOrigins.get(window);
|
|
1031
1279
|
if (serviceOrigin && serviceWindows.get(serviceOrigin) === window) {
|
|
1032
1280
|
serviceWindows.delete(serviceOrigin);
|
|
1033
|
-
|
|
1281
|
+
reportedServiceActivity.delete(serviceOrigin);
|
|
1282
|
+
observedServiceActivity.delete(serviceOrigin);
|
|
1034
1283
|
broadcastServiceActivity();
|
|
1035
1284
|
}
|
|
1036
1285
|
});
|
|
@@ -1085,18 +1334,28 @@ function installIpcHandlers() {
|
|
|
1085
1334
|
const origin = windowServiceOrigins.get(sourceWindow);
|
|
1086
1335
|
if (!origin)
|
|
1087
1336
|
return;
|
|
1088
|
-
const
|
|
1089
|
-
const
|
|
1090
|
-
runningCount: normalizeCount(activity?.runningCount),
|
|
1091
|
-
reviewCount: normalizeCount(activity?.reviewCount),
|
|
1092
|
-
};
|
|
1093
|
-
const current = serviceActivity.get(origin);
|
|
1337
|
+
const next = normalizeServiceActivity(activity);
|
|
1338
|
+
const current = reportedServiceActivity.get(origin);
|
|
1094
1339
|
if (current?.runningCount === next.runningCount && current.reviewCount === next.reviewCount) {
|
|
1095
1340
|
// The sender may have just reloaded and still needs the complete roster.
|
|
1096
1341
|
sourceWindow.webContents.send('desktop:service-activity-changed', serviceActivitySnapshot(sourceWindow));
|
|
1097
1342
|
return;
|
|
1098
1343
|
}
|
|
1099
|
-
|
|
1344
|
+
reportedServiceActivity.set(origin, next);
|
|
1345
|
+
broadcastServiceActivity();
|
|
1346
|
+
});
|
|
1347
|
+
ipcMain.on('desktop:observe-service-activity', (event, activity) => {
|
|
1348
|
+
const sourceWindow = BrowserWindow.fromWebContents(event.sender);
|
|
1349
|
+
if (!sourceWindow)
|
|
1350
|
+
return;
|
|
1351
|
+
const origin = windowServiceOrigins.get(sourceWindow);
|
|
1352
|
+
if (!origin)
|
|
1353
|
+
return;
|
|
1354
|
+
const next = normalizeServiceActivity(activity);
|
|
1355
|
+
const current = observedServiceActivity.get(origin);
|
|
1356
|
+
if (current?.runningCount === next.runningCount && current.reviewCount === next.reviewCount)
|
|
1357
|
+
return;
|
|
1358
|
+
observedServiceActivity.set(origin, next);
|
|
1100
1359
|
broadcastServiceActivity();
|
|
1101
1360
|
});
|
|
1102
1361
|
ipcMain.handle('desktop:focus-service', (event, origin) => {
|
|
@@ -1111,6 +1370,30 @@ function installIpcHandlers() {
|
|
|
1111
1370
|
return true;
|
|
1112
1371
|
});
|
|
1113
1372
|
ipcMain.handle('desktop:start-local', () => startLocalService());
|
|
1373
|
+
ipcMain.handle('desktop:update-preferences', async (_event, preferences) => {
|
|
1374
|
+
updateDesktopPreferences({
|
|
1375
|
+
...(typeof preferences?.menuBarStatusEnabled === 'boolean'
|
|
1376
|
+
? { menuBarStatusEnabled: preferences.menuBarStatusEnabled }
|
|
1377
|
+
: {}),
|
|
1378
|
+
...(typeof preferences?.floatingWidgetEnabled === 'boolean'
|
|
1379
|
+
? { floatingWidgetEnabled: preferences.floatingWidgetEnabled }
|
|
1380
|
+
: {}),
|
|
1381
|
+
});
|
|
1382
|
+
return snapshot();
|
|
1383
|
+
});
|
|
1384
|
+
ipcMain.handle('desktop:status-snapshot', () => desktopStatusSnapshot());
|
|
1385
|
+
ipcMain.handle('desktop:focus-next-service', (_event, scope) => {
|
|
1386
|
+
const normalized = scope === 'running'
|
|
1387
|
+
|| scope === 'review'
|
|
1388
|
+
|| scope === 'all'
|
|
1389
|
+
? scope
|
|
1390
|
+
: 'attention';
|
|
1391
|
+
focusNextService(normalized);
|
|
1392
|
+
});
|
|
1393
|
+
ipcMain.handle('desktop:disable-floating-widget', async () => {
|
|
1394
|
+
updateDesktopPreferences({ floatingWidgetEnabled: false });
|
|
1395
|
+
return snapshot();
|
|
1396
|
+
});
|
|
1114
1397
|
ipcMain.handle('desktop:install-cli', () => installCli());
|
|
1115
1398
|
ipcMain.handle('desktop:update-state', () => getDesktopUpdateState());
|
|
1116
1399
|
ipcMain.handle('desktop:check-update', () => checkForDesktopUpdates());
|
|
@@ -1388,6 +1671,9 @@ app.whenReady().then(async () => {
|
|
|
1388
1671
|
configureLocalServiceCertificateTrust();
|
|
1389
1672
|
installIpcHandlers();
|
|
1390
1673
|
installMenu();
|
|
1674
|
+
refreshDesktopStatusSurfaces();
|
|
1675
|
+
screen.on('display-removed', keepFloatingWidgetOnScreen);
|
|
1676
|
+
screen.on('display-metrics-changed', keepFloatingWidgetOnScreen);
|
|
1391
1677
|
configureDesktopUpdater(showDesktopMessageBox);
|
|
1392
1678
|
subscribeDesktopUpdateState((state) => {
|
|
1393
1679
|
for (const window of BrowserWindow.getAllWindows()) {
|