@owdproject/core 3.4.0 → 4.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.md +112 -13
  2. package/dist/dom/index.d.mts +65 -0
  3. package/dist/dom/index.d.ts +65 -0
  4. package/dist/dom/index.mjs +224 -0
  5. package/dist/events/index.d.mts +70 -0
  6. package/dist/events/index.d.ts +70 -0
  7. package/dist/events/index.mjs +3 -0
  8. package/dist/index.d.mts +73 -0
  9. package/dist/index.d.ts +73 -0
  10. package/dist/index.mjs +115 -0
  11. package/dist/layout/index.d.mts +41 -0
  12. package/dist/layout/index.d.ts +41 -0
  13. package/dist/layout/index.mjs +64 -0
  14. package/dist/machines/index.d.mts +31 -0
  15. package/dist/machines/index.d.ts +31 -0
  16. package/dist/machines/index.mjs +2 -0
  17. package/dist/managers/index.d.mts +118 -0
  18. package/dist/managers/index.d.ts +118 -0
  19. package/dist/managers/index.mjs +6 -0
  20. package/dist/session/index.d.mts +57 -0
  21. package/dist/session/index.d.ts +57 -0
  22. package/dist/session/index.mjs +65 -0
  23. package/dist/shared/core.B1XxB289.mjs +239 -0
  24. package/dist/shared/core.B3mp7ztE.mjs +237 -0
  25. package/dist/shared/core.BOvFdEVQ.d.ts +114 -0
  26. package/dist/shared/core.CsDFgGnX.mjs +40 -0
  27. package/dist/shared/core.Di1eFcr_.d.mts +114 -0
  28. package/dist/shared/core.Pd_Ogt4g.mjs +101 -0
  29. package/dist/shared/core.b3Xldzyu.mjs +29 -0
  30. package/dist/shared/core.d_A3q1kZ.mjs +869 -0
  31. package/dist/types/index.d.mts +266 -0
  32. package/dist/types/index.d.ts +266 -0
  33. package/dist/types/index.mjs +1 -0
  34. package/package.json +56 -112
  35. package/CHANGELOG.md +0 -98
  36. package/DESKTOP_KERNEL.md +0 -232
  37. package/MIGRATION_3.3.2.md +0 -23
  38. package/MIGRATION_3.4.md +0 -126
  39. package/PLAYGROUND.md +0 -91
  40. package/dist/module.d.mts +0 -13
  41. package/dist/module.json +0 -9
  42. package/dist/module.mjs +0 -254
  43. package/dist/types.d.mts +0 -7
  44. package/index.ts +0 -8
  45. package/kit/authoring.ts +0 -340
  46. package/kit/defineDesktopApp.ts +0 -56
  47. package/module.ts +0 -203
  48. package/runtime/components/Application/DesktopCoreApplicationRender.vue +0 -20
  49. package/runtime/components/Application/DesktopCoreApplicationWindowsRender.vue +0 -38
  50. package/runtime/components/background/DesktopCoreBackground.vue +0 -16
  51. package/runtime/components/core/DesktopCore.vue +0 -43
  52. package/runtime/components/time/DesktopCoreTime.vue +0 -56
  53. package/runtime/components/window/DesktopCoreWindow.vue +0 -215
  54. package/runtime/components/window/DesktopCoreWindowContent.vue +0 -28
  55. package/runtime/components/window/DesktopCoreWindowNav.vue +0 -66
  56. package/runtime/components/window/DesktopCoreWindowSnapHintsBase.vue +0 -74
  57. package/runtime/components/workspace/DesktopCoreWorkspaceEdgeHintsBase.vue +0 -84
  58. package/runtime/composables/useApplicationEntries.ts +0 -51
  59. package/runtime/composables/useApplicationManager.ts +0 -285
  60. package/runtime/composables/useBlockNonInputContextMenu.ts +0 -30
  61. package/runtime/composables/useDesktopConfig.ts +0 -8
  62. package/runtime/composables/useDesktopDialogs.ts +0 -16
  63. package/runtime/composables/useDesktopExtension.ts +0 -8
  64. package/runtime/composables/useDesktopManager.ts +0 -51
  65. package/runtime/composables/useDesktopManifest.ts +0 -17
  66. package/runtime/composables/useDesktopSession.ts +0 -27
  67. package/runtime/composables/useDesktopShellIdentity.ts +0 -99
  68. package/runtime/composables/useDesktopShellOptions.ts +0 -36
  69. package/runtime/composables/useDesktopShellReady.ts +0 -7
  70. package/runtime/composables/useDesktopWorkArea.ts +0 -93
  71. package/runtime/composables/useTerminalManager.ts +0 -159
  72. package/runtime/composables/useToggleWindowMaximize.ts +0 -35
  73. package/runtime/composables/useWindowDragHandlers.ts +0 -47
  74. package/runtime/composables/useWindowSnapDrop.ts +0 -112
  75. package/runtime/composables/useWorkspaceEdgeDrop.ts +0 -176
  76. package/runtime/composables/useWorkspaceManager.ts +0 -67
  77. package/runtime/composables/useWorkspaceOverviewCapture.ts +0 -92
  78. package/runtime/composables/useWorkspaceOverviewLiveScale.ts +0 -151
  79. package/runtime/constants/desktopShellKeys.ts +0 -12
  80. package/runtime/constants/shellLayout.ts +0 -5
  81. package/runtime/dialogs/desktopDialogProvider.ts +0 -73
  82. package/runtime/internal/controllers/ApplicationController.ts +0 -351
  83. package/runtime/internal/controllers/WindowController.ts +0 -547
  84. package/runtime/plugins/01.desktop-shell-init.client.ts +0 -28
  85. package/runtime/plugins/02.desktop-register-desktop-apps.client.ts +0 -13
  86. package/runtime/plugins/resize.client.ts +0 -6
  87. package/runtime/stores/storeApplicationMeta.ts +0 -60
  88. package/runtime/stores/storeApplicationWindows.ts +0 -21
  89. package/runtime/stores/storeDesktop.ts +0 -71
  90. package/runtime/stores/storeDesktopDefaultApps.ts +0 -54
  91. package/runtime/stores/storeDesktopVolume.ts +0 -19
  92. package/runtime/stores/storeDesktopWindow.ts +0 -38
  93. package/runtime/stores/storeDesktopWorkspace.ts +0 -105
  94. package/runtime/stores/storeIds.ts +0 -35
  95. package/runtime/types/desktopMenu.ts +0 -10
  96. package/runtime/utils/initDesktopShell.ts +0 -80
  97. package/runtime/utils/migratePersistedStoreIds.ts +0 -57
  98. package/runtime/utils/utilApp.ts +0 -25
  99. package/runtime/utils/utilCaptureElementToCanvas.ts +0 -21
  100. package/runtime/utils/utilCommon.ts +0 -26
  101. package/runtime/utils/utilDebug.ts +0 -30
  102. package/runtime/utils/utilDetectSnapZone.ts +0 -57
  103. package/runtime/utils/utilTerminal.ts +0 -6
  104. package/runtime/utils/utilWindowControllerAdapter.ts +0 -15
  105. package/runtime/utils/utilWindowLayout.ts +0 -82
  106. package/runtime/utils/utilWindowMaximizeLayout.ts +0 -44
  107. package/runtime/utils/utilWorkspaceWindows.ts +0 -33
  108. package/types/index.d.ts +0 -531
