@atom-js-org/runtime 0.4.5-alpha.0 → 0.5.0-alpha.0
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/README.md +15 -7
- package/index.d.ts +28 -0
- package/package.json +1 -1
- package/src/browser-window.cjs +147 -3
- package/src/runtime/macos-native-host.m +90 -4
- package/src/runtime/window-host.mjs +385 -14
- package/src/state.cjs +1 -0
package/README.md
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
# @atom-js-org/runtime
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The AtomJS runtime provides an Electron-style main-process API powered by operating-system WebViews.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```js
|
|
6
|
+
const { app, BrowserWindow } = require('electron');
|
|
7
|
+
|
|
8
|
+
app.whenReady().then(() => {
|
|
9
|
+
const main = new BrowserWindow({ width: 1000, height: 700 });
|
|
10
|
+
const auth = new BrowserWindow({ parent: main, modal: true, width: 520, height: 720 });
|
|
11
|
+
auth.loadURL('https://example.com/login');
|
|
12
|
+
});
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Supported window options include parent/modal relationships, frame and resize controls, always-on-top, opacity, transparency, min/max sizes, taskbar behavior, macOS title-bar styles and traffic-light positioning.
|
|
6
16
|
|
|
7
|
-
|
|
17
|
+
AtomJS does not install or execute Electron and does not bundle a private Chromium copy. Windows uses WebView2, macOS uses WKWebView, and Linux uses WebKitGTK.
|
|
18
|
+
|
|
19
|
+
Most applications should install the Electron-compatible alias:
|
|
8
20
|
|
|
9
21
|
```bash
|
|
10
22
|
npm install electron@npm:@atom-js-org/electron@alpha
|
|
11
23
|
```
|
|
12
24
|
|
|
13
|
-
```js
|
|
14
|
-
const { app, BrowserWindow, ipcMain } = require('electron');
|
|
15
|
-
```
|
|
16
|
-
|
|
17
25
|
Project: https://github.com/Atom-js-org/atom
|
package/index.d.ts
CHANGED
|
@@ -5,11 +5,30 @@ import { EventEmitter } from 'node:events';
|
|
|
5
5
|
export interface BrowserWindowConstructorOptions {
|
|
6
6
|
width?: number;
|
|
7
7
|
height?: number;
|
|
8
|
+
x?: number;
|
|
9
|
+
y?: number;
|
|
8
10
|
title?: string;
|
|
9
11
|
show?: boolean;
|
|
10
12
|
resizable?: boolean;
|
|
11
13
|
center?: boolean;
|
|
12
14
|
frame?: boolean;
|
|
15
|
+
parent?: BrowserWindow;
|
|
16
|
+
modal?: boolean;
|
|
17
|
+
alwaysOnTop?: boolean;
|
|
18
|
+
focusable?: boolean;
|
|
19
|
+
closable?: boolean;
|
|
20
|
+
minimizable?: boolean;
|
|
21
|
+
maximizable?: boolean;
|
|
22
|
+
fullscreenable?: boolean;
|
|
23
|
+
skipTaskbar?: boolean;
|
|
24
|
+
transparent?: boolean;
|
|
25
|
+
opacity?: number;
|
|
26
|
+
titleBarStyle?: 'default' | 'hidden' | 'hiddenInset' | 'customButtonsOnHover';
|
|
27
|
+
trafficLightPosition?: { x: number; y: number };
|
|
28
|
+
minWidth?: number;
|
|
29
|
+
minHeight?: number;
|
|
30
|
+
maxWidth?: number;
|
|
31
|
+
maxHeight?: number;
|
|
13
32
|
backgroundColor?: string;
|
|
14
33
|
webPreferences?: {
|
|
15
34
|
preload?: string;
|
|
@@ -48,6 +67,8 @@ export class BrowserWindow extends EventEmitter {
|
|
|
48
67
|
isDestroyed(): boolean;
|
|
49
68
|
setTitle(title: string): void;
|
|
50
69
|
getTitle(): string;
|
|
70
|
+
getParentWindow(): BrowserWindow | null;
|
|
71
|
+
getChildWindows(): BrowserWindow[];
|
|
51
72
|
setMenu(menu: Menu | null): this;
|
|
52
73
|
getMenu(): Menu | null;
|
|
53
74
|
removeMenu(): void;
|
|
@@ -55,6 +76,13 @@ export class BrowserWindow extends EventEmitter {
|
|
|
55
76
|
isMenuBarVisible(): boolean;
|
|
56
77
|
setAutoHideMenuBar(hide: boolean): void;
|
|
57
78
|
isFullScreen(): boolean;
|
|
79
|
+
setAlwaysOnTop(flag?: boolean): void;
|
|
80
|
+
isAlwaysOnTop(): boolean;
|
|
81
|
+
setOpacity(opacity: number): void;
|
|
82
|
+
getOpacity(): number;
|
|
83
|
+
setResizable(flag: boolean): void;
|
|
84
|
+
isResizable(): boolean;
|
|
85
|
+
isModal(): boolean;
|
|
58
86
|
setFullScreen(flag: boolean): void;
|
|
59
87
|
maximize(): void;
|
|
60
88
|
unmaximize(): void;
|
package/package.json
CHANGED
package/src/browser-window.cjs
CHANGED
|
@@ -15,6 +15,14 @@ class BrowserWindow extends EventEmitter {
|
|
|
15
15
|
constructor(options = {}) {
|
|
16
16
|
super();
|
|
17
17
|
this.id = state.nextWindowId++;
|
|
18
|
+
const explicitParent = options.parent instanceof BrowserWindow && !options.parent.isDestroyed()
|
|
19
|
+
? options.parent
|
|
20
|
+
: null;
|
|
21
|
+
const modalParent = !explicitParent && options.modal === true
|
|
22
|
+
? BrowserWindow.getFocusedWindow()
|
|
23
|
+
: null;
|
|
24
|
+
this._parent = explicitParent || modalParent || null;
|
|
25
|
+
this._children = new Set();
|
|
18
26
|
this.options = normalizeOptions(options);
|
|
19
27
|
this.webContents = new WebContents(this);
|
|
20
28
|
this._child = null;
|
|
@@ -32,12 +40,15 @@ class BrowserWindow extends EventEmitter {
|
|
|
32
40
|
this._fullScreen = false;
|
|
33
41
|
this._maximized = false;
|
|
34
42
|
this._minimized = false;
|
|
43
|
+
this._alwaysOnTop = this.options.alwaysOnTop;
|
|
44
|
+
this._opacity = this.options.opacity;
|
|
35
45
|
this._bounds = {
|
|
36
|
-
x: 0,
|
|
37
|
-
y: 0,
|
|
46
|
+
x: this.options.x == null ? 0 : this.options.x,
|
|
47
|
+
y: this.options.y == null ? 0 : this.options.y,
|
|
38
48
|
width: this.options.width,
|
|
39
49
|
height: this.options.height
|
|
40
50
|
};
|
|
51
|
+
if (this._parent) this._parent._children.add(this);
|
|
41
52
|
state.windows.set(this.id, this);
|
|
42
53
|
}
|
|
43
54
|
|
|
@@ -82,12 +93,33 @@ class BrowserWindow extends EventEmitter {
|
|
|
82
93
|
if (preloadPath) preloadCode = await fs.promises.readFile(path.resolve(preloadPath), 'utf8');
|
|
83
94
|
|
|
84
95
|
const config = {
|
|
96
|
+
windowId: this.id,
|
|
85
97
|
title: this.options.title || app.getName(),
|
|
86
98
|
width: this.options.width,
|
|
87
99
|
height: this.options.height,
|
|
100
|
+
x: this.options.x,
|
|
101
|
+
y: this.options.y,
|
|
88
102
|
resizable: this.options.resizable,
|
|
89
103
|
center: this.options.center,
|
|
90
104
|
frame: this.options.frame,
|
|
105
|
+
parentWindowId: this._parent ? this._parent.id : null,
|
|
106
|
+
parentProcessId: this._parent && this._parent._child ? this._parent._child.pid : null,
|
|
107
|
+
modal: this.options.modal,
|
|
108
|
+
alwaysOnTop: this.options.alwaysOnTop,
|
|
109
|
+
focusable: this.options.focusable,
|
|
110
|
+
closable: this.options.closable,
|
|
111
|
+
minimizable: this.options.minimizable,
|
|
112
|
+
maximizable: this.options.maximizable,
|
|
113
|
+
fullscreenable: this.options.fullscreenable,
|
|
114
|
+
skipTaskbar: this.options.skipTaskbar,
|
|
115
|
+
transparent: this.options.transparent,
|
|
116
|
+
opacity: this.options.opacity,
|
|
117
|
+
titleBarStyle: this.options.titleBarStyle,
|
|
118
|
+
trafficLightPosition: this.options.trafficLightPosition,
|
|
119
|
+
minWidth: this.options.minWidth,
|
|
120
|
+
minHeight: this.options.minHeight,
|
|
121
|
+
maxWidth: this.options.maxWidth,
|
|
122
|
+
maxHeight: this.options.maxHeight,
|
|
91
123
|
show: this.options.show,
|
|
92
124
|
backgroundColor: this.options.backgroundColor,
|
|
93
125
|
debug: Boolean(this.options.webPreferences.devTools || process.env.ATOM_DEV === '1'),
|
|
@@ -124,7 +156,8 @@ class BrowserWindow extends EventEmitter {
|
|
|
124
156
|
this._child = spawn(nodeExecutable, hostArgs, {
|
|
125
157
|
cwd: state.projectRoot,
|
|
126
158
|
env: process.env,
|
|
127
|
-
stdio: ['ignore', 'pipe', 'inherit']
|
|
159
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
160
|
+
windowsHide: true
|
|
128
161
|
});
|
|
129
162
|
this._hostAttached = true;
|
|
130
163
|
attachHostOutput(this, this._child);
|
|
@@ -198,10 +231,12 @@ class BrowserWindow extends EventEmitter {
|
|
|
198
231
|
return;
|
|
199
232
|
}
|
|
200
233
|
if (event.type === 'focus') {
|
|
234
|
+
state.focusedWindowId = this.id;
|
|
201
235
|
this.emit('focus');
|
|
202
236
|
return;
|
|
203
237
|
}
|
|
204
238
|
if (event.type === 'blur') {
|
|
239
|
+
if (state.focusedWindowId === this.id) state.focusedWindowId = null;
|
|
205
240
|
this.emit('blur');
|
|
206
241
|
return;
|
|
207
242
|
}
|
|
@@ -266,6 +301,10 @@ class BrowserWindow extends EventEmitter {
|
|
|
266
301
|
if (this._destroyed) return;
|
|
267
302
|
this._destroyed = true;
|
|
268
303
|
this._hostAttached = false;
|
|
304
|
+
if (state.focusedWindowId === this.id) state.focusedWindowId = null;
|
|
305
|
+
if (this._parent) this._parent._children.delete(this);
|
|
306
|
+
for (const child of this._children) child._parent = null;
|
|
307
|
+
this._children.clear();
|
|
269
308
|
state.windows.delete(this.id);
|
|
270
309
|
this.emit('closed', details);
|
|
271
310
|
if (state.windows.size === 0 && !state.isQuitting) app.emit('window-all-closed');
|
|
@@ -337,6 +376,14 @@ class BrowserWindow extends EventEmitter {
|
|
|
337
376
|
return this.options.title || app.getName();
|
|
338
377
|
}
|
|
339
378
|
|
|
379
|
+
getParentWindow() {
|
|
380
|
+
return this._parent && !this._parent.isDestroyed() ? this._parent : null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
getChildWindows() {
|
|
384
|
+
return [...this._children].filter((child) => !child.isDestroyed());
|
|
385
|
+
}
|
|
386
|
+
|
|
340
387
|
setMenu(menu) {
|
|
341
388
|
this._menu = menu || null;
|
|
342
389
|
return this;
|
|
@@ -368,6 +415,49 @@ class BrowserWindow extends EventEmitter {
|
|
|
368
415
|
return this._fullScreen;
|
|
369
416
|
}
|
|
370
417
|
|
|
418
|
+
setAlwaysOnTop(value = true) {
|
|
419
|
+
this._alwaysOnTop = Boolean(value);
|
|
420
|
+
this.options.alwaysOnTop = this._alwaysOnTop;
|
|
421
|
+
if (!this._sendHostCommand({ command: 'set-always-on-top', value: this._alwaysOnTop })) {
|
|
422
|
+
console.warn('[AtomJS] Changing always-on-top after creation is not supported by the current platform host. Set alwaysOnTop in BrowserWindow options.');
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
isAlwaysOnTop() {
|
|
427
|
+
return this._alwaysOnTop;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
setOpacity(value) {
|
|
431
|
+
const number = Number(value);
|
|
432
|
+
if (!Number.isFinite(number) || number < 0 || number > 1) {
|
|
433
|
+
throw new TypeError('BrowserWindow.setOpacity expects a number between 0 and 1.');
|
|
434
|
+
}
|
|
435
|
+
this._opacity = number;
|
|
436
|
+
this.options.opacity = number;
|
|
437
|
+
if (!this._sendHostCommand({ command: 'set-opacity', value: number })) {
|
|
438
|
+
console.warn('[AtomJS] Changing opacity after creation is not supported by the current platform host. Set opacity in BrowserWindow options.');
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
getOpacity() {
|
|
443
|
+
return this._opacity;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
setResizable(value) {
|
|
447
|
+
this.options.resizable = Boolean(value);
|
|
448
|
+
if (!this._sendHostCommand({ command: 'set-resizable', value: this.options.resizable })) {
|
|
449
|
+
console.warn('[AtomJS] Changing resizable after creation is not supported by the current platform host.');
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
isResizable() {
|
|
454
|
+
return this.options.resizable;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
isModal() {
|
|
458
|
+
return this.options.modal;
|
|
459
|
+
}
|
|
460
|
+
|
|
371
461
|
setFullScreen(value = true) {
|
|
372
462
|
this._fullScreen = Boolean(value);
|
|
373
463
|
if (!this._sendHostCommand({ command: 'fullscreen', value: this._fullScreen })) {
|
|
@@ -443,6 +533,10 @@ class BrowserWindow extends EventEmitter {
|
|
|
443
533
|
}
|
|
444
534
|
|
|
445
535
|
static getFocusedWindow() {
|
|
536
|
+
if (state.focusedWindowId != null) {
|
|
537
|
+
const focused = BrowserWindow.fromId(state.focusedWindowId);
|
|
538
|
+
if (focused && !focused.isDestroyed()) return focused;
|
|
539
|
+
}
|
|
446
540
|
const windows = BrowserWindow.getAllWindows();
|
|
447
541
|
return windows.length ? windows[windows.length - 1] : null;
|
|
448
542
|
}
|
|
@@ -483,9 +577,27 @@ function normalizeOptions(options) {
|
|
|
483
577
|
return {
|
|
484
578
|
width: finiteOr(options.width, 800),
|
|
485
579
|
height: finiteOr(options.height, 600),
|
|
580
|
+
x: finiteOrNull(options.x),
|
|
581
|
+
y: finiteOrNull(options.y),
|
|
486
582
|
title: options.title ? String(options.title) : '',
|
|
487
583
|
show: options.show !== false,
|
|
488
584
|
resizable: options.resizable !== false,
|
|
585
|
+
modal: options.modal === true,
|
|
586
|
+
alwaysOnTop: options.alwaysOnTop === true,
|
|
587
|
+
focusable: options.focusable !== false,
|
|
588
|
+
closable: options.closable !== false,
|
|
589
|
+
minimizable: options.minimizable !== false,
|
|
590
|
+
maximizable: options.maximizable !== false,
|
|
591
|
+
fullscreenable: options.fullscreenable !== false,
|
|
592
|
+
skipTaskbar: options.skipTaskbar === true,
|
|
593
|
+
transparent: options.transparent === true,
|
|
594
|
+
opacity: clampOpacity(options.opacity),
|
|
595
|
+
titleBarStyle: normalizeTitleBarStyle(options.titleBarStyle),
|
|
596
|
+
trafficLightPosition: normalizePoint(options.trafficLightPosition),
|
|
597
|
+
minWidth: finiteOrZero(options.minWidth),
|
|
598
|
+
minHeight: finiteOrZero(options.minHeight),
|
|
599
|
+
maxWidth: finiteOrZero(options.maxWidth),
|
|
600
|
+
maxHeight: finiteOrZero(options.maxHeight),
|
|
489
601
|
center: options.center !== false,
|
|
490
602
|
frame: options.frame !== false,
|
|
491
603
|
backgroundColor: options.backgroundColor || '#ffffff',
|
|
@@ -500,6 +612,38 @@ function normalizeOptions(options) {
|
|
|
500
612
|
};
|
|
501
613
|
}
|
|
502
614
|
|
|
615
|
+
function clampOpacity(value) {
|
|
616
|
+
if (value == null) return 1;
|
|
617
|
+
const number = Number(value);
|
|
618
|
+
if (!Number.isFinite(number)) return 1;
|
|
619
|
+
return Math.min(1, Math.max(0, number));
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function finiteOrZero(value) {
|
|
623
|
+
const number = Number(value);
|
|
624
|
+
return Number.isFinite(number) && number > 0 ? Math.round(number) : 0;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function finiteOrNull(value) {
|
|
628
|
+
if (value == null) return null;
|
|
629
|
+
const number = Number(value);
|
|
630
|
+
return Number.isFinite(number) ? Math.round(number) : null;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function normalizePoint(value) {
|
|
634
|
+
if (!value || typeof value !== 'object') return null;
|
|
635
|
+
const x = Number(value.x);
|
|
636
|
+
const y = Number(value.y);
|
|
637
|
+
return Number.isFinite(x) && Number.isFinite(y) ? { x, y } : null;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function normalizeTitleBarStyle(value) {
|
|
641
|
+
const normalized = String(value || 'default');
|
|
642
|
+
return ['default', 'hidden', 'hiddenInset', 'customButtonsOnHover'].includes(normalized)
|
|
643
|
+
? normalized
|
|
644
|
+
: 'default';
|
|
645
|
+
}
|
|
646
|
+
|
|
503
647
|
function finiteOr(value, fallback) {
|
|
504
648
|
const number = Number(value);
|
|
505
649
|
return Number.isFinite(number) && number > 0 ? Math.round(number) : fallback;
|
|
@@ -114,6 +114,8 @@ static void AtomJSConfigureApplicationIdentity(NSApplication *application) {
|
|
|
114
114
|
@property(nonatomic, strong) NSNumber *windowId;
|
|
115
115
|
@property(nonatomic, strong) NSWindow *window;
|
|
116
116
|
@property(nonatomic, strong) WKWebView *webView;
|
|
117
|
+
@property(nonatomic, weak) AtomJSWindowController *parentController;
|
|
118
|
+
@property(nonatomic, assign) BOOL modal;
|
|
117
119
|
- (instancetype)initWithWindowId:(NSNumber *)windowId config:(NSDictionary *)config;
|
|
118
120
|
- (void)navigate:(NSString *)urlString;
|
|
119
121
|
@end
|
|
@@ -130,10 +132,20 @@ static void AtomJSConfigureApplicationIdentity(NSApplication *application) {
|
|
|
130
132
|
CGFloat height = MAX(240.0, [AtomJSNumber(config[@"height"], @600) doubleValue]);
|
|
131
133
|
NSRect frame = NSMakeRect(0, 0, width, height);
|
|
132
134
|
|
|
133
|
-
NSWindowStyleMask style =
|
|
135
|
+
NSWindowStyleMask style = 0;
|
|
136
|
+
if (AtomJSBoolean(config[@"closable"], YES)) style |= NSWindowStyleMaskClosable;
|
|
137
|
+
if (AtomJSBoolean(config[@"minimizable"], YES)) style |= NSWindowStyleMaskMiniaturizable;
|
|
134
138
|
if (AtomJSBoolean(config[@"frame"], YES)) style |= NSWindowStyleMaskTitled;
|
|
135
139
|
if (AtomJSBoolean(config[@"resizable"], YES)) style |= NSWindowStyleMaskResizable;
|
|
136
140
|
|
|
141
|
+
NSString *titleBarStyle = AtomJSString(config[@"titleBarStyle"], @"default");
|
|
142
|
+
BOOL usesHiddenTitleBar = [titleBarStyle isEqualToString:@"hidden"]
|
|
143
|
+
|| [titleBarStyle isEqualToString:@"hiddenInset"]
|
|
144
|
+
|| [titleBarStyle isEqualToString:@"customButtonsOnHover"];
|
|
145
|
+
if (usesHiddenTitleBar) {
|
|
146
|
+
style |= NSWindowStyleMaskFullSizeContentView;
|
|
147
|
+
}
|
|
148
|
+
|
|
137
149
|
_window = [[NSWindow alloc]
|
|
138
150
|
initWithContentRect:frame
|
|
139
151
|
styleMask:style
|
|
@@ -144,8 +156,41 @@ static void AtomJSConfigureApplicationIdentity(NSApplication *application) {
|
|
|
144
156
|
_window.title = AtomJSString(config[@"title"], atomAppName);
|
|
145
157
|
_window.backgroundColor = AtomJSColor(config[@"backgroundColor"]);
|
|
146
158
|
_window.tabbingMode = NSWindowTabbingModeDisallowed;
|
|
159
|
+
_window.alphaValue = MIN(1.0, MAX(0.0, [AtomJSNumber(config[@"opacity"], @1) doubleValue]));
|
|
160
|
+
_window.opaque = !AtomJSBoolean(config[@"transparent"], NO);
|
|
161
|
+
if (!_window.opaque) {
|
|
162
|
+
_window.backgroundColor = [NSColor clearColor];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (usesHiddenTitleBar) {
|
|
166
|
+
_window.titleVisibility = NSWindowTitleHidden;
|
|
167
|
+
_window.titlebarAppearsTransparent = YES;
|
|
168
|
+
}
|
|
147
169
|
|
|
148
|
-
if (AtomJSBoolean(config[@"
|
|
170
|
+
if (!AtomJSBoolean(config[@"fullscreenable"], YES)) {
|
|
171
|
+
_window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenNone;
|
|
172
|
+
}
|
|
173
|
+
[_window standardWindowButton:NSWindowZoomButton].enabled = AtomJSBoolean(config[@"maximizable"], YES);
|
|
174
|
+
if (AtomJSBoolean(config[@"alwaysOnTop"], NO)) {
|
|
175
|
+
_window.level = NSFloatingWindowLevel;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
CGFloat minWidth = [AtomJSNumber(config[@"minWidth"], @0) doubleValue];
|
|
179
|
+
CGFloat minHeight = [AtomJSNumber(config[@"minHeight"], @0) doubleValue];
|
|
180
|
+
CGFloat maxWidth = [AtomJSNumber(config[@"maxWidth"], @0) doubleValue];
|
|
181
|
+
CGFloat maxHeight = [AtomJSNumber(config[@"maxHeight"], @0) doubleValue];
|
|
182
|
+
if (minWidth > 0 || minHeight > 0) {
|
|
183
|
+
_window.contentMinSize = NSMakeSize(MAX(1.0, minWidth), MAX(1.0, minHeight));
|
|
184
|
+
}
|
|
185
|
+
if (maxWidth > 0 || maxHeight > 0) {
|
|
186
|
+
_window.contentMaxSize = NSMakeSize(maxWidth > 0 ? maxWidth : CGFLOAT_MAX, maxHeight > 0 ? maxHeight : CGFLOAT_MAX);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if ([config[@"x"] isKindOfClass:[NSNumber class]] && [config[@"y"] isKindOfClass:[NSNumber class]]) {
|
|
190
|
+
[_window setFrameOrigin:NSMakePoint([config[@"x"] doubleValue], [config[@"y"] doubleValue])];
|
|
191
|
+
} else if (AtomJSBoolean(config[@"center"], YES)) {
|
|
192
|
+
[_window center];
|
|
193
|
+
}
|
|
149
194
|
|
|
150
195
|
WKUserContentController *contentController = [[WKUserContentController alloc] init];
|
|
151
196
|
NSString *bridgeScript = AtomJSString(config[@"bridgeScript"], @"");
|
|
@@ -163,14 +208,40 @@ static void AtomJSConfigureApplicationIdentity(NSApplication *application) {
|
|
|
163
208
|
_webView = [[WKWebView alloc] initWithFrame:frame configuration:webConfiguration];
|
|
164
209
|
_webView.navigationDelegate = self;
|
|
165
210
|
_webView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
211
|
+
if (AtomJSBoolean(config[@"transparent"], NO)) _webView.drawsBackground = NO;
|
|
166
212
|
_window.contentView = _webView;
|
|
167
213
|
|
|
214
|
+
NSNumber *parentWindowId = AtomJSNumber(config[@"parentWindowId"], nil);
|
|
215
|
+
_parentController = parentWindowId ? atomWindows[parentWindowId] : nil;
|
|
216
|
+
_modal = AtomJSBoolean(config[@"modal"], NO) && _parentController != nil;
|
|
217
|
+
|
|
218
|
+
NSDictionary *trafficLightPosition = [config[@"trafficLightPosition"] isKindOfClass:[NSDictionary class]]
|
|
219
|
+
? config[@"trafficLightPosition"]
|
|
220
|
+
: nil;
|
|
221
|
+
if (trafficLightPosition) {
|
|
222
|
+
CGFloat x = [AtomJSNumber(trafficLightPosition[@"x"], @0) doubleValue];
|
|
223
|
+
CGFloat y = [AtomJSNumber(trafficLightPosition[@"y"], @0) doubleValue];
|
|
224
|
+
NSButton *closeButton = [_window standardWindowButton:NSWindowCloseButton];
|
|
225
|
+
NSView *container = closeButton.superview;
|
|
226
|
+
if (container) {
|
|
227
|
+
NSPoint origin = container.frame.origin;
|
|
228
|
+
origin.x = x;
|
|
229
|
+
origin.y = MAX(0.0, _window.frame.size.height - container.frame.size.height - y);
|
|
230
|
+
[container setFrameOrigin:origin];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
168
234
|
[self navigate:AtomJSString(config[@"url"], @"about:blank")];
|
|
169
235
|
|
|
170
236
|
if (AtomJSBoolean(config[@"show"], YES)) {
|
|
171
237
|
[NSApp unhide:nil];
|
|
172
|
-
|
|
173
|
-
|
|
238
|
+
if (_modal) {
|
|
239
|
+
[_parentController.window beginSheet:_window completionHandler:nil];
|
|
240
|
+
} else {
|
|
241
|
+
if (_parentController) [_parentController.window addChildWindow:_window ordered:NSWindowAbove];
|
|
242
|
+
[_window makeKeyAndOrderFront:nil];
|
|
243
|
+
[_window orderFrontRegardless];
|
|
244
|
+
}
|
|
174
245
|
[NSApp activateIgnoringOtherApps:YES];
|
|
175
246
|
}
|
|
176
247
|
|
|
@@ -226,6 +297,13 @@ static void AtomJSConfigureApplicationIdentity(NSApplication *application) {
|
|
|
226
297
|
}
|
|
227
298
|
|
|
228
299
|
- (void)windowWillClose:(NSNotification *)notification {
|
|
300
|
+
if (self.parentController) {
|
|
301
|
+
if (self.modal && self.window.sheetParent) {
|
|
302
|
+
[self.window.sheetParent endSheet:self.window];
|
|
303
|
+
} else {
|
|
304
|
+
[self.parentController.window removeChildWindow:self.window];
|
|
305
|
+
}
|
|
306
|
+
}
|
|
229
307
|
[atomWindows removeObjectForKey:self.windowId];
|
|
230
308
|
AtomJSEmit(@{
|
|
231
309
|
@"type": @"closed",
|
|
@@ -486,6 +564,14 @@ static void AtomJSHandleMessage(NSDictionary *message) {
|
|
|
486
564
|
if ([bounds[@"width"] isKindOfClass:[NSNumber class]]) frame.size.width = MAX(1.0, [bounds[@"width"] doubleValue]);
|
|
487
565
|
if ([bounds[@"height"] isKindOfClass:[NSNumber class]]) frame.size.height = MAX(1.0, [bounds[@"height"] doubleValue]);
|
|
488
566
|
[controller.window setFrame:frame display:YES animate:NO];
|
|
567
|
+
} else if ([command isEqualToString:@"set-always-on-top"]) {
|
|
568
|
+
controller.window.level = AtomJSBoolean(message[@"value"], NO) ? NSFloatingWindowLevel : NSNormalWindowLevel;
|
|
569
|
+
} else if ([command isEqualToString:@"set-opacity"]) {
|
|
570
|
+
controller.window.alphaValue = MIN(1.0, MAX(0.0, [AtomJSNumber(message[@"value"], @1) doubleValue]));
|
|
571
|
+
} else if ([command isEqualToString:@"set-resizable"]) {
|
|
572
|
+
BOOL requested = AtomJSBoolean(message[@"value"], YES);
|
|
573
|
+
if (requested) controller.window.styleMask |= NSWindowStyleMaskResizable;
|
|
574
|
+
else controller.window.styleMask &= ~NSWindowStyleMaskResizable;
|
|
489
575
|
} else {
|
|
490
576
|
AtomJSRespond(requestId, NO, nil, [NSString stringWithFormat:@"Unsupported AtomJS native command: %@", command]);
|
|
491
577
|
return;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
2
3
|
|
|
4
|
+
const EVENT_PREFIX = '__ATOMJS_EVENT__';
|
|
3
5
|
const configPath = process.argv[2];
|
|
4
6
|
if (!configPath) {
|
|
5
7
|
console.error('AtomJS window host expected a configuration file');
|
|
@@ -33,18 +35,387 @@ try {
|
|
|
33
35
|
process.exit(1);
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
38
|
+
function emit(event) {
|
|
39
|
+
process.stdout.write(`${EVENT_PREFIX}${JSON.stringify(event)}\n`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function configureWindowsWindow(options) {
|
|
43
|
+
const parentProcessId = Number(options.parentProcessId || 0);
|
|
44
|
+
const modal = Boolean(options.modal && parentProcessId > 0);
|
|
45
|
+
const env = {
|
|
46
|
+
...process.env,
|
|
47
|
+
ATOMJS_CHILD_PID: String(process.pid),
|
|
48
|
+
ATOMJS_PARENT_PID: String(parentProcessId || 0),
|
|
49
|
+
ATOMJS_MODAL: modal ? '1' : '0',
|
|
50
|
+
ATOMJS_FRAME: options.frame === false ? '0' : '1',
|
|
51
|
+
ATOMJS_RESIZABLE: options.resizable === false ? '0' : '1',
|
|
52
|
+
ATOMJS_MINIMIZABLE: options.minimizable === false ? '0' : '1',
|
|
53
|
+
ATOMJS_MAXIMIZABLE: options.maximizable === false ? '0' : '1',
|
|
54
|
+
ATOMJS_CLOSABLE: options.closable === false ? '0' : '1',
|
|
55
|
+
ATOMJS_ALWAYS_ON_TOP: options.alwaysOnTop ? '1' : '0',
|
|
56
|
+
ATOMJS_FOCUSABLE: options.focusable === false ? '0' : '1',
|
|
57
|
+
ATOMJS_SKIP_TASKBAR: options.skipTaskbar ? '1' : '0',
|
|
58
|
+
ATOMJS_TRANSPARENT: options.transparent ? '1' : '0',
|
|
59
|
+
ATOMJS_OPACITY: String(normalizeOpacity(options.opacity)),
|
|
60
|
+
ATOMJS_CENTER: options.center === false ? '0' : '1',
|
|
61
|
+
ATOMJS_SHOW: options.show === false ? '0' : '1',
|
|
62
|
+
ATOMJS_X: Number.isFinite(Number(options.x)) ? String(Math.round(Number(options.x))) : '',
|
|
63
|
+
ATOMJS_Y: Number.isFinite(Number(options.y)) ? String(Math.round(Number(options.y))) : ''
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const result = spawnSync('powershell.exe', [
|
|
67
|
+
'-NoLogo',
|
|
68
|
+
'-NoProfile',
|
|
69
|
+
'-NonInteractive',
|
|
70
|
+
'-ExecutionPolicy', 'Bypass',
|
|
71
|
+
'-Command', WINDOWS_WINDOW_SETUP
|
|
72
|
+
], {
|
|
73
|
+
env,
|
|
74
|
+
encoding: 'utf8',
|
|
75
|
+
windowsHide: true,
|
|
76
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (result.error) {
|
|
80
|
+
console.warn(`[AtomJS] Could not configure the native Windows window: ${result.error.message}`);
|
|
81
|
+
} else if (result.status !== 0) {
|
|
82
|
+
const detail = String(result.stderr || result.stdout || '').trim();
|
|
83
|
+
if (detail) console.warn(`[AtomJS] Could not configure every native Windows option: ${detail}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return modal ? { parentProcessId } : null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function releaseWindowsModal(ownership) {
|
|
90
|
+
if (!ownership || !ownership.parentProcessId) return;
|
|
91
|
+
const result = spawnSync('powershell.exe', [
|
|
92
|
+
'-NoLogo',
|
|
93
|
+
'-NoProfile',
|
|
94
|
+
'-NonInteractive',
|
|
95
|
+
'-ExecutionPolicy', 'Bypass',
|
|
96
|
+
'-Command', WINDOWS_MODAL_RELEASE
|
|
97
|
+
], {
|
|
98
|
+
env: {
|
|
99
|
+
...process.env,
|
|
100
|
+
ATOMJS_PARENT_PID: String(ownership.parentProcessId)
|
|
101
|
+
},
|
|
102
|
+
encoding: 'utf8',
|
|
103
|
+
windowsHide: true,
|
|
104
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
105
|
+
});
|
|
106
|
+
if (result.error) {
|
|
107
|
+
console.warn(`[AtomJS] Could not restore the parent Windows window: ${result.error.message}`);
|
|
108
|
+
}
|
|
50
109
|
}
|
|
110
|
+
|
|
111
|
+
function normalizeOpacity(value) {
|
|
112
|
+
const number = Number(value);
|
|
113
|
+
if (!Number.isFinite(number)) return 1;
|
|
114
|
+
return Math.min(1, Math.max(0, number));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const WINDOWS_INTEROP = String.raw`
|
|
118
|
+
Add-Type -TypeDefinition @'
|
|
119
|
+
using System;
|
|
120
|
+
using System.Runtime.InteropServices;
|
|
121
|
+
|
|
122
|
+
public static class AtomJSNativeWindow {
|
|
123
|
+
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
|
|
124
|
+
|
|
125
|
+
[DllImport("user32.dll")]
|
|
126
|
+
public static extern bool EnumWindows(EnumWindowsProc callback, IntPtr lParam);
|
|
127
|
+
|
|
128
|
+
[DllImport("user32.dll")]
|
|
129
|
+
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
|
|
130
|
+
|
|
131
|
+
[DllImport("user32.dll")]
|
|
132
|
+
public static extern IntPtr GetParent(IntPtr hWnd);
|
|
133
|
+
|
|
134
|
+
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
|
|
135
|
+
private static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int index);
|
|
136
|
+
|
|
137
|
+
[DllImport("user32.dll", EntryPoint = "GetWindowLong")]
|
|
138
|
+
private static extern IntPtr GetWindowLongPtr32(IntPtr hWnd, int index);
|
|
139
|
+
|
|
140
|
+
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
|
|
141
|
+
private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int index, IntPtr value);
|
|
142
|
+
|
|
143
|
+
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
|
|
144
|
+
private static extern IntPtr SetWindowLongPtr32(IntPtr hWnd, int index, IntPtr value);
|
|
145
|
+
|
|
146
|
+
[DllImport("user32.dll")]
|
|
147
|
+
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr insertAfter, int x, int y, int cx, int cy, uint flags);
|
|
148
|
+
|
|
149
|
+
[DllImport("user32.dll")]
|
|
150
|
+
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
|
151
|
+
|
|
152
|
+
[DllImport("user32.dll")]
|
|
153
|
+
public static extern IntPtr GetForegroundWindow();
|
|
154
|
+
|
|
155
|
+
[DllImport("user32.dll")]
|
|
156
|
+
public static extern bool AttachThreadInput(uint attachThreadId, uint attachToThreadId, bool attach);
|
|
157
|
+
|
|
158
|
+
[DllImport("kernel32.dll")]
|
|
159
|
+
public static extern uint GetCurrentThreadId();
|
|
160
|
+
|
|
161
|
+
[DllImport("user32.dll")]
|
|
162
|
+
public static extern IntPtr SetActiveWindow(IntPtr hWnd);
|
|
163
|
+
|
|
164
|
+
[DllImport("user32.dll")]
|
|
165
|
+
public static extern IntPtr SetFocus(IntPtr hWnd);
|
|
166
|
+
|
|
167
|
+
[DllImport("user32.dll")]
|
|
168
|
+
public static extern bool BringWindowToTop(IntPtr hWnd);
|
|
169
|
+
|
|
170
|
+
[DllImport("user32.dll")]
|
|
171
|
+
public static extern bool ShowWindow(IntPtr hWnd, int command);
|
|
172
|
+
|
|
173
|
+
[DllImport("user32.dll")]
|
|
174
|
+
public static extern bool EnableWindow(IntPtr hWnd, bool enabled);
|
|
175
|
+
|
|
176
|
+
[DllImport("user32.dll")]
|
|
177
|
+
public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
|
|
178
|
+
|
|
179
|
+
[DllImport("user32.dll")]
|
|
180
|
+
public static extern int GetSystemMetrics(int index);
|
|
181
|
+
|
|
182
|
+
[DllImport("user32.dll")]
|
|
183
|
+
public static extern IntPtr GetSystemMenu(IntPtr hWnd, bool revert);
|
|
184
|
+
|
|
185
|
+
[DllImport("user32.dll")]
|
|
186
|
+
public static extern bool DeleteMenu(IntPtr menu, uint position, uint flags);
|
|
187
|
+
|
|
188
|
+
[DllImport("user32.dll")]
|
|
189
|
+
public static extern bool DrawMenuBar(IntPtr hWnd);
|
|
190
|
+
|
|
191
|
+
[DllImport("user32.dll")]
|
|
192
|
+
public static extern bool SetLayeredWindowAttributes(IntPtr hWnd, uint colorKey, byte alpha, uint flags);
|
|
193
|
+
|
|
194
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
195
|
+
public struct RECT { public int Left; public int Top; public int Right; public int Bottom; }
|
|
196
|
+
|
|
197
|
+
public static IntPtr GetWindowLongPtr(IntPtr hWnd, int index) {
|
|
198
|
+
return IntPtr.Size == 8 ? GetWindowLongPtr64(hWnd, index) : GetWindowLongPtr32(hWnd, index);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public static IntPtr SetWindowLongPtr(IntPtr hWnd, int index, IntPtr value) {
|
|
202
|
+
return IntPtr.Size == 8 ? SetWindowLongPtr64(hWnd, index, value) : SetWindowLongPtr32(hWnd, index, value);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public static IntPtr FindWindowForProcess(uint expectedProcessId) {
|
|
206
|
+
IntPtr result = IntPtr.Zero;
|
|
207
|
+
EnumWindows((window, state) => {
|
|
208
|
+
uint processId;
|
|
209
|
+
GetWindowThreadProcessId(window, out processId);
|
|
210
|
+
if (processId == expectedProcessId && GetParent(window) == IntPtr.Zero) {
|
|
211
|
+
result = window;
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
return true;
|
|
215
|
+
}, IntPtr.Zero);
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
'@
|
|
220
|
+
|
|
221
|
+
function Find-AtomWindow([uint32] $ProcessId) {
|
|
222
|
+
for ($attempt = 0; $attempt -lt 50; $attempt += 1) {
|
|
223
|
+
$window = [AtomJSNativeWindow]::FindWindowForProcess($ProcessId)
|
|
224
|
+
if ($window -ne [IntPtr]::Zero) { return $window }
|
|
225
|
+
Start-Sleep -Milliseconds 40
|
|
226
|
+
}
|
|
227
|
+
return [IntPtr]::Zero
|
|
228
|
+
}
|
|
229
|
+
`;
|
|
230
|
+
|
|
231
|
+
const WINDOWS_WINDOW_SETUP = WINDOWS_INTEROP + String.raw`
|
|
232
|
+
$child = Find-AtomWindow ([uint32]$env:ATOMJS_CHILD_PID)
|
|
233
|
+
if ($child -eq [IntPtr]::Zero) { throw 'The AtomJS WebView window handle was not found.' }
|
|
234
|
+
|
|
235
|
+
$GWL_STYLE = -16
|
|
236
|
+
$GWL_EXSTYLE = -20
|
|
237
|
+
$GWLP_HWNDPARENT = -8
|
|
238
|
+
$WS_CAPTION = 0x00C00000L
|
|
239
|
+
$WS_THICKFRAME = 0x00040000L
|
|
240
|
+
$WS_MINIMIZEBOX = 0x00020000L
|
|
241
|
+
$WS_MAXIMIZEBOX = 0x00010000L
|
|
242
|
+
$WS_EX_TOOLWINDOW = 0x00000080L
|
|
243
|
+
$WS_EX_APPWINDOW = 0x00040000L
|
|
244
|
+
$WS_EX_LAYERED = 0x00080000L
|
|
245
|
+
$WS_EX_NOACTIVATE = 0x08000000L
|
|
246
|
+
$SWP_NOSIZE = 0x0001
|
|
247
|
+
$SWP_NOMOVE = 0x0002
|
|
248
|
+
$SWP_NOACTIVATE = 0x0010
|
|
249
|
+
$SWP_FRAMECHANGED = 0x0020
|
|
250
|
+
$SWP_SHOWWINDOW = 0x0040
|
|
251
|
+
$SC_CLOSE = 0xF060
|
|
252
|
+
$MF_BYCOMMAND = 0x0000
|
|
253
|
+
$LWA_ALPHA = 0x00000002
|
|
254
|
+
|
|
255
|
+
$style = [AtomJSNativeWindow]::GetWindowLongPtr($child, $GWL_STYLE).ToInt64()
|
|
256
|
+
if ($env:ATOMJS_FRAME -eq '0') { $style = $style -band (-bnot ($WS_CAPTION -bor $WS_THICKFRAME)) }
|
|
257
|
+
if ($env:ATOMJS_RESIZABLE -eq '0') { $style = $style -band (-bnot $WS_THICKFRAME) }
|
|
258
|
+
if ($env:ATOMJS_MINIMIZABLE -eq '0') { $style = $style -band (-bnot $WS_MINIMIZEBOX) }
|
|
259
|
+
if ($env:ATOMJS_MAXIMIZABLE -eq '0') { $style = $style -band (-bnot $WS_MAXIMIZEBOX) }
|
|
260
|
+
[void][AtomJSNativeWindow]::SetWindowLongPtr($child, $GWL_STYLE, [IntPtr]$style)
|
|
261
|
+
|
|
262
|
+
$extendedStyle = [AtomJSNativeWindow]::GetWindowLongPtr($child, $GWL_EXSTYLE).ToInt64()
|
|
263
|
+
if ($env:ATOMJS_SKIP_TASKBAR -eq '1') {
|
|
264
|
+
$extendedStyle = ($extendedStyle -bor $WS_EX_TOOLWINDOW) -band (-bnot $WS_EX_APPWINDOW)
|
|
265
|
+
}
|
|
266
|
+
if ($env:ATOMJS_FOCUSABLE -eq '0') {
|
|
267
|
+
$extendedStyle = $extendedStyle -bor $WS_EX_NOACTIVATE
|
|
268
|
+
} else {
|
|
269
|
+
$extendedStyle = $extendedStyle -band (-bnot $WS_EX_NOACTIVATE)
|
|
270
|
+
}
|
|
271
|
+
$opacity = [Math]::Max(0.0, [Math]::Min(1.0, [double]$env:ATOMJS_OPACITY))
|
|
272
|
+
if ($env:ATOMJS_TRANSPARENT -eq '1' -or $opacity -lt 1.0) {
|
|
273
|
+
$extendedStyle = $extendedStyle -bor $WS_EX_LAYERED
|
|
274
|
+
}
|
|
275
|
+
[void][AtomJSNativeWindow]::SetWindowLongPtr($child, $GWL_EXSTYLE, [IntPtr]$extendedStyle)
|
|
276
|
+
|
|
277
|
+
if ($env:ATOMJS_CLOSABLE -eq '0') {
|
|
278
|
+
$menu = [AtomJSNativeWindow]::GetSystemMenu($child, $false)
|
|
279
|
+
if ($menu -ne [IntPtr]::Zero) {
|
|
280
|
+
[void][AtomJSNativeWindow]::DeleteMenu($menu, $SC_CLOSE, $MF_BYCOMMAND)
|
|
281
|
+
[void][AtomJSNativeWindow]::DrawMenuBar($child)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
$alpha = [byte][Math]::Round($opacity * 255.0)
|
|
286
|
+
if ($env:ATOMJS_TRANSPARENT -eq '1' -or $opacity -lt 1.0) {
|
|
287
|
+
[void][AtomJSNativeWindow]::SetLayeredWindowAttributes($child, 0, $alpha, $LWA_ALPHA)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
$parent = [IntPtr]::Zero
|
|
291
|
+
if ([uint32]$env:ATOMJS_PARENT_PID -gt 0) {
|
|
292
|
+
$parent = Find-AtomWindow ([uint32]$env:ATOMJS_PARENT_PID)
|
|
293
|
+
if ($parent -ne [IntPtr]::Zero) {
|
|
294
|
+
[void][AtomJSNativeWindow]::SetWindowLongPtr($child, $GWLP_HWNDPARENT, $parent)
|
|
295
|
+
if ($env:ATOMJS_MODAL -eq '1') { [void][AtomJSNativeWindow]::EnableWindow($parent, $false) }
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
$rect = New-Object AtomJSNativeWindow+RECT
|
|
300
|
+
[void][AtomJSNativeWindow]::GetWindowRect($child, [ref]$rect)
|
|
301
|
+
$width = $rect.Right - $rect.Left
|
|
302
|
+
$height = $rect.Bottom - $rect.Top
|
|
303
|
+
$x = $rect.Left
|
|
304
|
+
$y = $rect.Top
|
|
305
|
+
if ($env:ATOMJS_CENTER -eq '1' -and $parent -ne [IntPtr]::Zero) {
|
|
306
|
+
$parentRect = New-Object AtomJSNativeWindow+RECT
|
|
307
|
+
[void][AtomJSNativeWindow]::GetWindowRect($parent, [ref]$parentRect)
|
|
308
|
+
$x = $parentRect.Left + [Math]::Max(0, (($parentRect.Right - $parentRect.Left) - $width) / 2)
|
|
309
|
+
$y = $parentRect.Top + [Math]::Max(0, (($parentRect.Bottom - $parentRect.Top) - $height) / 2)
|
|
310
|
+
} elseif ($env:ATOMJS_CENTER -eq '1') {
|
|
311
|
+
$x = [Math]::Max(0, ([AtomJSNativeWindow]::GetSystemMetrics(0) - $width) / 2)
|
|
312
|
+
$y = [Math]::Max(0, ([AtomJSNativeWindow]::GetSystemMetrics(1) - $height) / 2)
|
|
313
|
+
}
|
|
314
|
+
if ($env:ATOMJS_X -ne '') { $x = [int]$env:ATOMJS_X }
|
|
315
|
+
if ($env:ATOMJS_Y -ne '') { $y = [int]$env:ATOMJS_Y }
|
|
316
|
+
|
|
317
|
+
$insertAfter = if ($env:ATOMJS_ALWAYS_ON_TOP -eq '1') { [IntPtr](-1) } else { [IntPtr]::Zero }
|
|
318
|
+
$positionFlags = $SWP_FRAMECHANGED
|
|
319
|
+
if ($env:ATOMJS_SHOW -ne '0') { $positionFlags = $positionFlags -bor $SWP_SHOWWINDOW }
|
|
320
|
+
[void][AtomJSNativeWindow]::SetWindowPos($child, $insertAfter, [int]$x, [int]$y, $width, $height, $positionFlags)
|
|
321
|
+
if ($env:ATOMJS_SHOW -eq '0') {
|
|
322
|
+
[void][AtomJSNativeWindow]::ShowWindow($child, 0)
|
|
323
|
+
} else {
|
|
324
|
+
[void][AtomJSNativeWindow]::ShowWindow($child, 5)
|
|
325
|
+
[void][AtomJSNativeWindow]::BringWindowToTop($child)
|
|
326
|
+
}
|
|
327
|
+
if ($env:ATOMJS_SHOW -ne '0' -and $env:ATOMJS_FOCUSABLE -ne '0') {
|
|
328
|
+
# WebView windows live in helper processes. Temporarily attach the setup
|
|
329
|
+
# thread to the foreground and WebView UI threads so Windows accepts the
|
|
330
|
+
# activation request instead of leaving OAuth/login windows behind the app.
|
|
331
|
+
$foreground = [AtomJSNativeWindow]::GetForegroundWindow()
|
|
332
|
+
[uint32]$foregroundProcess = 0
|
|
333
|
+
[uint32]$childProcess = 0
|
|
334
|
+
$foregroundThread = if ($foreground -ne [IntPtr]::Zero) {
|
|
335
|
+
[AtomJSNativeWindow]::GetWindowThreadProcessId($foreground, [ref]$foregroundProcess)
|
|
336
|
+
} else { 0 }
|
|
337
|
+
$childThread = [AtomJSNativeWindow]::GetWindowThreadProcessId($child, [ref]$childProcess)
|
|
338
|
+
$currentThread = [AtomJSNativeWindow]::GetCurrentThreadId()
|
|
339
|
+
$attachedForeground = $false
|
|
340
|
+
$attachedChild = $false
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
if ($foregroundThread -ne 0 -and $foregroundThread -ne $currentThread) {
|
|
344
|
+
$attachedForeground = [AtomJSNativeWindow]::AttachThreadInput($currentThread, $foregroundThread, $true)
|
|
345
|
+
}
|
|
346
|
+
if ($childThread -ne 0 -and $childThread -ne $currentThread) {
|
|
347
|
+
$attachedChild = [AtomJSNativeWindow]::AttachThreadInput($currentThread, $childThread, $true)
|
|
348
|
+
}
|
|
349
|
+
[void][AtomJSNativeWindow]::BringWindowToTop($child)
|
|
350
|
+
[void][AtomJSNativeWindow]::SetActiveWindow($child)
|
|
351
|
+
[void][AtomJSNativeWindow]::SetForegroundWindow($child)
|
|
352
|
+
[void][AtomJSNativeWindow]::SetFocus($child)
|
|
353
|
+
} finally {
|
|
354
|
+
if ($attachedChild) {
|
|
355
|
+
[void][AtomJSNativeWindow]::AttachThreadInput($currentThread, $childThread, $false)
|
|
356
|
+
}
|
|
357
|
+
if ($attachedForeground) {
|
|
358
|
+
[void][AtomJSNativeWindow]::AttachThreadInput($currentThread, $foregroundThread, $false)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
`;
|
|
363
|
+
|
|
364
|
+
const WINDOWS_MODAL_RELEASE = WINDOWS_INTEROP + String.raw`
|
|
365
|
+
$parent = Find-AtomWindow ([uint32]$env:ATOMJS_PARENT_PID)
|
|
366
|
+
if ($parent -ne [IntPtr]::Zero) {
|
|
367
|
+
[void][AtomJSNativeWindow]::EnableWindow($parent, $true)
|
|
368
|
+
[void][AtomJSNativeWindow]::ShowWindow($parent, 5)
|
|
369
|
+
[void][AtomJSNativeWindow]::BringWindowToTop($parent)
|
|
370
|
+
[void][AtomJSNativeWindow]::SetForegroundWindow($parent)
|
|
371
|
+
}
|
|
372
|
+
`;
|
|
373
|
+
|
|
374
|
+
async function main() {
|
|
375
|
+
let view = null;
|
|
376
|
+
let windowsOwnership = null;
|
|
377
|
+
try {
|
|
378
|
+
view = new Webview(Boolean(config.debug));
|
|
379
|
+
view.title(String(config.title || 'AtomJS App'));
|
|
380
|
+
|
|
381
|
+
const width = Number(config.width || 800);
|
|
382
|
+
const height = Number(config.height || 600);
|
|
383
|
+
view.size(
|
|
384
|
+
width,
|
|
385
|
+
height,
|
|
386
|
+
config.resizable === false ? SizeHint.Fixed : SizeHint.None
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
if (Number(config.minWidth) > 0 || Number(config.minHeight) > 0) {
|
|
390
|
+
view.size(
|
|
391
|
+
Number(config.minWidth) > 0 ? Number(config.minWidth) : width,
|
|
392
|
+
Number(config.minHeight) > 0 ? Number(config.minHeight) : height,
|
|
393
|
+
SizeHint.Min
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
if (Number(config.maxWidth) > 0 || Number(config.maxHeight) > 0) {
|
|
397
|
+
view.size(
|
|
398
|
+
Number(config.maxWidth) > 0 ? Number(config.maxWidth) : width,
|
|
399
|
+
Number(config.maxHeight) > 0 ? Number(config.maxHeight) : height,
|
|
400
|
+
SizeHint.Max
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
view.init(String(config.bridgeScript || ''));
|
|
405
|
+
view.navigate(String(config.url));
|
|
406
|
+
|
|
407
|
+
if (process.platform === 'win32') {
|
|
408
|
+
windowsOwnership = configureWindowsWindow(config);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
emit({ type: 'created', windowId: Number(config.windowId || 0), pid: process.pid });
|
|
412
|
+
view.show();
|
|
413
|
+
} catch (error) {
|
|
414
|
+
console.error('AtomJS native window failed:', error && error.stack ? error.stack : error);
|
|
415
|
+
process.exitCode = 1;
|
|
416
|
+
} finally {
|
|
417
|
+
if (process.platform === 'win32') releaseWindowsModal(windowsOwnership);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
await main();
|