@atom-js-org/runtime 0.4.5-alpha.0 → 0.5.1-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 +112 -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;
|
|
@@ -70,6 +70,28 @@ static NSColor *AtomJSColor(NSString *hex) {
|
|
|
70
70
|
return [NSColor colorWithSRGBRed:red green:green blue:blue alpha:alpha];
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
static void AtomJSConfigureTransparentWebView(WKWebView *webView) {
|
|
74
|
+
if (!webView) return;
|
|
75
|
+
|
|
76
|
+
// `drawsBackground` is not exposed as a public Objective-C property by every
|
|
77
|
+
// macOS SDK. Use guarded KVC so older SDKs still compile, while retaining the
|
|
78
|
+
// WebKit behavior needed for genuinely transparent pages.
|
|
79
|
+
@try {
|
|
80
|
+
[webView setValue:@NO forKey:@"drawsBackground"];
|
|
81
|
+
} @catch (__unused NSException *exception) {}
|
|
82
|
+
|
|
83
|
+
// Newer WebKit versions expose a public under-page color. Set it through the
|
|
84
|
+
// Objective-C runtime so the host also compiles against SDKs that predate it.
|
|
85
|
+
if ([webView respondsToSelector:NSSelectorFromString(@"setUnderPageBackgroundColor:")]) {
|
|
86
|
+
@try {
|
|
87
|
+
[webView setValue:[NSColor clearColor] forKey:@"underPageBackgroundColor"];
|
|
88
|
+
} @catch (__unused NSException *exception) {}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
webView.wantsLayer = YES;
|
|
92
|
+
webView.layer.backgroundColor = [NSColor clearColor].CGColor;
|
|
93
|
+
}
|
|
94
|
+
|
|
73
95
|
static NSImage *AtomJSDefaultApplicationIcon(void) {
|
|
74
96
|
NSSize size = NSMakeSize(512.0, 512.0);
|
|
75
97
|
NSImage *image = [[NSImage alloc] initWithSize:size];
|
|
@@ -114,6 +136,8 @@ static void AtomJSConfigureApplicationIdentity(NSApplication *application) {
|
|
|
114
136
|
@property(nonatomic, strong) NSNumber *windowId;
|
|
115
137
|
@property(nonatomic, strong) NSWindow *window;
|
|
116
138
|
@property(nonatomic, strong) WKWebView *webView;
|
|
139
|
+
@property(nonatomic, weak) AtomJSWindowController *parentController;
|
|
140
|
+
@property(nonatomic, assign) BOOL modal;
|
|
117
141
|
- (instancetype)initWithWindowId:(NSNumber *)windowId config:(NSDictionary *)config;
|
|
118
142
|
- (void)navigate:(NSString *)urlString;
|
|
119
143
|
@end
|
|
@@ -130,10 +154,20 @@ static void AtomJSConfigureApplicationIdentity(NSApplication *application) {
|
|
|
130
154
|
CGFloat height = MAX(240.0, [AtomJSNumber(config[@"height"], @600) doubleValue]);
|
|
131
155
|
NSRect frame = NSMakeRect(0, 0, width, height);
|
|
132
156
|
|
|
133
|
-
NSWindowStyleMask style =
|
|
157
|
+
NSWindowStyleMask style = 0;
|
|
158
|
+
if (AtomJSBoolean(config[@"closable"], YES)) style |= NSWindowStyleMaskClosable;
|
|
159
|
+
if (AtomJSBoolean(config[@"minimizable"], YES)) style |= NSWindowStyleMaskMiniaturizable;
|
|
134
160
|
if (AtomJSBoolean(config[@"frame"], YES)) style |= NSWindowStyleMaskTitled;
|
|
135
161
|
if (AtomJSBoolean(config[@"resizable"], YES)) style |= NSWindowStyleMaskResizable;
|
|
136
162
|
|
|
163
|
+
NSString *titleBarStyle = AtomJSString(config[@"titleBarStyle"], @"default");
|
|
164
|
+
BOOL usesHiddenTitleBar = [titleBarStyle isEqualToString:@"hidden"]
|
|
165
|
+
|| [titleBarStyle isEqualToString:@"hiddenInset"]
|
|
166
|
+
|| [titleBarStyle isEqualToString:@"customButtonsOnHover"];
|
|
167
|
+
if (usesHiddenTitleBar) {
|
|
168
|
+
style |= NSWindowStyleMaskFullSizeContentView;
|
|
169
|
+
}
|
|
170
|
+
|
|
137
171
|
_window = [[NSWindow alloc]
|
|
138
172
|
initWithContentRect:frame
|
|
139
173
|
styleMask:style
|
|
@@ -144,8 +178,41 @@ static void AtomJSConfigureApplicationIdentity(NSApplication *application) {
|
|
|
144
178
|
_window.title = AtomJSString(config[@"title"], atomAppName);
|
|
145
179
|
_window.backgroundColor = AtomJSColor(config[@"backgroundColor"]);
|
|
146
180
|
_window.tabbingMode = NSWindowTabbingModeDisallowed;
|
|
181
|
+
_window.alphaValue = MIN(1.0, MAX(0.0, [AtomJSNumber(config[@"opacity"], @1) doubleValue]));
|
|
182
|
+
_window.opaque = !AtomJSBoolean(config[@"transparent"], NO);
|
|
183
|
+
if (!_window.opaque) {
|
|
184
|
+
_window.backgroundColor = [NSColor clearColor];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (usesHiddenTitleBar) {
|
|
188
|
+
_window.titleVisibility = NSWindowTitleHidden;
|
|
189
|
+
_window.titlebarAppearsTransparent = YES;
|
|
190
|
+
}
|
|
147
191
|
|
|
148
|
-
if (AtomJSBoolean(config[@"
|
|
192
|
+
if (!AtomJSBoolean(config[@"fullscreenable"], YES)) {
|
|
193
|
+
_window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenNone;
|
|
194
|
+
}
|
|
195
|
+
[_window standardWindowButton:NSWindowZoomButton].enabled = AtomJSBoolean(config[@"maximizable"], YES);
|
|
196
|
+
if (AtomJSBoolean(config[@"alwaysOnTop"], NO)) {
|
|
197
|
+
_window.level = NSFloatingWindowLevel;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
CGFloat minWidth = [AtomJSNumber(config[@"minWidth"], @0) doubleValue];
|
|
201
|
+
CGFloat minHeight = [AtomJSNumber(config[@"minHeight"], @0) doubleValue];
|
|
202
|
+
CGFloat maxWidth = [AtomJSNumber(config[@"maxWidth"], @0) doubleValue];
|
|
203
|
+
CGFloat maxHeight = [AtomJSNumber(config[@"maxHeight"], @0) doubleValue];
|
|
204
|
+
if (minWidth > 0 || minHeight > 0) {
|
|
205
|
+
_window.contentMinSize = NSMakeSize(MAX(1.0, minWidth), MAX(1.0, minHeight));
|
|
206
|
+
}
|
|
207
|
+
if (maxWidth > 0 || maxHeight > 0) {
|
|
208
|
+
_window.contentMaxSize = NSMakeSize(maxWidth > 0 ? maxWidth : CGFLOAT_MAX, maxHeight > 0 ? maxHeight : CGFLOAT_MAX);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if ([config[@"x"] isKindOfClass:[NSNumber class]] && [config[@"y"] isKindOfClass:[NSNumber class]]) {
|
|
212
|
+
[_window setFrameOrigin:NSMakePoint([config[@"x"] doubleValue], [config[@"y"] doubleValue])];
|
|
213
|
+
} else if (AtomJSBoolean(config[@"center"], YES)) {
|
|
214
|
+
[_window center];
|
|
215
|
+
}
|
|
149
216
|
|
|
150
217
|
WKUserContentController *contentController = [[WKUserContentController alloc] init];
|
|
151
218
|
NSString *bridgeScript = AtomJSString(config[@"bridgeScript"], @"");
|
|
@@ -163,14 +230,40 @@ static void AtomJSConfigureApplicationIdentity(NSApplication *application) {
|
|
|
163
230
|
_webView = [[WKWebView alloc] initWithFrame:frame configuration:webConfiguration];
|
|
164
231
|
_webView.navigationDelegate = self;
|
|
165
232
|
_webView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
233
|
+
if (AtomJSBoolean(config[@"transparent"], NO)) AtomJSConfigureTransparentWebView(_webView);
|
|
166
234
|
_window.contentView = _webView;
|
|
167
235
|
|
|
236
|
+
NSNumber *parentWindowId = AtomJSNumber(config[@"parentWindowId"], nil);
|
|
237
|
+
_parentController = parentWindowId ? atomWindows[parentWindowId] : nil;
|
|
238
|
+
_modal = AtomJSBoolean(config[@"modal"], NO) && _parentController != nil;
|
|
239
|
+
|
|
240
|
+
NSDictionary *trafficLightPosition = [config[@"trafficLightPosition"] isKindOfClass:[NSDictionary class]]
|
|
241
|
+
? config[@"trafficLightPosition"]
|
|
242
|
+
: nil;
|
|
243
|
+
if (trafficLightPosition) {
|
|
244
|
+
CGFloat x = [AtomJSNumber(trafficLightPosition[@"x"], @0) doubleValue];
|
|
245
|
+
CGFloat y = [AtomJSNumber(trafficLightPosition[@"y"], @0) doubleValue];
|
|
246
|
+
NSButton *closeButton = [_window standardWindowButton:NSWindowCloseButton];
|
|
247
|
+
NSView *container = closeButton.superview;
|
|
248
|
+
if (container) {
|
|
249
|
+
NSPoint origin = container.frame.origin;
|
|
250
|
+
origin.x = x;
|
|
251
|
+
origin.y = MAX(0.0, _window.frame.size.height - container.frame.size.height - y);
|
|
252
|
+
[container setFrameOrigin:origin];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
168
256
|
[self navigate:AtomJSString(config[@"url"], @"about:blank")];
|
|
169
257
|
|
|
170
258
|
if (AtomJSBoolean(config[@"show"], YES)) {
|
|
171
259
|
[NSApp unhide:nil];
|
|
172
|
-
|
|
173
|
-
|
|
260
|
+
if (_modal) {
|
|
261
|
+
[_parentController.window beginSheet:_window completionHandler:nil];
|
|
262
|
+
} else {
|
|
263
|
+
if (_parentController) [_parentController.window addChildWindow:_window ordered:NSWindowAbove];
|
|
264
|
+
[_window makeKeyAndOrderFront:nil];
|
|
265
|
+
[_window orderFrontRegardless];
|
|
266
|
+
}
|
|
174
267
|
[NSApp activateIgnoringOtherApps:YES];
|
|
175
268
|
}
|
|
176
269
|
|
|
@@ -226,6 +319,13 @@ static void AtomJSConfigureApplicationIdentity(NSApplication *application) {
|
|
|
226
319
|
}
|
|
227
320
|
|
|
228
321
|
- (void)windowWillClose:(NSNotification *)notification {
|
|
322
|
+
if (self.parentController) {
|
|
323
|
+
if (self.modal && self.window.sheetParent) {
|
|
324
|
+
[self.window.sheetParent endSheet:self.window];
|
|
325
|
+
} else {
|
|
326
|
+
[self.parentController.window removeChildWindow:self.window];
|
|
327
|
+
}
|
|
328
|
+
}
|
|
229
329
|
[atomWindows removeObjectForKey:self.windowId];
|
|
230
330
|
AtomJSEmit(@{
|
|
231
331
|
@"type": @"closed",
|
|
@@ -486,6 +586,14 @@ static void AtomJSHandleMessage(NSDictionary *message) {
|
|
|
486
586
|
if ([bounds[@"width"] isKindOfClass:[NSNumber class]]) frame.size.width = MAX(1.0, [bounds[@"width"] doubleValue]);
|
|
487
587
|
if ([bounds[@"height"] isKindOfClass:[NSNumber class]]) frame.size.height = MAX(1.0, [bounds[@"height"] doubleValue]);
|
|
488
588
|
[controller.window setFrame:frame display:YES animate:NO];
|
|
589
|
+
} else if ([command isEqualToString:@"set-always-on-top"]) {
|
|
590
|
+
controller.window.level = AtomJSBoolean(message[@"value"], NO) ? NSFloatingWindowLevel : NSNormalWindowLevel;
|
|
591
|
+
} else if ([command isEqualToString:@"set-opacity"]) {
|
|
592
|
+
controller.window.alphaValue = MIN(1.0, MAX(0.0, [AtomJSNumber(message[@"value"], @1) doubleValue]));
|
|
593
|
+
} else if ([command isEqualToString:@"set-resizable"]) {
|
|
594
|
+
BOOL requested = AtomJSBoolean(message[@"value"], YES);
|
|
595
|
+
if (requested) controller.window.styleMask |= NSWindowStyleMaskResizable;
|
|
596
|
+
else controller.window.styleMask &= ~NSWindowStyleMaskResizable;
|
|
489
597
|
} else {
|
|
490
598
|
AtomJSRespond(requestId, NO, nil, [NSString stringWithFormat:@"Unsupported AtomJS native command: %@", command]);
|
|
491
599
|
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();
|