@owdproject/core 3.3.1 → 4.0.0-alpha.3

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 (93) hide show
  1. package/README.md +91 -59
  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 +117 -0
  18. package/dist/managers/index.d.ts +117 -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.B0B0KfVK.mjs +865 -0
  24. package/dist/shared/core.B1XxB289.mjs +239 -0
  25. package/dist/shared/core.B3mp7ztE.mjs +237 -0
  26. package/dist/shared/core.BOvFdEVQ.d.ts +114 -0
  27. package/dist/shared/core.CsDFgGnX.mjs +40 -0
  28. package/dist/shared/core.Di1eFcr_.d.mts +114 -0
  29. package/dist/shared/core.Pd_Ogt4g.mjs +101 -0
  30. package/dist/shared/core.b3Xldzyu.mjs +29 -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 +60 -87
  35. package/bin/cli.js +0 -395
  36. package/bin/desktop.js +0 -5
  37. package/bin/lib/catalog.js +0 -293
  38. package/bin/lib/config.js +0 -109
  39. package/bin/lib/desktopConfig.js +0 -56
  40. package/bin/lib/generateTemplate.js +0 -389
  41. package/bin/lib/install.js +0 -408
  42. package/bin/lib/migrateModulePlayground.config.mjs +0 -164
  43. package/bin/lib/migrateModulePlayground.mjs +0 -313
  44. package/bin/lib/npmVersions.js +0 -124
  45. package/bin/lib/playgroundContext.js +0 -188
  46. package/bin/lib/scaffold.js +0 -197
  47. package/bin/lib/status.js +0 -289
  48. package/bin/lib/validateModule.js +0 -697
  49. package/bin/lib/workspace.js +0 -182
  50. package/bin/owd.js +0 -5
  51. package/bin/tui.js +0 -1379
  52. package/dist/module.d.mts +0 -13
  53. package/dist/module.json +0 -9
  54. package/dist/module.mjs +0 -268
  55. package/dist/types.d.mts +0 -7
  56. package/index.ts +0 -5
  57. package/module.ts +0 -223
  58. package/runtime/components/Desktop/Application/DesktopApplicationRender.vue +0 -20
  59. package/runtime/components/Desktop/Application/DesktopApplicationWindowsRender.vue +0 -38
  60. package/runtime/components/Desktop/Background/DesktopBackground.vue +0 -16
  61. package/runtime/components/Desktop/Core/DesktopCore.vue +0 -90
  62. package/runtime/components/Desktop/Time/DesktopTime.vue +0 -56
  63. package/runtime/components/Desktop/Window/DesktopWindow.vue +0 -204
  64. package/runtime/components/Desktop/Window/DesktopWindowContent.vue +0 -28
  65. package/runtime/components/Desktop/Window/DesktopWindowNav.vue +0 -66
  66. package/runtime/composables/useApplicationEntries.ts +0 -51
  67. package/runtime/composables/useApplicationManager.ts +0 -285
  68. package/runtime/composables/useDesktopManager.ts +0 -49
  69. package/runtime/composables/useTerminalManager.ts +0 -66
  70. package/runtime/composables/useWorkspaceManager.ts +0 -41
  71. package/runtime/internal/controllers/ApplicationController.ts +0 -345
  72. package/runtime/internal/controllers/WindowController.ts +0 -462
  73. package/runtime/plugins/02.desktop-register-desktop-apps.client.ts +0 -17
  74. package/runtime/plugins/resize.client.ts +0 -6
  75. package/runtime/stores/storeApplicationMeta.ts +0 -59
  76. package/runtime/stores/storeApplicationWindows.ts +0 -20
  77. package/runtime/stores/storeDesktop.ts +0 -111
  78. package/runtime/stores/storeDesktopDefaultApps.ts +0 -53
  79. package/runtime/stores/storeDesktopVolume.ts +0 -18
  80. package/runtime/stores/storeDesktopWindow.ts +0 -20
  81. package/runtime/stores/storeDesktopWorkspace.ts +0 -75
  82. package/runtime/utils/captureElementToCanvas.ts +0 -21
  83. package/runtime/utils/explorerEntryPath.ts +0 -10
  84. package/runtime/utils/resolveDesktopConfigPath.ts +0 -52
  85. package/runtime/utils/splitDesktopUserConfig.ts +0 -85
  86. package/runtime/utils/utilApp.ts +0 -37
  87. package/runtime/utils/utilCommon.ts +0 -26
  88. package/runtime/utils/utilDebug.ts +0 -30
  89. package/runtime/utils/utilDesktop.ts +0 -35
  90. package/runtime/utils/utilTerminal.ts +0 -6
  91. package/runtime/utils/utilWindow.ts +0 -15
  92. package/runtime/utils/validateDesktopUserConfig.ts +0 -44
  93. package/types/index.d.ts +0 -420
