@atom-js-org/runtime 0.2.0-alpha.0 → 0.4.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/package.json +1 -1
- package/src/app.cjs +2 -0
- package/src/browser-window.cjs +149 -52
- package/src/dialog.cjs +32 -28
- package/src/native-host.cjs +356 -0
- package/src/runtime/macos-native-host.m +519 -0
- package/src/runtime/window-host.mjs +28 -72
- package/src/web-contents.cjs +8 -2
- package/src/runtime/macos-window-host.jxa.js +0 -147
package/package.json
CHANGED
package/src/app.cjs
CHANGED
|
@@ -6,6 +6,7 @@ const path = require('node:path');
|
|
|
6
6
|
const fs = require('node:fs');
|
|
7
7
|
const state = require('./state.cjs');
|
|
8
8
|
const { BridgeServer } = require('./bridge-server.cjs');
|
|
9
|
+
const { stopNativeHost } = require('./native-host.cjs');
|
|
9
10
|
|
|
10
11
|
class App extends EventEmitter {
|
|
11
12
|
constructor() {
|
|
@@ -41,6 +42,7 @@ class App extends EventEmitter {
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
for (const win of [...state.windows.values()]) win.destroy();
|
|
45
|
+
await stopNativeHost();
|
|
44
46
|
if (state.bridgeServer) await state.bridgeServer.stop();
|
|
45
47
|
this.emit('will-quit');
|
|
46
48
|
this.emit('quit', {}, 0);
|
package/src/browser-window.cjs
CHANGED
|
@@ -9,6 +9,7 @@ const state = require('./state.cjs');
|
|
|
9
9
|
const app = require('./app.cjs');
|
|
10
10
|
const { WebContents } = require('./web-contents.cjs');
|
|
11
11
|
const { generateBridgeScript } = require('./bridge-script.cjs');
|
|
12
|
+
const { getNativeHost } = require('./native-host.cjs');
|
|
12
13
|
|
|
13
14
|
class BrowserWindow extends EventEmitter {
|
|
14
15
|
constructor(options = {}) {
|
|
@@ -17,6 +18,8 @@ class BrowserWindow extends EventEmitter {
|
|
|
17
18
|
this.options = normalizeOptions(options);
|
|
18
19
|
this.webContents = new WebContents(this);
|
|
19
20
|
this._child = null;
|
|
21
|
+
this._nativeHost = null;
|
|
22
|
+
this._hostAttached = false;
|
|
20
23
|
this._destroyed = false;
|
|
21
24
|
this._rendererReady = false;
|
|
22
25
|
this._visible = this.options.show;
|
|
@@ -26,14 +29,20 @@ class BrowserWindow extends EventEmitter {
|
|
|
26
29
|
this._menu = null;
|
|
27
30
|
this._menuBarVisible = true;
|
|
28
31
|
this._lastFinishedLoad = null;
|
|
32
|
+
this._fullScreen = false;
|
|
33
|
+
this._maximized = false;
|
|
34
|
+
this._minimized = false;
|
|
35
|
+
this._bounds = {
|
|
36
|
+
x: 0,
|
|
37
|
+
y: 0,
|
|
38
|
+
width: this.options.width,
|
|
39
|
+
height: this.options.height
|
|
40
|
+
};
|
|
29
41
|
state.windows.set(this.id, this);
|
|
30
42
|
}
|
|
31
43
|
|
|
32
44
|
loadFile(filePath) {
|
|
33
45
|
const task = this._loadFile(filePath);
|
|
34
|
-
// Electron applications commonly call loadFile() without awaiting it. Keep
|
|
35
|
-
// failures observable to callers while preventing a second, unhandled
|
|
36
|
-
// rejection from terminating modern Node.js processes.
|
|
37
46
|
task.catch(() => {});
|
|
38
47
|
return task;
|
|
39
48
|
}
|
|
@@ -61,43 +70,63 @@ class BrowserWindow extends EventEmitter {
|
|
|
61
70
|
|
|
62
71
|
async _load(url) {
|
|
63
72
|
if (this._destroyed) throw new Error('BrowserWindow has been destroyed');
|
|
64
|
-
this._currentUrl = url;
|
|
73
|
+
this._currentUrl = String(url);
|
|
65
74
|
|
|
66
|
-
if (this.
|
|
67
|
-
|
|
75
|
+
if (this._hostAttached) {
|
|
76
|
+
this._sendHostCommand({ command: 'navigate', url: this._currentUrl });
|
|
68
77
|
return;
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
const preloadPath = this.options.webPreferences.preload;
|
|
72
81
|
let preloadCode = '';
|
|
73
|
-
if (preloadPath)
|
|
74
|
-
preloadCode = await fs.promises.readFile(path.resolve(preloadPath), 'utf8');
|
|
75
|
-
}
|
|
82
|
+
if (preloadPath) preloadCode = await fs.promises.readFile(path.resolve(preloadPath), 'utf8');
|
|
76
83
|
|
|
77
84
|
const config = {
|
|
78
85
|
title: this.options.title || app.getName(),
|
|
79
86
|
width: this.options.width,
|
|
80
87
|
height: this.options.height,
|
|
81
88
|
resizable: this.options.resizable,
|
|
89
|
+
center: this.options.center,
|
|
90
|
+
frame: this.options.frame,
|
|
91
|
+
show: this.options.show,
|
|
92
|
+
backgroundColor: this.options.backgroundColor,
|
|
82
93
|
debug: Boolean(this.options.webPreferences.devTools || process.env.ATOM_DEV === '1'),
|
|
83
|
-
url,
|
|
94
|
+
url: this._currentUrl,
|
|
84
95
|
bridgeScript: generateBridgeScript({
|
|
85
96
|
websocketUrl: state.bridgeServer.websocketUrl(this.id),
|
|
86
97
|
preloadCode
|
|
87
98
|
})
|
|
88
99
|
};
|
|
89
100
|
|
|
101
|
+
this._pendingLoad = this._createPendingLoad();
|
|
102
|
+
|
|
103
|
+
if (process.platform === 'darwin') {
|
|
104
|
+
this._nativeHost = getNativeHost(app.getName());
|
|
105
|
+
this._hostAttached = true;
|
|
106
|
+
await this._nativeHost.createWindow(this, config);
|
|
107
|
+
} else {
|
|
108
|
+
await this._startLegacyHost(config);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this._pendingLoad.catch(() => {});
|
|
112
|
+
return this._pendingLoad;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async _startLegacyHost(config) {
|
|
90
116
|
const configPath = path.join(os.tmpdir(), `atomjs-window-${process.pid}-${this.id}-${Date.now()}.json`);
|
|
91
117
|
await fs.promises.writeFile(configPath, JSON.stringify(config), { mode: 0o600 });
|
|
92
118
|
|
|
93
|
-
const hostPath = path.join(__dirname, 'runtime', 'window-host.mjs');
|
|
119
|
+
const hostPath = process.env.ATOM_WINDOW_HOST_ENTRY || path.join(__dirname, 'runtime', 'window-host.mjs');
|
|
94
120
|
const nodeExecutable = process.env.ATOM_NODE_EXECUTABLE || process.execPath;
|
|
95
|
-
|
|
121
|
+
const hostArgs = process.env.ATOM_EMBEDDED_RUNTIME === '1'
|
|
122
|
+
? ['--atomjs-window-host', configPath]
|
|
123
|
+
: [hostPath, configPath];
|
|
124
|
+
this._child = spawn(nodeExecutable, hostArgs, {
|
|
96
125
|
cwd: state.projectRoot,
|
|
97
126
|
env: process.env,
|
|
98
127
|
stdio: ['ignore', 'pipe', 'inherit']
|
|
99
128
|
});
|
|
100
|
-
|
|
129
|
+
this._hostAttached = true;
|
|
101
130
|
attachHostOutput(this, this._child);
|
|
102
131
|
|
|
103
132
|
this._child.once('error', (error) => {
|
|
@@ -111,16 +140,22 @@ class BrowserWindow extends EventEmitter {
|
|
|
111
140
|
const failedBeforeReady = !this._rendererReady && code !== 0 && signal == null;
|
|
112
141
|
if (failedBeforeReady) {
|
|
113
142
|
process.exitCode = code || 1;
|
|
114
|
-
this.webContents.emit(
|
|
143
|
+
this.webContents.emit(
|
|
144
|
+
'did-fail-load',
|
|
145
|
+
{},
|
|
146
|
+
code || 1,
|
|
147
|
+
'AtomJS window host exited before the renderer became ready',
|
|
148
|
+
this._currentUrl,
|
|
149
|
+
true
|
|
150
|
+
);
|
|
115
151
|
}
|
|
116
|
-
this.
|
|
117
|
-
state.windows.delete(this.id);
|
|
118
|
-
this.emit('closed', { code, signal });
|
|
119
|
-
if (state.windows.size === 0 && !state.isQuitting) app.emit('window-all-closed');
|
|
152
|
+
this._finalizeClosed({ code, signal });
|
|
120
153
|
}
|
|
121
154
|
});
|
|
155
|
+
}
|
|
122
156
|
|
|
123
|
-
|
|
157
|
+
_createPendingLoad() {
|
|
158
|
+
return new Promise((resolve, reject) => {
|
|
124
159
|
const timeout = setTimeout(() => {
|
|
125
160
|
cleanup();
|
|
126
161
|
reject(new Error('Renderer did not become ready within 20 seconds'));
|
|
@@ -141,11 +176,14 @@ class BrowserWindow extends EventEmitter {
|
|
|
141
176
|
this.once('ready-to-show', onReady);
|
|
142
177
|
this.once('closed', onClosed);
|
|
143
178
|
});
|
|
179
|
+
}
|
|
144
180
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
181
|
+
_sendHostCommand(command) {
|
|
182
|
+
if (this._nativeHost) {
|
|
183
|
+
this._nativeHost.send({ ...command, windowId: this.id });
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
149
187
|
}
|
|
150
188
|
|
|
151
189
|
_markRendererReady(details) {
|
|
@@ -154,6 +192,29 @@ class BrowserWindow extends EventEmitter {
|
|
|
154
192
|
|
|
155
193
|
_handleHostEvent(event) {
|
|
156
194
|
if (!event || typeof event !== 'object') return;
|
|
195
|
+
|
|
196
|
+
if (event.type === 'closed') {
|
|
197
|
+
this._finalizeClosed({ code: 0, signal: null });
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (event.type === 'focus') {
|
|
201
|
+
this.emit('focus');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (event.type === 'blur') {
|
|
205
|
+
this.emit('blur');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (event.type === 'minimize') {
|
|
209
|
+
this._minimized = true;
|
|
210
|
+
this.emit('minimize');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (event.type === 'restore') {
|
|
214
|
+
this._minimized = false;
|
|
215
|
+
this.emit('restore');
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
157
218
|
if (event.type === 'did-start-loading') {
|
|
158
219
|
if (event.url) this._currentUrl = String(event.url);
|
|
159
220
|
this.webContents.emit('did-start-loading');
|
|
@@ -179,10 +240,12 @@ class BrowserWindow extends EventEmitter {
|
|
|
179
240
|
if (href) this._currentUrl = href;
|
|
180
241
|
|
|
181
242
|
const now = Date.now();
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
243
|
+
if (
|
|
244
|
+
this._lastFinishedLoad &&
|
|
245
|
+
this._lastFinishedLoad.url === this._currentUrl &&
|
|
246
|
+
this._lastFinishedLoad.source !== source &&
|
|
247
|
+
now - this._lastFinishedLoad.time < 300
|
|
248
|
+
) {
|
|
186
249
|
return;
|
|
187
250
|
}
|
|
188
251
|
this._lastFinishedLoad = { url: this._currentUrl, time: now, source };
|
|
@@ -199,14 +262,26 @@ class BrowserWindow extends EventEmitter {
|
|
|
199
262
|
}
|
|
200
263
|
}
|
|
201
264
|
|
|
265
|
+
_finalizeClosed(details) {
|
|
266
|
+
if (this._destroyed) return;
|
|
267
|
+
this._destroyed = true;
|
|
268
|
+
this._hostAttached = false;
|
|
269
|
+
state.windows.delete(this.id);
|
|
270
|
+
this.emit('closed', details);
|
|
271
|
+
if (state.windows.size === 0 && !state.isQuitting) app.emit('window-all-closed');
|
|
272
|
+
}
|
|
273
|
+
|
|
202
274
|
show() {
|
|
203
275
|
this._visible = true;
|
|
276
|
+
this._sendHostCommand({ command: 'show' });
|
|
204
277
|
this.emit('show');
|
|
205
278
|
}
|
|
206
279
|
|
|
207
280
|
hide() {
|
|
208
281
|
this._visible = false;
|
|
209
|
-
|
|
282
|
+
if (!this._sendHostCommand({ command: 'hide' })) {
|
|
283
|
+
console.warn('[AtomJS] Native hide is not supported by the current platform host.');
|
|
284
|
+
}
|
|
210
285
|
this.emit('hide');
|
|
211
286
|
}
|
|
212
287
|
|
|
@@ -215,7 +290,9 @@ class BrowserWindow extends EventEmitter {
|
|
|
215
290
|
}
|
|
216
291
|
|
|
217
292
|
focus() {
|
|
218
|
-
|
|
293
|
+
if (!this._sendHostCommand({ command: 'focus' })) {
|
|
294
|
+
console.warn('[AtomJS] Native focus is not supported by the current platform host.');
|
|
295
|
+
}
|
|
219
296
|
}
|
|
220
297
|
|
|
221
298
|
blur() {}
|
|
@@ -225,6 +302,8 @@ class BrowserWindow extends EventEmitter {
|
|
|
225
302
|
const event = { defaultPrevented: false, preventDefault() { this.defaultPrevented = true; } };
|
|
226
303
|
this.emit('close', event);
|
|
227
304
|
if (event.defaultPrevented) return;
|
|
305
|
+
|
|
306
|
+
if (this._sendHostCommand({ command: 'close' })) return;
|
|
228
307
|
state.bridgeServer && state.bridgeServer.send(this.id, { type: 'system', command: 'close' });
|
|
229
308
|
setTimeout(() => {
|
|
230
309
|
if (!this._destroyed && this._child) this._child.kill();
|
|
@@ -233,13 +312,14 @@ class BrowserWindow extends EventEmitter {
|
|
|
233
312
|
|
|
234
313
|
destroy() {
|
|
235
314
|
if (this._destroyed) return;
|
|
236
|
-
this.
|
|
237
|
-
|
|
315
|
+
if (this._nativeHost) {
|
|
316
|
+
try { this._sendHostCommand({ command: 'destroy' }); } catch {}
|
|
317
|
+
}
|
|
238
318
|
if (this._child) {
|
|
239
319
|
try { this._child.kill(); } catch {}
|
|
240
320
|
this._child = null;
|
|
241
321
|
}
|
|
242
|
-
this.
|
|
322
|
+
this._finalizeClosed({ code: 0, signal: null });
|
|
243
323
|
}
|
|
244
324
|
|
|
245
325
|
isDestroyed() {
|
|
@@ -248,7 +328,7 @@ class BrowserWindow extends EventEmitter {
|
|
|
248
328
|
|
|
249
329
|
setTitle(title) {
|
|
250
330
|
this.options.title = String(title);
|
|
251
|
-
if (state.bridgeServer) {
|
|
331
|
+
if (!this._sendHostCommand({ command: 'set-title', title: this.options.title }) && state.bridgeServer) {
|
|
252
332
|
state.bridgeServer.send(this.id, { type: 'system', command: 'set-title', title: this.options.title });
|
|
253
333
|
}
|
|
254
334
|
}
|
|
@@ -257,7 +337,6 @@ class BrowserWindow extends EventEmitter {
|
|
|
257
337
|
return this.options.title || app.getName();
|
|
258
338
|
}
|
|
259
339
|
|
|
260
|
-
|
|
261
340
|
setMenu(menu) {
|
|
262
341
|
this._menu = menu || null;
|
|
263
342
|
return this;
|
|
@@ -286,51 +365,69 @@ class BrowserWindow extends EventEmitter {
|
|
|
286
365
|
setAutoHideMenuBar() {}
|
|
287
366
|
|
|
288
367
|
isFullScreen() {
|
|
289
|
-
return
|
|
368
|
+
return this._fullScreen;
|
|
290
369
|
}
|
|
291
370
|
|
|
292
|
-
setFullScreen() {
|
|
293
|
-
|
|
371
|
+
setFullScreen(value = true) {
|
|
372
|
+
this._fullScreen = Boolean(value);
|
|
373
|
+
if (!this._sendHostCommand({ command: 'fullscreen', value: this._fullScreen })) {
|
|
374
|
+
console.warn('[AtomJS] Native fullscreen switching is not supported by the current platform host.');
|
|
375
|
+
}
|
|
294
376
|
}
|
|
295
377
|
|
|
296
378
|
maximize() {
|
|
297
|
-
|
|
379
|
+
this._maximized = true;
|
|
380
|
+
if (!this._sendHostCommand({ command: 'maximize' })) {
|
|
381
|
+
console.warn('[AtomJS] Native maximize is not supported by the current platform host.');
|
|
382
|
+
}
|
|
298
383
|
}
|
|
299
384
|
|
|
300
|
-
unmaximize() {
|
|
385
|
+
unmaximize() {
|
|
386
|
+
this._maximized = false;
|
|
387
|
+
this._sendHostCommand({ command: 'unmaximize' });
|
|
388
|
+
}
|
|
301
389
|
|
|
302
390
|
isMaximized() {
|
|
303
|
-
return
|
|
391
|
+
return this._maximized;
|
|
304
392
|
}
|
|
305
393
|
|
|
306
394
|
minimize() {
|
|
307
|
-
|
|
395
|
+
this._minimized = true;
|
|
396
|
+
if (!this._sendHostCommand({ command: 'minimize' })) {
|
|
397
|
+
console.warn('[AtomJS] Native minimize is not supported by the current platform host.');
|
|
398
|
+
}
|
|
308
399
|
}
|
|
309
400
|
|
|
310
|
-
restore() {
|
|
401
|
+
restore() {
|
|
402
|
+
this._minimized = false;
|
|
403
|
+
this._sendHostCommand({ command: 'restore' });
|
|
404
|
+
}
|
|
311
405
|
|
|
312
406
|
isMinimized() {
|
|
313
|
-
return
|
|
407
|
+
return this._minimized;
|
|
314
408
|
}
|
|
315
409
|
|
|
316
410
|
setSize(width, height) {
|
|
317
|
-
this.
|
|
318
|
-
this.options.height = Number(height);
|
|
319
|
-
console.warn('[AtomJS] Changing the native window size after creation is not yet supported.');
|
|
411
|
+
this.setBounds({ width, height });
|
|
320
412
|
}
|
|
321
413
|
|
|
322
414
|
getSize() {
|
|
323
|
-
return [this.
|
|
415
|
+
return [this._bounds.width, this._bounds.height];
|
|
324
416
|
}
|
|
325
417
|
|
|
326
418
|
setBounds(bounds) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
419
|
+
const next = { ...this._bounds };
|
|
420
|
+
for (const key of ['x', 'y', 'width', 'height']) {
|
|
421
|
+
if (Number.isFinite(Number(bounds?.[key]))) next[key] = Number(bounds[key]);
|
|
422
|
+
}
|
|
423
|
+
this._bounds = next;
|
|
424
|
+
if (!this._sendHostCommand({ command: 'set-bounds', bounds: next })) {
|
|
425
|
+
console.warn('[AtomJS] Changing native bounds is not supported by the current platform host.');
|
|
426
|
+
}
|
|
330
427
|
}
|
|
331
428
|
|
|
332
429
|
getBounds() {
|
|
333
|
-
return {
|
|
430
|
+
return { ...this._bounds };
|
|
334
431
|
}
|
|
335
432
|
|
|
336
433
|
reload() {
|
|
@@ -372,7 +469,7 @@ function attachHostOutput(win, child) {
|
|
|
372
469
|
function handleHostLine(win, line) {
|
|
373
470
|
const prefix = '__ATOMJS_EVENT__';
|
|
374
471
|
if (!line.startsWith(prefix)) {
|
|
375
|
-
if (line) process.stdout.write(line
|
|
472
|
+
if (line) process.stdout.write(`${line}\n`);
|
|
376
473
|
return;
|
|
377
474
|
}
|
|
378
475
|
try {
|
package/src/dialog.cjs
CHANGED
|
@@ -2,10 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
const { execFile } = require('node:child_process');
|
|
4
4
|
const { promisify } = require('node:util');
|
|
5
|
+
const app = require('./app.cjs');
|
|
6
|
+
const { getNativeHost } = require('./native-host.cjs');
|
|
7
|
+
|
|
5
8
|
const execFileAsync = promisify(execFile);
|
|
6
9
|
|
|
7
|
-
async function showOpenDialog(
|
|
8
|
-
const options = normalizeOptions(
|
|
10
|
+
async function showOpenDialog(browserWindowOrOptions, maybeOptions) {
|
|
11
|
+
const options = normalizeOptions(browserWindowOrOptions, maybeOptions);
|
|
12
|
+
|
|
13
|
+
if (process.platform === 'darwin') {
|
|
14
|
+
return macOSRequest('dialog-open', options);
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
try {
|
|
10
18
|
const filePaths = await openDialogForPlatform(options);
|
|
11
19
|
return { canceled: filePaths.length === 0, filePaths };
|
|
@@ -15,8 +23,17 @@ async function showOpenDialog(_browserWindowOrOptions, maybeOptions) {
|
|
|
15
23
|
}
|
|
16
24
|
}
|
|
17
25
|
|
|
18
|
-
async function showSaveDialog(
|
|
19
|
-
const options = normalizeOptions(
|
|
26
|
+
async function showSaveDialog(browserWindowOrOptions, maybeOptions) {
|
|
27
|
+
const options = normalizeOptions(browserWindowOrOptions, maybeOptions);
|
|
28
|
+
|
|
29
|
+
if (process.platform === 'darwin') {
|
|
30
|
+
const result = await macOSRequest('dialog-save', options);
|
|
31
|
+
return {
|
|
32
|
+
canceled: Boolean(result.canceled),
|
|
33
|
+
filePath: result.filePath || undefined
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
20
37
|
try {
|
|
21
38
|
const filePath = await saveDialogForPlatform(options);
|
|
22
39
|
return { canceled: !filePath, filePath: filePath || undefined };
|
|
@@ -26,19 +43,21 @@ async function showSaveDialog(_browserWindowOrOptions, maybeOptions) {
|
|
|
26
43
|
}
|
|
27
44
|
}
|
|
28
45
|
|
|
29
|
-
async function showMessageBox(
|
|
30
|
-
const options = normalizeOptions(
|
|
46
|
+
async function showMessageBox(browserWindowOrOptions, maybeOptions) {
|
|
47
|
+
const options = normalizeOptions(browserWindowOrOptions, maybeOptions);
|
|
31
48
|
const title = options.title || 'AtomJS';
|
|
32
49
|
const message = options.message || '';
|
|
33
50
|
|
|
51
|
+
if (process.platform === 'darwin') {
|
|
52
|
+
return macOSRequest('dialog-message', { ...options, title, message });
|
|
53
|
+
}
|
|
54
|
+
|
|
34
55
|
if (process.platform === 'win32') {
|
|
35
56
|
const script = [
|
|
36
57
|
'Add-Type -AssemblyName PresentationFramework',
|
|
37
58
|
`[System.Windows.MessageBox]::Show(${psQuote(message)}, ${psQuote(title)}) | Out-Null`
|
|
38
59
|
].join('; ');
|
|
39
60
|
await execFileAsync('powershell.exe', ['-NoProfile', '-STA', '-Command', script]);
|
|
40
|
-
} else if (process.platform === 'darwin') {
|
|
41
|
-
await execFileAsync('osascript', ['-e', `display dialog ${appleQuote(message)} with title ${appleQuote(title)} buttons {"OK"} default button "OK"`]);
|
|
42
61
|
} else {
|
|
43
62
|
await execFileAsync('zenity', ['--info', `--title=${title}`, `--text=${message}`]);
|
|
44
63
|
}
|
|
@@ -46,6 +65,11 @@ async function showMessageBox(_browserWindowOrOptions, maybeOptions) {
|
|
|
46
65
|
return { response: 0, checkboxChecked: false };
|
|
47
66
|
}
|
|
48
67
|
|
|
68
|
+
async function macOSRequest(command, options) {
|
|
69
|
+
const host = getNativeHost(app.getName());
|
|
70
|
+
return host.request({ command, options });
|
|
71
|
+
}
|
|
72
|
+
|
|
49
73
|
async function openDialogForPlatform(options) {
|
|
50
74
|
const multiple = Array.isArray(options.properties) && options.properties.includes('multiSelections');
|
|
51
75
|
|
|
@@ -63,15 +87,6 @@ async function openDialogForPlatform(options) {
|
|
|
63
87
|
return splitLines(stdout);
|
|
64
88
|
}
|
|
65
89
|
|
|
66
|
-
if (process.platform === 'darwin') {
|
|
67
|
-
const prompt = options.title || 'Choose a file';
|
|
68
|
-
const script = multiple
|
|
69
|
-
? `set chosenFiles to choose file with prompt ${appleQuote(prompt)} with multiple selections allowed\nset output to ""\nrepeat with f in chosenFiles\nset output to output & POSIX path of f & linefeed\nend repeat\nreturn output`
|
|
70
|
-
: `POSIX path of (choose file with prompt ${appleQuote(prompt)})`;
|
|
71
|
-
const { stdout } = await execFileAsync('osascript', ['-e', script], { encoding: 'utf8' });
|
|
72
|
-
return splitLines(stdout);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
90
|
const args = ['--file-selection'];
|
|
76
91
|
if (options.title) args.push(`--title=${options.title}`);
|
|
77
92
|
if (options.defaultPath) args.push(`--filename=${options.defaultPath}`);
|
|
@@ -94,13 +109,6 @@ async function saveDialogForPlatform(options) {
|
|
|
94
109
|
return stdout.trim();
|
|
95
110
|
}
|
|
96
111
|
|
|
97
|
-
if (process.platform === 'darwin') {
|
|
98
|
-
const defaultName = options.defaultPath ? String(options.defaultPath).split(/[\\/]/).pop() : 'Untitled';
|
|
99
|
-
const script = `POSIX path of (choose file name with prompt ${appleQuote(options.title || 'Save file')} default name ${appleQuote(defaultName)})`;
|
|
100
|
-
const { stdout } = await execFileAsync('osascript', ['-e', script], { encoding: 'utf8' });
|
|
101
|
-
return stdout.trim();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
112
|
const args = ['--file-selection', '--save', '--confirm-overwrite'];
|
|
105
113
|
if (options.title) args.push(`--title=${options.title}`);
|
|
106
114
|
if (options.defaultPath) args.push(`--filename=${options.defaultPath}`);
|
|
@@ -134,8 +142,4 @@ function psQuote(value) {
|
|
|
134
142
|
return `'${String(value).replace(/'/g, "''")}'`;
|
|
135
143
|
}
|
|
136
144
|
|
|
137
|
-
function appleQuote(value) {
|
|
138
|
-
return JSON.stringify(String(value));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
145
|
module.exports = { showOpenDialog, showSaveDialog, showMessageBox };
|