termdock 1.4.107 → 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 +345 -1
- package/dist/client/assets/{ModelPreview-CoR41z2e.js → ModelPreview-Cxk4IZLv.js} +1 -1
- package/dist/client/assets/{abnfDiagram-VRR7QNED-CXFrMEcU.js → abnfDiagram-VRR7QNED-Bz2HWRRX.js} +1 -1
- package/dist/client/assets/{arc-DXT1yvki.js → arc-D3vf35f_.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-B4Tl5gm4.js → architectureDiagram-ZJ3FMSHR-LI-OXsDp.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-D8OY0bG2.js → blockDiagram-677ZJIJ3-Dm1JtLIo.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-BHIhcBy3.js → c4Diagram-LMCZKHZV-DSkxlBnU.js} +1 -1
- package/dist/client/assets/channel-qZtjoGxW.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-NFF-sZCT.js → chunk-2Q5K7J3B-Wd2AO6yQ.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-Cysd2WNQ.js → chunk-32BRIVSS-CIBkFIAg.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-BRHAAr29.js → chunk-5VM5RSS4-9veDV0NJ.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-BovDZYVQ.js → chunk-EX3LRPZG-DVi5mqrV.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-ByWUvKGO.js → chunk-JWPE2WC7-DZc_3_5i.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-GWk63RII.js → chunk-MOJQB5TN-Czq1YGEA.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-GvzHMCqG.js → chunk-RYQCIY6F-BN3UqH_7.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-BoiSCBad.js → chunk-V7JOEXUC-BgDCEp_z.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-ByS_rjkq.js → chunk-VR4S4FIN-BnyRVZSn.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-D9fRuB0M.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-BVOwkeRc.js → cose-bilkent-JH36ORCC-B-P2BstB.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-8rNTW_mJ.js → cynefin-VYW2F7L2-DOSg0Q6x.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-DHASHiYD.js → cynefinDiagram-TSTJHNR4-Bm8Ct13U.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-wWqVXFNE.js → dagre-VKFMJZFB-Cnw99n0y.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-Bp2E7omd.js → diagram-FQU43EPY-B742HZhK.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-DajrBIg7.js → diagram-G47NLZAW-BGXKr2u9.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-ByGTRgNg.js → diagram-NH7WQ7WH-DIyEYN64.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-bsRCtWPe.js → diagram-OA4YK3LP-BdRMWU80.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-9f1_0Bjd.js → diagram-WEI45ONY-Bu-180k8.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-DyvJDQQ5.js → ebnfDiagram-CCIWWBDH-Djcej7Pq.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-DwLHRiMz.js → erDiagram-Q63AITRT-D70JxkGr.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U--9lOgA3z.js → flowDiagram-23GEKE2U-DovWUM1i.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-j9C0N_DS.js → ganttDiagram-NO4QXBWP-Izsfqq5i.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-lHdVffsh.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-D8kAsmdn.js → infoDiagram-FWYZ7A6U-Ina5uVvZ.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-CTY9BLyE.js → ishikawaDiagram-FXEZZL3T-iUdzuACl.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-B3QO8wF2.js → journeyDiagram-5HDEW3XC-lh15NShH.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-CesXysl2.js → kanban-definition-HUTT4EX6-CKZooxMa.js} +1 -1
- package/dist/client/assets/{linear-wxKR9xNu.js → linear-BrmrQdSX.js} +1 -1
- package/dist/client/assets/{mermaid.core-DqsMKQ0n.js → mermaid.core-fbY4nFyi.js} +4 -4
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-CCfpEZEP.js → mindmap-definition-LN4V7U3C-BHUhjMUf.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR-DEje4xdU.js → pegDiagram-2B236MQR-8qcHqxsE.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-B5guqQ7K.js → pieDiagram-ENE6RG2P-CpIkkExH.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-D58kwRrP.js → quadrantDiagram-ABIIQ3AL-hWzvYsWG.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-D9VFfFu0.js → railroadDiagram-RFXS5EU6-2nhrE9bK.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-B0q4l0cr.js → requirementDiagram-TGXJPOKE-B05mEN7w.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-DTsXXi0M.js → sankeyDiagram-HTMAVEWB-CXHkBpDN.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-3Xpi1nmi.js → sequenceDiagram-DBY2YBRQ-C4wG1r_I.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-C0jHd0eC.js → sizeCapture-X5ZJPWSS-D_3i42tQ.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-DlDm_dYg.js → stateDiagram-2N3HPSRC-DhEgczRA.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-D9gqRttI.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-C128cuo6.js → swimlanes-5IMT3BWC-DTrpFviv.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-CNOSFfLB.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-BHsLELm-.js → timeline-definition-FHXFAJF6-DLpl_8Za.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-DkdQFPIy.js → vennDiagram-L72KCM5P-CgnCFHj7.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-DWaahbD5.js → wardleyDiagram-EHGQE667-BwNfD-cz.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-CSwOYmgm.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/filesystem.js +31 -6
- package/dist/server/routes/terminal.js +6 -2
- package/dist/server/utils/heicPreview.js +50 -0
- package/dist/server/utils/runtimeMonitor.js +287 -0
- package/dist/server/utils/settings.js +9 -0
- package/dist/server/workers/heicPreviewWorker.js +14 -0
- package/package.json +3 -1
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/channel-CXw70OW3.js +0 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-CNF7sVVd.js +0 -1
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-CNF7sVVd.js +0 -1
- package/dist/client/assets/index-B1FKcn-W.js +0 -451
- package/dist/client/assets/index-BloZ2WQg.css +0 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-BVOBevpU.js +0 -1
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-Bf87D8dr.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,7 +53,12 @@ function triggerLocalNetworkPermission() {
|
|
|
52
53
|
let mainWindow = null;
|
|
53
54
|
const serviceWindows = new Map();
|
|
54
55
|
const windowServiceOrigins = new WeakMap();
|
|
56
|
+
const reportedServiceActivity = new Map();
|
|
57
|
+
const observedServiceActivity = new Map();
|
|
55
58
|
let lastFocusedServiceWindow = null;
|
|
59
|
+
let menuBarStatus = null;
|
|
60
|
+
let floatingWidgetWindow = null;
|
|
61
|
+
let floatingPositionTimer = null;
|
|
56
62
|
let isQuitting = false;
|
|
57
63
|
function focusedWorkspaceWindow() {
|
|
58
64
|
const focused = BrowserWindow.getFocusedWindow();
|
|
@@ -71,6 +77,29 @@ function showAndFocusWindow(window) {
|
|
|
71
77
|
window.show();
|
|
72
78
|
window.focus();
|
|
73
79
|
}
|
|
80
|
+
function serviceActivitySnapshot(targetWindow) {
|
|
81
|
+
const currentOrigin = windowServiceOrigins.get(targetWindow);
|
|
82
|
+
return [...serviceWindows.entries()].flatMap(([origin, window]) => {
|
|
83
|
+
if (window.isDestroyed())
|
|
84
|
+
return [];
|
|
85
|
+
const activity = mergeServiceActivity(reportedServiceActivity.get(origin), observedServiceActivity.get(origin));
|
|
86
|
+
return [{
|
|
87
|
+
origin,
|
|
88
|
+
label: serviceLabel(origin),
|
|
89
|
+
current: origin === currentOrigin,
|
|
90
|
+
focused: window.isFocused(),
|
|
91
|
+
...activity,
|
|
92
|
+
}];
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function broadcastServiceActivity() {
|
|
96
|
+
for (const window of serviceWindows.values()) {
|
|
97
|
+
if (window.isDestroyed())
|
|
98
|
+
continue;
|
|
99
|
+
window.webContents.send('desktop:service-activity-changed', serviceActivitySnapshot(window));
|
|
100
|
+
}
|
|
101
|
+
refreshDesktopStatusSurfaces();
|
|
102
|
+
}
|
|
74
103
|
function serviceLabel(url) {
|
|
75
104
|
const normalized = normalizeServiceUrl(url);
|
|
76
105
|
const saved = readDesktopConfig().connections.find((entry) => entry.url === normalized);
|
|
@@ -81,6 +110,223 @@ function serviceLabel(url) {
|
|
|
81
110
|
? '本机'
|
|
82
111
|
: parsed.host;
|
|
83
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
|
+
}
|
|
84
330
|
function showDesktopMessageBox(options) {
|
|
85
331
|
const parent = BrowserWindow.getFocusedWindow() ?? mainWindow;
|
|
86
332
|
return parent
|
|
@@ -134,6 +380,28 @@ function defaultConfig() {
|
|
|
134
380
|
connections: [],
|
|
135
381
|
lastConnectionUrl: null,
|
|
136
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,
|
|
137
405
|
};
|
|
138
406
|
}
|
|
139
407
|
function normalizeServiceUrl(raw) {
|
|
@@ -263,6 +531,7 @@ function readDesktopConfig() {
|
|
|
263
531
|
&& typeof entry.subject === 'string'
|
|
264
532
|
&& typeof entry.trustedAt === 'number')
|
|
265
533
|
: [],
|
|
534
|
+
desktopPreferences: normalizeDesktopPreferences(parsed.desktopPreferences),
|
|
266
535
|
};
|
|
267
536
|
}
|
|
268
537
|
catch {
|
|
@@ -593,6 +862,7 @@ async function snapshot() {
|
|
|
593
862
|
localService,
|
|
594
863
|
connections: config.connections,
|
|
595
864
|
lastConnectionUrl: config.lastConnectionUrl,
|
|
865
|
+
desktopPreferences: config.desktopPreferences,
|
|
596
866
|
};
|
|
597
867
|
}
|
|
598
868
|
function desktopRuntimeEnv(runtime = runtimePaths()) {
|
|
@@ -807,6 +1077,7 @@ async function connectWindow(rawUrl) {
|
|
|
807
1077
|
}
|
|
808
1078
|
const workspaceWindow = createDesktopWindow({ serviceOrigin: key, label: serviceLabel(probe.url) });
|
|
809
1079
|
serviceWindows.set(key, workspaceWindow);
|
|
1080
|
+
broadcastServiceActivity();
|
|
810
1081
|
while (!workspaceWindow.isDestroyed()) {
|
|
811
1082
|
try {
|
|
812
1083
|
await workspaceWindow.loadURL(probe.url);
|
|
@@ -1007,11 +1278,15 @@ function createDesktopWindow(options) {
|
|
|
1007
1278
|
const serviceOrigin = windowServiceOrigins.get(window);
|
|
1008
1279
|
if (serviceOrigin && serviceWindows.get(serviceOrigin) === window) {
|
|
1009
1280
|
serviceWindows.delete(serviceOrigin);
|
|
1281
|
+
reportedServiceActivity.delete(serviceOrigin);
|
|
1282
|
+
observedServiceActivity.delete(serviceOrigin);
|
|
1283
|
+
broadcastServiceActivity();
|
|
1010
1284
|
}
|
|
1011
1285
|
});
|
|
1012
1286
|
window.on('focus', () => {
|
|
1013
1287
|
if (windowServiceOrigins.has(window))
|
|
1014
1288
|
lastFocusedServiceWindow = window;
|
|
1289
|
+
broadcastServiceActivity();
|
|
1015
1290
|
// User is looking at the app — the Dock badge has served its purpose.
|
|
1016
1291
|
clearUnreadNotifications();
|
|
1017
1292
|
});
|
|
@@ -1052,7 +1327,73 @@ function installIpcHandlers() {
|
|
|
1052
1327
|
return snapshot();
|
|
1053
1328
|
});
|
|
1054
1329
|
ipcMain.handle('desktop:connect', (_event, url) => connectWindow(url));
|
|
1330
|
+
ipcMain.on('desktop:report-service-activity', (event, activity) => {
|
|
1331
|
+
const sourceWindow = BrowserWindow.fromWebContents(event.sender);
|
|
1332
|
+
if (!sourceWindow)
|
|
1333
|
+
return;
|
|
1334
|
+
const origin = windowServiceOrigins.get(sourceWindow);
|
|
1335
|
+
if (!origin)
|
|
1336
|
+
return;
|
|
1337
|
+
const next = normalizeServiceActivity(activity);
|
|
1338
|
+
const current = reportedServiceActivity.get(origin);
|
|
1339
|
+
if (current?.runningCount === next.runningCount && current.reviewCount === next.reviewCount) {
|
|
1340
|
+
// The sender may have just reloaded and still needs the complete roster.
|
|
1341
|
+
sourceWindow.webContents.send('desktop:service-activity-changed', serviceActivitySnapshot(sourceWindow));
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
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);
|
|
1359
|
+
broadcastServiceActivity();
|
|
1360
|
+
});
|
|
1361
|
+
ipcMain.handle('desktop:focus-service', (event, origin) => {
|
|
1362
|
+
const sourceWindow = BrowserWindow.fromWebContents(event.sender);
|
|
1363
|
+
if (!sourceWindow || !windowServiceOrigins.has(sourceWindow))
|
|
1364
|
+
return false;
|
|
1365
|
+
const targetWindow = serviceWindows.get(origin);
|
|
1366
|
+
if (!targetWindow || targetWindow.isDestroyed())
|
|
1367
|
+
return false;
|
|
1368
|
+
showAndFocusWindow(targetWindow);
|
|
1369
|
+
broadcastServiceActivity();
|
|
1370
|
+
return true;
|
|
1371
|
+
});
|
|
1055
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
|
+
});
|
|
1056
1397
|
ipcMain.handle('desktop:install-cli', () => installCli());
|
|
1057
1398
|
ipcMain.handle('desktop:update-state', () => getDesktopUpdateState());
|
|
1058
1399
|
ipcMain.handle('desktop:check-update', () => checkForDesktopUpdates());
|
|
@@ -1330,6 +1671,9 @@ app.whenReady().then(async () => {
|
|
|
1330
1671
|
configureLocalServiceCertificateTrust();
|
|
1331
1672
|
installIpcHandlers();
|
|
1332
1673
|
installMenu();
|
|
1674
|
+
refreshDesktopStatusSurfaces();
|
|
1675
|
+
screen.on('display-removed', keepFloatingWidgetOnScreen);
|
|
1676
|
+
screen.on('display-metrics-changed', keepFloatingWidgetOnScreen);
|
|
1333
1677
|
configureDesktopUpdater(showDesktopMessageBox);
|
|
1334
1678
|
subscribeDesktopUpdateState((state) => {
|
|
1335
1679
|
for (const window of BrowserWindow.getAllWindows()) {
|