@@ -0,0 +1,865 @@
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
+ async launch(entryKey, argument) {
631
+ const entries = this.config.entries ?? {};
632
+ const resolvedKey = entryKey ?? Object.keys(entries)[0];
633
+ const entry = resolvedKey ? entries[resolvedKey] : void 0;
634
+ const launchCtx = { app: this, entryKey: resolvedKey, argument, cancel: false };
635
+ await this.hooks.callHook("app:before-launch", launchCtx);
636
+ if (launchCtx.cancel) return false;
637
+ this.ensureProcessStarted();
638
+ this.actor.send({ type: "LAUNCH" });
639
+ if (entry) {
640
+ if (typeof entry.command === "function") {
641
+ await entry.command(this, argument);
642
+ } else if (typeof entry.command === "string" && this.config.commands) {
643
+ const parts = entry.command.trim().split(/\s+/);
644
+ const cmdName = parts[0];
645
+ const fn = cmdName ? this.config.commands[cmdName] : void 0;
646
+ if (fn) {
647
+ await fn(this, argument ?? parts.slice(1).join(" "));
648
+ }
649
+ }
650
+ } else if (this.config.windows) {
651
+ const firstModel = Object.keys(this.config.windows)[0];
652
+ if (firstModel) {
653
+ await this.openWindow(firstModel);
654
+ }
655
+ }
656
+ await this.config.onLaunch?.(this);
657
+ this.refreshSnapshot();
658
+ await this.hooks.callHook("app:launched", { app: this, pid: this._pid });
659
+ this.ipc.emit("app:launched", { id: this.id, pid: this._pid }, { source: this.id });
660
+ return true;
661
+ }
662
+ async openWindow(model, meta, initialState) {
663
+ const windowConfig = this.config.windows?.[model] ?? {
664
+ title: this.config.title,
665
+ icon: this.config.icon,
666
+ category: this.config.category,
667
+ size: { width: 640, height: 480 }
668
+ };
669
+ if (this.config.singleton && this._windowIds.size > 0) {
670
+ const existingId = Array.from(this._windowIds)[0];
671
+ const existingWin = this.windowManager.get(existingId);
672
+ if (existingWin) {
673
+ existingWin.unminimize();
674
+ this.windowManager.focusWindow(existingWin.id);
675
+ return existingWin;
676
+ }
677
+ }
678
+ const openCtx = {
679
+ app: this,
680
+ model,
681
+ config: windowConfig,
682
+ meta,
683
+ cancel: false
684
+ };
685
+ await this.hooks.callHook("window:before-open", openCtx);
686
+ if (openCtx.cancel) return void 0;
687
+ this.ensureProcessStarted();
688
+ const winId = `${this.id}-${model}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
689
+ const workspace = initialState?.workspace ?? this.workspaceManager?.activeWorkspace ?? "workspace-1";
690
+ const instance = this.windowManager.createWindow({
691
+ id: winId,
692
+ model,
693
+ applicationId: this.id,
694
+ config: {
695
+ ...windowConfig,
696
+ title: windowConfig.title ?? this.config.title,
697
+ icon: windowConfig.icon ?? this.config.icon,
698
+ category: windowConfig.category ?? this.config.category
699
+ },
700
+ initialState,
701
+ meta,
702
+ workspace
703
+ });
704
+ this._windowIds.add(instance.id);
705
+ this.actor.send({ type: "OPEN_WINDOW" });
706
+ this.refreshSnapshot();
707
+ await this.hooks.callHook("window:opened", { window: instance, app: this });
708
+ return instance;
709
+ }
710
+ async closeWindow(windowId) {
711
+ const win = this.windowManager.get(windowId);
712
+ if (!win) return false;
713
+ const closeCtx = { window: win, app: this, cancel: false };
714
+ await this.hooks.callHook("window:before-close", closeCtx);
715
+ if (closeCtx.cancel) return false;
716
+ this._windowIds.delete(windowId);
717
+ this.windowManager.closeWindow(windowId);
718
+ this.actor.send({ type: "CLOSE_WINDOW" });
719
+ this.refreshSnapshot();
720
+ await this.hooks.callHook("window:closed", { windowId, app: this });
721
+ return true;
722
+ }
723
+ closeAllWindows() {
724
+ for (const id of Array.from(this._windowIds)) {
725
+ this.windowManager.closeWindow(id);
726
+ }
727
+ this._windowIds.clear();
728
+ this.refreshSnapshot();
729
+ }
730
+ async suspend() {
731
+ if (!this.isRunning || this.isSuspended) return false;
732
+ const ctx = { app: this, signal: "SIGSTOP", cancel: false };
733
+ await this.hooks.callHook("app:before-suspend", ctx);
734
+ if (ctx.cancel) return false;
735
+ for (const id of this._windowIds) {
736
+ this.windowManager.get(id)?.minimize();
737
+ }
738
+ this.actor.send({ type: "SIGSTOP" });
739
+ this.refreshSnapshot();
740
+ await this.hooks.callHook("app:suspended", { app: this });
741
+ this.ipc.emit("process:suspended", { id: this.id, pid: this._pid }, { source: "kernel" });
742
+ return true;
743
+ }
744
+ async resume() {
745
+ if (!this.isSuspended) return false;
746
+ const ctx = { app: this, signal: "SIGCONT", cancel: false };
747
+ await this.hooks.callHook("app:before-resume", ctx);
748
+ if (ctx.cancel) return false;
749
+ for (const id of this._windowIds) {
750
+ const win = this.windowManager.get(id);
751
+ if (win) {
752
+ win.unminimize();
753
+ this.windowManager.focusWindow(win.id);
754
+ }
755
+ }
756
+ this.actor.send({ type: "SIGCONT" });
757
+ this.refreshSnapshot();
758
+ await this.hooks.callHook("app:resumed", { app: this });
759
+ this.ipc.emit("process:resumed", { id: this.id, pid: this._pid }, { source: "kernel" });
760
+ return true;
761
+ }
762
+ async terminate(signal = "SIGTERM") {
763
+ const ctx = { app: this, signal, cancel: false };
764
+ await this.hooks.callHook("app:before-terminate", ctx);
765
+ if (ctx.cancel && signal !== "SIGKILL") return false;
766
+ const oldPid = this._pid;
767
+ this.closeAllWindows();
768
+ await this.config.onClose?.(this);
769
+ this._pid = null;
770
+ this._startedAt = null;
771
+ this.actor.send({ type: signal });
772
+ this.refreshSnapshot();
773
+ await this.hooks.callHook("app:terminated", {
774
+ id: this.id,
775
+ title: this.config.title,
776
+ pid: oldPid
777
+ });
778
+ this.ipc.emit(
779
+ "process:terminated",
780
+ { id: this.id, pid: oldPid, signal },
781
+ { source: "kernel", severity: signal === "SIGKILL" ? "warn" : "info" }
782
+ );
783
+ return true;
784
+ }
785
+ }
786
+ class ApplicationManager {
787
+ apps = /* @__PURE__ */ new Map();
788
+ snapshotCache = Object.freeze([]);
789
+ pidCounter = 1e3;
790
+ subscribers = /* @__PURE__ */ new Set();
791
+ windowManager;
792
+ workspaceManager;
793
+ hooks;
794
+ ipc;
795
+ constructor(options) {
796
+ this.windowManager = options.windowManager;
797
+ this.workspaceManager = options.workspaceManager;
798
+ this.hooks = options.hooks ?? globalDesktopHooks;
799
+ this.ipc = options.ipc ?? globalIPCBus;
800
+ }
801
+ getSnapshot() {
802
+ return this.snapshotCache;
803
+ }
804
+ subscribe(callback) {
805
+ this.subscribers.add(callback);
806
+ callback(this.getSnapshot());
807
+ return () => {
808
+ this.subscribers.delete(callback);
809
+ };
810
+ }
811
+ notify() {
812
+ this.snapshotCache = Object.freeze(Array.from(this.apps.values()));
813
+ const snapshot = this.getSnapshot();
814
+ for (const sub of this.subscribers) {
815
+ try {
816
+ sub(snapshot);
817
+ } catch {
818
+ }
819
+ }
820
+ }
821
+ register(config) {
822
+ const existing = this.apps.get(config.id);
823
+ if (existing) return existing;
824
+ const instance = new ApplicationInstance({
825
+ config,
826
+ windowManager: this.windowManager,
827
+ workspaceManager: this.workspaceManager,
828
+ hooks: this.hooks,
829
+ ipc: this.ipc,
830
+ allocatePid: () => {
831
+ this.pidCounter += Math.floor(Math.random() * 7) + 1;
832
+ return this.pidCounter;
833
+ }
834
+ });
835
+ instance.subscribe(() => {
836
+ this.notify();
837
+ });
838
+ this.apps.set(config.id, instance);
839
+ void config.onReady?.(instance);
840
+ void this.hooks.callHook("app:defined", { app: instance });
841
+ this.notify();
842
+ return instance;
843
+ }
844
+ get(id) {
845
+ return this.apps.get(id);
846
+ }
847
+ getByPid(pid) {
848
+ return Array.from(this.apps.values()).find((app) => app.pid === pid);
849
+ }
850
+ get runningApps() {
851
+ return Array.from(this.apps.values()).filter((app) => app.isRunning);
852
+ }
853
+ async launch(id, entryKey, argument) {
854
+ const app = this.apps.get(id);
855
+ if (!app) return false;
856
+ return app.launch(entryKey, argument);
857
+ }
858
+ async terminate(idOrPid, signal = "SIGTERM") {
859
+ const app = typeof idOrPid === "number" ? this.getByPid(idOrPid) : this.get(idOrPid);
860
+ if (!app) return false;
861
+ return app.terminate(signal);
862
+ }
863
+ }
864
+
865
+ export { ApplicationInstance as A, WindowInstance as W, ApplicationManager as a, WindowManager as b, WorkspaceManager as c };