@@ -0,0 +1,869 @@
1
+ import { createActor } from 'xstate';
2
+ import { w as windowMachine, a as applicationMachine } from './core.B3mp7ztE.mjs';
3
+ import { c as computeSnapRect } from './core.CsDFgGnX.mjs';
4
+ import { g as globalDesktopHooks, a as globalIPCBus } from './core.Pd_Ogt4g.mjs';
5
+
6
+ function formatWindowSnapshotValue(value) {
7
+ if (typeof value === "string") return value;
8
+ if (value && typeof value === "object") {
9
+ const entries = Object.entries(value);
10
+ if (entries.length > 0) {
11
+ const [k, v] = entries[0];
12
+ return `${k}.${formatWindowSnapshotValue(v)}`;
13
+ }
14
+ }
15
+ return "normal";
16
+ }
17
+ class WindowInstance {
18
+ id;
19
+ model;
20
+ applicationId;
21
+ config;
22
+ state;
23
+ meta;
24
+ actor;
25
+ isDisposed = false;
26
+ subscribers = /* @__PURE__ */ new Set();
27
+ constructor(options) {
28
+ this.id = options.id;
29
+ this.model = options.model;
30
+ this.applicationId = options.applicationId;
31
+ this.config = { ...options.config };
32
+ this.meta = options.meta ? { ...options.meta } : {};
33
+ const baseState = {
34
+ id: this.id,
35
+ createdAt: Date.now(),
36
+ category: options.config.category,
37
+ workspace: options.initialState?.workspace ?? options.workspace ?? "workspace-1",
38
+ position: options.initialState?.position ? { ...options.initialState.position } : options.config.position ? { ...options.config.position } : { x: 0, y: 0, z: 0 },
39
+ size: options.initialState?.size ? { ...options.initialState.size } : options.config.size ? { ...options.config.size } : { width: 640, height: 480 },
40
+ focused: options.initialState?.focused ?? true,
41
+ active: options.initialState?.active ?? true,
42
+ maximized: options.initialState?.maximized ?? false,
43
+ layout: options.initialState?.layout ?? "normal",
44
+ draggable: options.config.draggable !== false,
45
+ resizable: options.config.resizable !== false,
46
+ minimizable: options.config.minimizable !== false,
47
+ maximizable: options.config.maximizable !== false,
48
+ destroyable: options.config.destroyable !== false,
49
+ ...options.initialState
50
+ };
51
+ this.state = Object.freeze(baseState);
52
+ let initialMachineValue = { normal: "unfocused" };
53
+ if (this.state.active === false) {
54
+ initialMachineValue = "minimized";
55
+ } else if (this.state.maximized === true) {
56
+ initialMachineValue = this.state.focused ? { maximized: "focused" } : { maximized: "unfocused" };
57
+ } else if (this.state.focused === true) {
58
+ initialMachineValue = { normal: { focused: "idle" } };
59
+ }
60
+ this.actor = createActor(windowMachine, {
61
+ state: windowMachine.resolveState({
62
+ value: initialMachineValue,
63
+ context: {
64
+ wasMaximized: !!this.state.maximized,
65
+ config: this.config
66
+ }
67
+ })
68
+ });
69
+ this.actor.subscribe((snapshot) => {
70
+ if (this.isDisposed) return;
71
+ const isMinimized = snapshot.matches("minimized");
72
+ const isMaximized = snapshot.matches("maximized");
73
+ const isFocused = snapshot.matches("normal.focused") || snapshot.matches("maximized.focused");
74
+ const isDragging = snapshot.matches("normal.focused.dragging");
75
+ const isResizing = snapshot.matches("normal.focused.resizing");
76
+ const nextActive = !isMinimized;
77
+ if (this.state.active !== nextActive || this.state.maximized !== isMaximized || this.state.focused !== isFocused || this.state.dragging !== isDragging || this.state.resizing !== isResizing) {
78
+ this.updateState({
79
+ active: nextActive,
80
+ maximized: isMaximized,
81
+ focused: isFocused,
82
+ dragging: isDragging,
83
+ resizing: isResizing
84
+ });
85
+ }
86
+ });
87
+ this.actor.start();
88
+ }
89
+ updateState(patch) {
90
+ this.state = Object.freeze({
91
+ ...this.state,
92
+ ...patch
93
+ });
94
+ this.notify();
95
+ }
96
+ getSnapshot() {
97
+ return this.state;
98
+ }
99
+ subscribe(callback) {
100
+ this.subscribers.add(callback);
101
+ callback(this.state);
102
+ return () => {
103
+ this.subscribers.delete(callback);
104
+ };
105
+ }
106
+ notify() {
107
+ for (const sub of this.subscribers) {
108
+ try {
109
+ sub(this.state);
110
+ } catch {
111
+ }
112
+ }
113
+ }
114
+ get stateValue() {
115
+ return formatWindowSnapshotValue(this.actor.getSnapshot().value);
116
+ }
117
+ setPosition(data) {
118
+ this.updateState({
119
+ position: {
120
+ x: Math.round(data.x),
121
+ y: Math.round(data.y),
122
+ z: data.z !== void 0 ? data.z : this.state.position?.z ?? 0
123
+ }
124
+ });
125
+ }
126
+ setSize(data) {
127
+ this.updateState({
128
+ size: {
129
+ ...this.state.size,
130
+ width: Math.round(data.width),
131
+ height: Math.round(data.height)
132
+ }
133
+ });
134
+ }
135
+ setFocus(value) {
136
+ if (this.isDisposed) return;
137
+ if (value) {
138
+ if (!this.state.focused) {
139
+ this.actor.send({ type: "FOCUS" });
140
+ }
141
+ } else {
142
+ if (this.state.focused) {
143
+ this.actor.send({ type: "BLUR" });
144
+ }
145
+ }
146
+ }
147
+ minimize() {
148
+ if (this.isDisposed || !this.state.minimizable) return false;
149
+ this.actor.send({ type: "MINIMIZE" });
150
+ return true;
151
+ }
152
+ unminimize() {
153
+ if (this.isDisposed) return false;
154
+ this.actor.send({ type: "RESTORE" });
155
+ return true;
156
+ }
157
+ maximize() {
158
+ if (this.isDisposed || !this.state.maximizable) return false;
159
+ this.actor.send({ type: "MAXIMIZE" });
160
+ return true;
161
+ }
162
+ unmaximize() {
163
+ if (this.isDisposed || !this.state.maximizable) return false;
164
+ this.actor.send({ type: "RESTORE" });
165
+ return true;
166
+ }
167
+ toggleMaximize() {
168
+ if (this.isDisposed || !this.state.maximizable) return false;
169
+ if (this.state.maximized) {
170
+ return this.unmaximize();
171
+ } else {
172
+ return this.maximize();
173
+ }
174
+ }
175
+ startDrag() {
176
+ if (!this.isDisposed) this.actor.send({ type: "DRAG_START" });
177
+ }
178
+ stopDrag() {
179
+ if (!this.isDisposed) this.actor.send({ type: "DRAG_END" });
180
+ }
181
+ startResize() {
182
+ if (!this.isDisposed) this.actor.send({ type: "RESIZE_START" });
183
+ }
184
+ stopResize() {
185
+ if (!this.isDisposed) this.actor.send({ type: "RESIZE_END" });
186
+ }
187
+ setLayout(layout, workarea) {
188
+ if (this.isDisposed) return false;
189
+ if (layout === "normal") {
190
+ this.actor.send({ type: "RESTORE" });
191
+ this.clearLayout();
192
+ return true;
193
+ }
194
+ if (!this.state.maximizable) return false;
195
+ if (layout === "maximize") {
196
+ this.saveBoundsBeforeLayout();
197
+ this.actor.send({ type: "MAXIMIZE" });
198
+ this.updateState({ layout: "maximize", maximized: true });
199
+ if (workarea && workarea.width > 0 && workarea.height > 0) {
200
+ this.setPosition({ x: workarea.x, y: workarea.y });
201
+ this.setSize({ width: workarea.width, height: workarea.height });
202
+ }
203
+ } else {
204
+ this.actor.send({ type: "RESTORE" });
205
+ this.saveBoundsBeforeLayout();
206
+ this.updateState({ layout, maximized: false });
207
+ if (workarea && workarea.width > 0 && workarea.height > 0) {
208
+ const rect = computeSnapRect(layout, workarea);
209
+ this.setPosition({ x: rect.x, y: rect.y });
210
+ this.setSize({ width: rect.width, height: rect.height });
211
+ }
212
+ }
213
+ return true;
214
+ }
215
+ clearLayout() {
216
+ const bounds = this.state.boundsBeforeLayout;
217
+ const nextState = {
218
+ layout: "normal",
219
+ maximized: false,
220
+ boundsBeforeLayout: void 0
221
+ };
222
+ if (bounds) {
223
+ nextState.position = {
224
+ x: Math.round(bounds.x),
225
+ y: Math.round(bounds.y),
226
+ z: this.state.position?.z ?? 0
227
+ };
228
+ nextState.size = {
229
+ ...this.state.size,
230
+ width: Math.round(bounds.width),
231
+ height: Math.round(bounds.height)
232
+ };
233
+ }
234
+ this.updateState(nextState);
235
+ return true;
236
+ }
237
+ saveBoundsBeforeLayout() {
238
+ if (this.state.layout && this.state.layout !== "normal") return;
239
+ const pos = this.state.position ?? { x: 0, y: 0 };
240
+ const size = this.state.size ?? { width: 640, height: 480 };
241
+ const bounds = {
242
+ x: pos.x ?? 0,
243
+ y: pos.y ?? 0,
244
+ width: typeof size.width === "number" ? size.width : 640,
245
+ height: typeof size.height === "number" ? size.height : 480
246
+ };
247
+ this.updateState({ boundsBeforeLayout: bounds });
248
+ }
249
+ setWorkspace(workspaceId) {
250
+ this.updateState({ workspace: workspaceId });
251
+ }
252
+ get workspace() {
253
+ return this.state.workspace;
254
+ }
255
+ setSpatialTransform(transform) {
256
+ this.updateState({ spatial: { ...transform } });
257
+ }
258
+ get spatial() {
259
+ return this.state.spatial;
260
+ }
261
+ dispose() {
262
+ if (this.isDisposed) return;
263
+ this.isDisposed = true;
264
+ try {
265
+ this.actor.stop();
266
+ } catch {
267
+ }
268
+ this.subscribers.clear();
269
+ }
270
+ }
271
+ class WindowManager {
272
+ windows = /* @__PURE__ */ new Map();
273
+ snapshotCache = Object.freeze([]);
274
+ zIndexCounter = 100;
275
+ subscribers = /* @__PURE__ */ new Set();
276
+ hooks;
277
+ ipc;
278
+ constructor(options) {
279
+ this.hooks = options?.hooks ?? globalDesktopHooks;
280
+ this.ipc = options?.ipc ?? globalIPCBus;
281
+ }
282
+ getSnapshot() {
283
+ return this.snapshotCache;
284
+ }
285
+ subscribe(callback) {
286
+ this.subscribers.add(callback);
287
+ callback(this.getSnapshot());
288
+ return () => {
289
+ this.subscribers.delete(callback);
290
+ };
291
+ }
292
+ notify() {
293
+ this.snapshotCache = Object.freeze(Array.from(this.windows.values()));
294
+ const snapshot = this.getSnapshot();
295
+ for (const sub of this.subscribers) {
296
+ try {
297
+ sub(snapshot);
298
+ } catch {
299
+ }
300
+ }
301
+ }
302
+ get(id) {
303
+ return this.windows.get(id);
304
+ }
305
+ get focusedWindow() {
306
+ return Array.from(this.windows.values()).find((w) => w.state.focused);
307
+ }
308
+ incrementZIndex() {
309
+ this.zIndexCounter += 2;
310
+ return this.zIndexCounter;
311
+ }
312
+ addWindow(instance) {
313
+ this.windows.set(instance.id, instance);
314
+ this.focusWindow(instance.id);
315
+ this.notify();
316
+ }
317
+ createWindow(options) {
318
+ const instance = new WindowInstance(options);
319
+ this.addWindow(instance);
320
+ this.ipc.emit("window:opened", { id: instance.id, model: instance.model }, { source: "window-manager" });
321
+ return instance;
322
+ }
323
+ focusWindow(id) {
324
+ const target = this.windows.get(id);
325
+ if (!target) return;
326
+ let previousWindow;
327
+ for (const win of this.windows.values()) {
328
+ if (win.id !== id && win.state.focused) {
329
+ previousWindow = win;
330
+ win.setFocus(false);
331
+ void this.hooks.callHook("window:blurred", { window: win });
332
+ }
333
+ }
334
+ target.setPosition({
335
+ x: target.state.position?.x ?? 0,
336
+ y: target.state.position?.y ?? 0,
337
+ z: this.incrementZIndex()
338
+ });
339
+ target.setFocus(true);
340
+ void this.hooks.callHook("window:focused", { window: target, previousWindow });
341
+ this.notify();
342
+ }
343
+ closeWindow(id) {
344
+ const target = this.windows.get(id);
345
+ if (!target) return false;
346
+ target.dispose();
347
+ this.windows.delete(id);
348
+ const remaining = Array.from(this.windows.values()).filter((w) => w.state.active).sort((a, b) => (b.state.position?.z ?? 0) - (a.state.position?.z ?? 0));
349
+ if (remaining.length > 0) {
350
+ this.focusWindow(remaining[0].id);
351
+ }
352
+ this.ipc.emit("window:closed", { id }, { source: "window-manager" });
353
+ this.notify();
354
+ return true;
355
+ }
356
+ closeAllWindows() {
357
+ for (const win of this.windows.values()) {
358
+ win.dispose();
359
+ }
360
+ this.windows.clear();
361
+ this.notify();
362
+ }
363
+ tileWindows(mode = "horizontal", workarea, workspaceId, gap = 0) {
364
+ const activeWins = Array.from(this.windows.values()).filter(
365
+ (w) => w.state.active && (!workspaceId || w.state.workspace === workspaceId)
366
+ );
367
+ if (activeWins.length === 0 || workarea.width <= 0 || workarea.height <= 0) {
368
+ return 0;
369
+ }
370
+ const count = activeWins.length;
371
+ const safeGap = Math.max(0, Math.round(gap));
372
+ if (mode === "horizontal") {
373
+ const totalGap = safeGap * (count - 1);
374
+ const colWidth = Math.floor((workarea.width - totalGap) / count);
375
+ activeWins.forEach((win, index) => {
376
+ win.saveBoundsBeforeLayout();
377
+ win.setLayout("normal");
378
+ win.setPosition({
379
+ x: workarea.x + index * (colWidth + safeGap),
380
+ y: workarea.y
381
+ });
382
+ win.setSize({
383
+ width: index === count - 1 ? workarea.width - (count - 1) * (colWidth + safeGap) : colWidth,
384
+ height: workarea.height
385
+ });
386
+ });
387
+ } else if (mode === "vertical") {
388
+ const totalGap = safeGap * (count - 1);
389
+ const rowHeight = Math.floor((workarea.height - totalGap) / count);
390
+ activeWins.forEach((win, index) => {
391
+ win.saveBoundsBeforeLayout();
392
+ win.setLayout("normal");
393
+ win.setPosition({
394
+ x: workarea.x,
395
+ y: workarea.y + index * (rowHeight + safeGap)
396
+ });
397
+ win.setSize({
398
+ width: workarea.width,
399
+ height: index === count - 1 ? workarea.height - (count - 1) * (rowHeight + safeGap) : rowHeight
400
+ });
401
+ });
402
+ } else if (mode === "grid") {
403
+ const cols = Math.ceil(Math.sqrt(count));
404
+ const rows = Math.ceil(count / cols);
405
+ const colWidth = Math.floor((workarea.width - safeGap * (cols - 1)) / cols);
406
+ const rowHeight = Math.floor((workarea.height - safeGap * (rows - 1)) / rows);
407
+ activeWins.forEach((win, index) => {
408
+ const col = index % cols;
409
+ const row = Math.floor(index / cols);
410
+ win.saveBoundsBeforeLayout();
411
+ win.setLayout("normal");
412
+ win.setPosition({
413
+ x: workarea.x + col * (colWidth + safeGap),
414
+ y: workarea.y + row * (rowHeight + safeGap)
415
+ });
416
+ win.setSize({
417
+ width: col === cols - 1 ? workarea.width - (cols - 1) * (colWidth + safeGap) : colWidth,
418
+ height: row === rows - 1 ? workarea.height - (rows - 1) * (rowHeight + safeGap) : rowHeight
419
+ });
420
+ });
421
+ }
422
+ this.notify();
423
+ return count;
424
+ }
425
+ setSpatialTransform(windowId, transform) {
426
+ const win = this.windows.get(windowId);
427
+ if (win) {
428
+ win.setSpatialTransform(transform);
429
+ this.notify();
430
+ }
431
+ }
432
+ }
433
+
434
+ class WorkspaceManager {
435
+ state;
436
+ subscribers = /* @__PURE__ */ new Set();
437
+ hooks;
438
+ ipc;
439
+ constructor(options) {
440
+ this.hooks = options?.hooks ?? globalDesktopHooks;
441
+ this.ipc = options?.ipc ?? globalIPCBus;
442
+ this.state = Object.freeze({
443
+ workspaces: Object.freeze([...options?.workspaces ?? ["workspace-1", "workspace-2", "workspace-3", "workspace-4"]]),
444
+ active: options?.active ?? "workspace-1",
445
+ workarea: Object.freeze({ ...options?.workarea ?? { x: 0, y: 0, width: 0, height: 0 } })
446
+ });
447
+ }
448
+ updateState(patch) {
449
+ this.state = Object.freeze({
450
+ workspaces: patch.workspaces ? Object.freeze([...patch.workspaces]) : this.state.workspaces,
451
+ active: patch.active ?? this.state.active,
452
+ workarea: patch.workarea ? Object.freeze({ ...patch.workarea }) : this.state.workarea
453
+ });
454
+ this.notify();
455
+ }
456
+ getSnapshot() {
457
+ return this.state;
458
+ }
459
+ subscribe(callback) {
460
+ this.subscribers.add(callback);
461
+ callback(this.state);
462
+ return () => {
463
+ this.subscribers.delete(callback);
464
+ };
465
+ }
466
+ notify() {
467
+ for (const sub of this.subscribers) {
468
+ try {
469
+ sub(this.state);
470
+ } catch {
471
+ }
472
+ }
473
+ }
474
+ get activeWorkspace() {
475
+ return this.state.active;
476
+ }
477
+ get workspaces() {
478
+ return [...this.state.workspaces];
479
+ }
480
+ get workarea() {
481
+ return { ...this.state.workarea };
482
+ }
483
+ async setWorkspace(workspaceId) {
484
+ if (!this.state.workspaces.includes(workspaceId)) {
485
+ return false;
486
+ }
487
+ const previous = this.state.active;
488
+ if (previous === workspaceId) return true;
489
+ const switchCtx = { from: previous, to: workspaceId, cancel: false };
490
+ await this.hooks.callHook("workspace:before-switch", switchCtx);
491
+ if (switchCtx.cancel) return false;
492
+ this.updateState({ active: workspaceId });
493
+ await this.hooks.callHook("workspace:switched", { from: previous, to: workspaceId });
494
+ this.ipc.emit("workspace:switched", { from: previous, to: workspaceId }, { source: "workspace-manager" });
495
+ return true;
496
+ }
497
+ setWorkarea(workarea) {
498
+ this.updateState({ workarea });
499
+ }
500
+ addWorkspace(id) {
501
+ if (!this.state.workspaces.includes(id)) {
502
+ this.updateState({ workspaces: [...this.state.workspaces, id] });
503
+ }
504
+ }
505
+ removeWorkspace(id) {
506
+ if (this.state.workspaces.length <= 1 || !this.state.workspaces.includes(id)) {
507
+ return false;
508
+ }
509
+ const nextWorkspaces = this.state.workspaces.filter((w) => w !== id);
510
+ const nextActive = this.state.active === id ? nextWorkspaces[0] : this.state.active;
511
+ this.updateState({ workspaces: nextWorkspaces, active: nextActive });
512
+ return true;
513
+ }
514
+ }
515
+
516
+ function formatAppSnapshotValue(value) {
517
+ if (typeof value === "string") return value;
518
+ if (value && typeof value === "object") {
519
+ const entries = Object.entries(value);
520
+ if (entries.length > 0) {
521
+ const [k, v] = entries[0];
522
+ return `${k}.${formatAppSnapshotValue(v)}`;
523
+ }
524
+ }
525
+ return "idle";
526
+ }
527
+ class ApplicationInstance {
528
+ id;
529
+ config;
530
+ actor;
531
+ _pid = null;
532
+ _startedAt = null;
533
+ _windowIds = /* @__PURE__ */ new Set();
534
+ snapshot;
535
+ subscribers = /* @__PURE__ */ new Set();
536
+ windowManager;
537
+ workspaceManager;
538
+ hooks;
539
+ ipc;
540
+ allocatePid;
541
+ constructor(options) {
542
+ this.id = options.config.id;
543
+ this.config = { ...options.config };
544
+ this.windowManager = options.windowManager;
545
+ this.workspaceManager = options.workspaceManager;
546
+ this.hooks = options.hooks ?? globalDesktopHooks;
547
+ this.ipc = options.ipc ?? globalIPCBus;
548
+ this.allocatePid = options.allocatePid;
549
+ this.actor = createActor(applicationMachine, {
550
+ input: { id: this.id, config: this.config }
551
+ });
552
+ this.snapshot = this.computeSnapshot("idle");
553
+ this.actor.subscribe((stateSnapshot) => {
554
+ const stateVal = formatAppSnapshotValue(stateSnapshot.value);
555
+ if (stateVal === "terminating") {
556
+ this._pid = null;
557
+ this._startedAt = null;
558
+ this.actor.send({ type: "TERMINATED" });
559
+ return;
560
+ }
561
+ this.snapshot = this.computeSnapshot(stateVal);
562
+ this.notify();
563
+ });
564
+ this.actor.start();
565
+ this.actor.send({ type: "LAUNCH" });
566
+ this.actor.send({ type: "INITIALIZED" });
567
+ }
568
+ computeSnapshot(stateValue) {
569
+ const currentVal = stateValue ?? formatAppSnapshotValue(this.actor.getSnapshot().value);
570
+ return Object.freeze({
571
+ id: this.id,
572
+ title: this.config.title,
573
+ icon: this.config.icon,
574
+ category: this.config.category,
575
+ stateValue: currentVal,
576
+ pid: this._pid,
577
+ startedAt: this._startedAt,
578
+ isRunning: currentVal.startsWith("running"),
579
+ isSuspended: currentVal === "running.suspended",
580
+ windowIds: Object.freeze(Array.from(this._windowIds))
581
+ });
582
+ }
583
+ refreshSnapshot() {
584
+ this.snapshot = this.computeSnapshot();
585
+ this.notify();
586
+ }
587
+ notify() {
588
+ for (const sub of this.subscribers) {
589
+ try {
590
+ sub(this.snapshot);
591
+ } catch {
592
+ }
593
+ }
594
+ }
595
+ getSnapshot() {
596
+ return this.snapshot;
597
+ }
598
+ subscribe(callback) {
599
+ this.subscribers.add(callback);
600
+ callback(this.snapshot);
601
+ return () => {
602
+ this.subscribers.delete(callback);
603
+ };
604
+ }
605
+ get pid() {
606
+ return this._pid;
607
+ }
608
+ get startedAt() {
609
+ return this._startedAt;
610
+ }
611
+ get stateValue() {
612
+ return this.snapshot.stateValue;
613
+ }
614
+ get isRunning() {
615
+ return this.snapshot.isRunning;
616
+ }
617
+ get isSuspended() {
618
+ return this.snapshot.isSuspended;
619
+ }
620
+ hasPermission(permission) {
621
+ if (!this.config.permissions) return true;
622
+ return this.config.permissions.includes(permission);
623
+ }
624
+ ensureProcessStarted() {
625
+ if (this._pid === null) {
626
+ this._pid = this.allocatePid();
627
+ this._startedAt = Date.now();
628
+ }
629
+ }
630
+ reuseSingletonWindow() {
631
+ if (!this.config.singleton || this._windowIds.size === 0) return void 0;
632
+ const existingId = Array.from(this._windowIds)[0];
633
+ const existingWindow = this.windowManager.get(existingId);
634
+ if (!existingWindow) return void 0;
635
+ existingWindow.unminimize();
636
+ this.windowManager.focusWindow(existingWindow.id);
637
+ return existingWindow;
638
+ }
639
+ async launch(entryKey, argument) {
640
+ const entries = this.config.entries ?? {};
641
+ const resolvedKey = entryKey ?? Object.keys(entries)[0];
642
+ const entry = resolvedKey ? entries[resolvedKey] : void 0;
643
+ const launchCtx = { app: this, entryKey: resolvedKey, argument, cancel: false };
644
+ await this.hooks.callHook("app:before-launch", launchCtx);
645
+ if (launchCtx.cancel) return false;
646
+ this.ensureProcessStarted();
647
+ this.actor.send({ type: "LAUNCH" });
648
+ if (entry) {
649
+ if (typeof entry.command === "function") {
650
+ await entry.command(this, argument);
651
+ } else if (typeof entry.command === "string" && this.config.commands) {
652
+ const parts = entry.command.trim().split(/\s+/);
653
+ const cmdName = parts[0];
654
+ const fn = cmdName ? this.config.commands[cmdName] : void 0;
655
+ if (fn) {
656
+ await fn(this, argument ?? parts.slice(1).join(" "));
657
+ }
658
+ }
659
+ } else if (this.config.windows) {
660
+ const firstModel = Object.keys(this.config.windows)[0];
661
+ if (firstModel) {
662
+ await this.openWindow(firstModel);
663
+ }
664
+ }
665
+ await this.config.onLaunch?.(this);
666
+ this.refreshSnapshot();
667
+ await this.hooks.callHook("app:launched", { app: this, pid: this._pid });
668
+ this.ipc.emit("app:launched", { id: this.id, pid: this._pid }, { source: this.id });
669
+ return true;
670
+ }
671
+ async openWindow(model, meta, initialState) {
672
+ const windowConfig = this.config.windows?.[model] ?? {
673
+ title: this.config.title,
674
+ icon: this.config.icon,
675
+ category: this.config.category,
676
+ size: { width: 640, height: 480 }
677
+ };
678
+ const existingWindow = this.reuseSingletonWindow();
679
+ if (existingWindow) return existingWindow;
680
+ const openCtx = {
681
+ app: this,
682
+ model,
683
+ config: windowConfig,
684
+ meta,
685
+ cancel: false
686
+ };
687
+ await this.hooks.callHook("window:before-open", openCtx);
688
+ if (openCtx.cancel) return void 0;
689
+ const concurrentWindow = this.reuseSingletonWindow();
690
+ if (concurrentWindow) return concurrentWindow;
691
+ this.ensureProcessStarted();
692
+ const winId = `${this.id}-${model}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
693
+ const workspace = initialState?.workspace ?? this.workspaceManager?.activeWorkspace ?? "workspace-1";
694
+ const instance = this.windowManager.createWindow({
695
+ id: winId,
696
+ model,
697
+ applicationId: this.id,
698
+ config: {
699
+ ...windowConfig,
700
+ title: windowConfig.title ?? this.config.title,
701
+ icon: windowConfig.icon ?? this.config.icon,
702
+ category: windowConfig.category ?? this.config.category
703
+ },
704
+ initialState,
705
+ meta,
706
+ workspace
707
+ });
708
+ this._windowIds.add(instance.id);
709
+ this.actor.send({ type: "OPEN_WINDOW" });
710
+ this.refreshSnapshot();
711
+ await this.hooks.callHook("window:opened", { window: instance, app: this });
712
+ return instance;
713
+ }
714
+ async closeWindow(windowId) {
715
+ const win = this.windowManager.get(windowId);
716
+ if (!win) return false;
717
+ const closeCtx = { window: win, app: this, cancel: false };
718
+ await this.hooks.callHook("window:before-close", closeCtx);
719
+ if (closeCtx.cancel) return false;
720
+ this._windowIds.delete(windowId);
721
+ this.windowManager.closeWindow(windowId);
722
+ this.actor.send({ type: "CLOSE_WINDOW" });
723
+ this.refreshSnapshot();
724
+ await this.hooks.callHook("window:closed", { windowId, app: this });
725
+ return true;
726
+ }
727
+ closeAllWindows() {
728
+ for (const id of Array.from(this._windowIds)) {
729
+ this.windowManager.closeWindow(id);
730
+ }
731
+ this._windowIds.clear();
732
+ this.refreshSnapshot();
733
+ }
734
+ async suspend() {
735
+ if (!this.isRunning || this.isSuspended) return false;
736
+ const ctx = { app: this, signal: "SIGSTOP", cancel: false };
737
+ await this.hooks.callHook("app:before-suspend", ctx);
738
+ if (ctx.cancel) return false;
739
+ for (const id of this._windowIds) {
740
+ this.windowManager.get(id)?.minimize();
741
+ }
742
+ this.actor.send({ type: "SIGSTOP" });
743
+ this.refreshSnapshot();
744
+ await this.hooks.callHook("app:suspended", { app: this });
745
+ this.ipc.emit("process:suspended", { id: this.id, pid: this._pid }, { source: "kernel" });
746
+ return true;
747
+ }
748
+ async resume() {
749
+ if (!this.isSuspended) return false;
750
+ const ctx = { app: this, signal: "SIGCONT", cancel: false };
751
+ await this.hooks.callHook("app:before-resume", ctx);
752
+ if (ctx.cancel) return false;
753
+ for (const id of this._windowIds) {
754
+ const win = this.windowManager.get(id);
755
+ if (win) {
756
+ win.unminimize();
757
+ this.windowManager.focusWindow(win.id);
758
+ }
759
+ }
760
+ this.actor.send({ type: "SIGCONT" });
761
+ this.refreshSnapshot();
762
+ await this.hooks.callHook("app:resumed", { app: this });
763
+ this.ipc.emit("process:resumed", { id: this.id, pid: this._pid }, { source: "kernel" });
764
+ return true;
765
+ }
766
+ async terminate(signal = "SIGTERM") {
767
+ const ctx = { app: this, signal, cancel: false };
768
+ await this.hooks.callHook("app:before-terminate", ctx);
769
+ if (ctx.cancel && signal !== "SIGKILL") return false;
770
+ const oldPid = this._pid;
771
+ this.closeAllWindows();
772
+ await this.config.onClose?.(this);
773
+ this._pid = null;
774
+ this._startedAt = null;
775
+ this.actor.send({ type: signal });
776
+ this.refreshSnapshot();
777
+ await this.hooks.callHook("app:terminated", {
778
+ id: this.id,
779
+ title: this.config.title,
780
+ pid: oldPid
781
+ });
782
+ this.ipc.emit(
783
+ "process:terminated",
784
+ { id: this.id, pid: oldPid, signal },
785
+ { source: "kernel", severity: signal === "SIGKILL" ? "warn" : "info" }
786
+ );
787
+ return true;
788
+ }
789
+ }
790
+ class ApplicationManager {
791
+ apps = /* @__PURE__ */ new Map();
792
+ snapshotCache = Object.freeze([]);
793
+ pidCounter = 1e3;
794
+ subscribers = /* @__PURE__ */ new Set();
795
+ windowManager;
796
+ workspaceManager;
797
+ hooks;
798
+ ipc;
799
+ constructor(options) {
800
+ this.windowManager = options.windowManager;
801
+ this.workspaceManager = options.workspaceManager;
802
+ this.hooks = options.hooks ?? globalDesktopHooks;
803
+ this.ipc = options.ipc ?? globalIPCBus;
804
+ }
805
+ getSnapshot() {
806
+ return this.snapshotCache;
807
+ }
808
+ subscribe(callback) {
809
+ this.subscribers.add(callback);
810
+ callback(this.getSnapshot());
811
+ return () => {
812
+ this.subscribers.delete(callback);
813
+ };
814
+ }
815
+ notify() {
816
+ this.snapshotCache = Object.freeze(Array.from(this.apps.values()));
817
+ const snapshot = this.getSnapshot();
818
+ for (const sub of this.subscribers) {
819
+ try {
820
+ sub(snapshot);
821
+ } catch {
822
+ }
823
+ }
824
+ }
825
+ register(config) {
826
+ const existing = this.apps.get(config.id);
827
+ if (existing) return existing;
828
+ const instance = new ApplicationInstance({
829
+ config,
830
+ windowManager: this.windowManager,
831
+ workspaceManager: this.workspaceManager,
832
+ hooks: this.hooks,
833
+ ipc: this.ipc,
834
+ allocatePid: () => {
835
+ this.pidCounter += Math.floor(Math.random() * 7) + 1;
836
+ return this.pidCounter;
837
+ }
838
+ });
839
+ instance.subscribe(() => {
840
+ this.notify();
841
+ });
842
+ this.apps.set(config.id, instance);
843
+ void config.onReady?.(instance);
844
+ void this.hooks.callHook("app:defined", { app: instance });
845
+ this.notify();
846
+ return instance;
847
+ }
848
+ get(id) {
849
+ return this.apps.get(id);
850
+ }
851
+ getByPid(pid) {
852
+ return Array.from(this.apps.values()).find((app) => app.pid === pid);
853
+ }
854
+ get runningApps() {
855
+ return Array.from(this.apps.values()).filter((app) => app.isRunning);
856
+ }
857
+ async launch(id, entryKey, argument) {
858
+ const app = this.apps.get(id);
859
+ if (!app) return false;
860
+ return app.launch(entryKey, argument);
861
+ }
862
+ async terminate(idOrPid, signal = "SIGTERM") {
863
+ const app = typeof idOrPid === "number" ? this.getByPid(idOrPid) : this.get(idOrPid);
864
+ if (!app) return false;
865
+ return app.terminate(signal);
866
+ }
867
+ }
868
+
869
+ export { ApplicationInstance as A, WindowInstance as W, ApplicationManager as a, WindowManager as b, WorkspaceManager as c };