react-x11 2.5.0 → 2.6.1
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 +4 -3
- package/src/Reconciler.js +6 -0
- package/src/cocoa/app.js +240 -31
- package/src/cocoa/context2d.js +287 -26
- package/src/cocoa/panehost.js +20 -2
- package/src/cocoa/panewindow.js +61 -9
- package/src/cocoa/presenter.js +191 -16
- package/src/cocoa/surface.js +9 -3
- package/src/cocoa/window.js +123 -13
- package/src/events.js +23 -5
- package/src/index.d.ts +25 -0
- package/src/nodes.js +849 -138
- package/src/paintcache.js +71 -11
- package/src/screens.js +39 -4
- package/src/svgnodes.js +4 -3
- package/src/types/elements.d.ts +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-x11",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"description": "react renderer with X11 as a target",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -66,7 +66,8 @@
|
|
|
66
66
|
"docs:dev": "npm --prefix website start",
|
|
67
67
|
"docs:build": "npm --prefix website run build",
|
|
68
68
|
"docs:test": "npm --prefix website test",
|
|
69
|
-
"bench:presenters": "node --import tsx scripts/bench/presenters.js"
|
|
69
|
+
"bench:presenters": "node --import tsx scripts/bench/presenters.js",
|
|
70
|
+
"bench:touched": "node scripts/bench/touched.js"
|
|
70
71
|
},
|
|
71
72
|
"repository": {
|
|
72
73
|
"type": "git",
|
|
@@ -90,7 +91,7 @@
|
|
|
90
91
|
"yoga-layout": "^3.2.1"
|
|
91
92
|
},
|
|
92
93
|
"optionalDependencies": {
|
|
93
|
-
"@windowkit/appkit": "^0.
|
|
94
|
+
"@windowkit/appkit": "^0.4.0",
|
|
94
95
|
"dbus-native": "^0.15.1",
|
|
95
96
|
"x11-dri": "^0.7.0"
|
|
96
97
|
},
|
package/src/Reconciler.js
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
flushWindowMaps,
|
|
32
32
|
flushWindowRestacks,
|
|
33
33
|
windowAttributes,
|
|
34
|
+
setTextStripBelow,
|
|
34
35
|
} from './nodes.js';
|
|
35
36
|
import { hasDropProps } from './dnd.js';
|
|
36
37
|
import { AppProvider } from './appcontext.js';
|
|
@@ -790,6 +791,11 @@ export async function createRoot(options = {}) {
|
|
|
790
791
|
// anything from disk.
|
|
791
792
|
beginCompose(app, rest.compose);
|
|
792
793
|
|
|
794
|
+
// Under what size a paragraph is painted as a strip of its ink instead
|
|
795
|
+
// of as glyphs (nodes.js, `TextNode._paintsStrip`): six logical pixels by
|
|
796
|
+
// default, 0 for glyphs at every size.
|
|
797
|
+
setTextStripBelow(app, rest.textStripBelow);
|
|
798
|
+
|
|
793
799
|
// Whether a subtree coming back out of hiding — a `<Suspense>` boundary
|
|
794
800
|
// resolving, an `<Activity>` shown again — takes the keyboard back with it
|
|
795
801
|
// (src/events.js, `subtreeRevealed`). On by default; `false` is the
|
package/src/cocoa/app.js
CHANGED
|
@@ -29,7 +29,18 @@ import { CocoaWindow } from './window.js';
|
|
|
29
29
|
import { decodeKey, modifierMask } from './keymap.js';
|
|
30
30
|
import { loadNative } from './native.js';
|
|
31
31
|
|
|
32
|
+
// The frame interval: how often a scheduled frame may paint, in ms. The
|
|
33
|
+
// default is the period of the display the window is on — `listScreens`
|
|
34
|
+
// reports each panel's `fps` (NSScreen.maximumFramesPerSecond, bridge 0.4),
|
|
35
|
+
// so a 120Hz panel paints every 8.3ms and a 60Hz monitor every 16.7 —
|
|
36
|
+
// and `createRoot({ cocoa: { frameInterval } })` overrides it for every
|
|
37
|
+
// window. 16 stands in where the OS cannot say (`fps: 0`) and under a
|
|
38
|
+
// bridge that does not report it: a 60Hz floor every Mac clears.
|
|
32
39
|
const RAF_INTERVAL_MS = 16;
|
|
40
|
+
const PUMP_INTERVAL_MS = 8;
|
|
41
|
+
// How early a pump tick may take a frame that is not quite due, in ms — the
|
|
42
|
+
// drift of a timer, not a fraction of the pump (`_frameDue`).
|
|
43
|
+
const FRAME_SLACK_MS = 1;
|
|
33
44
|
|
|
34
45
|
export class CocoaApp {
|
|
35
46
|
constructor(native, options = {}) {
|
|
@@ -37,8 +48,18 @@ export class CocoaApp {
|
|
|
37
48
|
this.options = options;
|
|
38
49
|
this._windows = new Map(); // windowNumber -> CocoaWindow
|
|
39
50
|
this._grabWindow = null;
|
|
40
|
-
this._rafQueue = [];
|
|
51
|
+
this._rafQueue = []; // [{ cb, wnd }]
|
|
52
|
+
// the app's own frame clock, for a frame no window owns (a pane's)
|
|
41
53
|
this._rafLast = 0;
|
|
54
|
+
// an explicit interval applies to every window; null means each
|
|
55
|
+
// window paces itself on its own display (`frameIntervalFor`)
|
|
56
|
+
this._frameInterval = options.cocoa?.frameInterval ?? null;
|
|
57
|
+
this._pumpInterval = PUMP_INTERVAL_MS;
|
|
58
|
+
// the one-shot that runs a frame due between two pump ticks, and when
|
|
59
|
+
// it is due (`_armFrameTimer`)
|
|
60
|
+
this._frameTimer = null;
|
|
61
|
+
this._frameTimerAt = 0;
|
|
62
|
+
this._geometryFlushQueued = false;
|
|
42
63
|
this._shadowStale = new Set();
|
|
43
64
|
this._pump = null;
|
|
44
65
|
this._closed = false;
|
|
@@ -243,6 +264,35 @@ export class CocoaApp {
|
|
|
243
264
|
this._windows.set(wnd.windowNumber, wnd);
|
|
244
265
|
}
|
|
245
266
|
|
|
267
|
+
/**
|
|
268
|
+
* How often `wnd` may paint, in ms: the explicit `frameInterval` when the
|
|
269
|
+
* root was given one, else the period of the screen under the window's
|
|
270
|
+
* centre — the display's own rate is the only honest cadence, and on a
|
|
271
|
+
* desk with a 120Hz panel and a 60Hz monitor the two windows differ. A
|
|
272
|
+
* window on no screen (mid-drag between two, or off the edge) and a
|
|
273
|
+
* screen the OS reports no rate for take the primary's, then 16ms.
|
|
274
|
+
*/
|
|
275
|
+
frameIntervalFor(wnd) {
|
|
276
|
+
if (this._frameInterval != null) return this._frameInterval;
|
|
277
|
+
const s = this.scale;
|
|
278
|
+
const screens = this._screens ?? [];
|
|
279
|
+
let screen = null;
|
|
280
|
+
if (wnd) {
|
|
281
|
+
const cx = (wnd.x + wnd.width / 2) / s;
|
|
282
|
+
const cy = (wnd.y + wnd.height / 2) / s;
|
|
283
|
+
screen =
|
|
284
|
+
screens.find(
|
|
285
|
+
(sc) =>
|
|
286
|
+
cx >= sc.x &&
|
|
287
|
+
cx < sc.x + sc.width &&
|
|
288
|
+
cy >= sc.y &&
|
|
289
|
+
cy < sc.y + sc.height,
|
|
290
|
+
) ?? null;
|
|
291
|
+
}
|
|
292
|
+
const fps = screen?.fps || screens[0]?.fps || 0;
|
|
293
|
+
return fps > 0 ? 1000 / fps : RAF_INTERVAL_MS;
|
|
294
|
+
}
|
|
295
|
+
|
|
246
296
|
/**
|
|
247
297
|
* The pane's end of the frame channel (childmain hands it over,
|
|
248
298
|
* feature-detected so the X11 pane path never notices): geometry and
|
|
@@ -277,8 +327,9 @@ export class CocoaApp {
|
|
|
277
327
|
|
|
278
328
|
// --- the pump ------------------------------------------------------------
|
|
279
329
|
|
|
280
|
-
start({ pumpInterval =
|
|
330
|
+
start({ pumpInterval = PUMP_INTERVAL_MS } = {}) {
|
|
281
331
|
if (this._pump) return;
|
|
332
|
+
this._pumpInterval = pumpInterval;
|
|
282
333
|
const native = this._native;
|
|
283
334
|
// A pane process has no NSApplication to pump — no windows, no events,
|
|
284
335
|
// no dock presence. Its loop is frames and presents only.
|
|
@@ -292,6 +343,7 @@ export class CocoaApp {
|
|
|
292
343
|
native.initApp();
|
|
293
344
|
native.setBackendEventCallback((ev) => this._route(ev));
|
|
294
345
|
this._pump = setInterval(() => {
|
|
346
|
+
this._endLiveResizes();
|
|
295
347
|
native.pump2(); // flushes the previous tick's CATransaction
|
|
296
348
|
if (this._shadowStale.size) {
|
|
297
349
|
for (const wnd of this._shadowStale) {
|
|
@@ -304,27 +356,129 @@ export class CocoaApp {
|
|
|
304
356
|
}, pumpInterval);
|
|
305
357
|
}
|
|
306
358
|
|
|
307
|
-
|
|
308
|
-
|
|
359
|
+
/**
|
|
360
|
+
* A pump tick means no modal loop owns the thread, so no window is being
|
|
361
|
+
* resized live right now: the flag the delegate set on the last tick of a
|
|
362
|
+
* drag comes off here, ahead of the frames that tick, and the catch-up
|
|
363
|
+
* frame a deferred layout owes (nodes.js) runs on this very tick.
|
|
364
|
+
*/
|
|
365
|
+
_endLiveResizes() {
|
|
366
|
+
for (const wnd of this._windows.values()) wnd.liveResizing = false;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
_requestFrame(cb, wnd = null) {
|
|
370
|
+
this._rafQueue.push({ cb, wnd });
|
|
309
371
|
return this._rafQueue.length;
|
|
310
372
|
}
|
|
311
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Whether `clock` — a window, or the app for a frame no window owns —
|
|
376
|
+
* is due a frame at `now`, and if so, stamps it.
|
|
377
|
+
*
|
|
378
|
+
* The clock keeps the display's period, not the pump's: a frame that ran
|
|
379
|
+
* is stamped one interval after the last one was due rather than at the
|
|
380
|
+
* moment it happened to run, so a tick that took it a little early or a
|
|
381
|
+
* timer that fired a little late moves nothing — the next frame is still
|
|
382
|
+
* due where the display's next refresh is. A frame that arrives more than
|
|
383
|
+
* an interval late (a slow one) re-anchors the clock instead of owing the
|
|
384
|
+
* frames it missed.
|
|
385
|
+
*
|
|
386
|
+
* The gate is a millisecond of slack under the interval, which is a
|
|
387
|
+
* timer's drift and no more. It used to be half a pump, so that a frame
|
|
388
|
+
* would land on the first tick at or after the interval instead of
|
|
389
|
+
* alternating between the tick before and the one after; that quantized
|
|
390
|
+
* the period to the pump — a 75Hz monitor's 13.3ms gate of 9.3 fell on
|
|
391
|
+
* the 16ms tick, and the window painted at 62.5fps. A frame due between
|
|
392
|
+
* two ticks now gets a timer of its own (`_armFrameTimer`), and the
|
|
393
|
+
* period is the display's whatever the pump's cadence.
|
|
394
|
+
*/
|
|
395
|
+
_frameDue(clock, now) {
|
|
396
|
+
const interval =
|
|
397
|
+
clock === this ? this.frameIntervalFor(null) : clock._frameInterval;
|
|
398
|
+
const since = now - clock._rafLast;
|
|
399
|
+
if (since < interval - FRAME_SLACK_MS) return false;
|
|
400
|
+
clock._rafLast = since < 2 * interval ? clock._rafLast + interval : now;
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** How long until `clock` is due, from `now`. */
|
|
405
|
+
_frameWait(clock, now) {
|
|
406
|
+
const interval =
|
|
407
|
+
clock === this ? this.frameIntervalFor(null) : clock._frameInterval;
|
|
408
|
+
return interval - FRAME_SLACK_MS - (now - clock._rafLast);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* A frame that falls between two pump ticks gets a tick of its own: a
|
|
413
|
+
* one-shot timer at the moment it is due, which runs the frame queue and
|
|
414
|
+
* presents what it painted. Only when the next pump tick would be late
|
|
415
|
+
* for it — a frame due after that tick waits for the tick, which decides
|
|
416
|
+
* again. One timer at a time, at the soonest of what is owed; a pump tick
|
|
417
|
+
* that comes first runs whatever is due and re-arms for the rest.
|
|
418
|
+
*
|
|
419
|
+
* The timer costs the frame nothing the tick does not: a present commits
|
|
420
|
+
* its own transaction (the bridge flushes on the flip), so what is
|
|
421
|
+
* painted here is on glass without waiting for the next pump. What the
|
|
422
|
+
* tick alone still does is pump AppKit's events, which is what
|
|
423
|
+
* `pumpInterval` stays the cadence of.
|
|
424
|
+
*/
|
|
425
|
+
_armFrameTimer(wait, now) {
|
|
426
|
+
if (!(wait > 0 && wait < this._pumpInterval)) return;
|
|
427
|
+
const at = now + wait;
|
|
428
|
+
if (this._frameTimer) {
|
|
429
|
+
if (at >= this._frameTimerAt) return;
|
|
430
|
+
clearTimeout(this._frameTimer);
|
|
431
|
+
}
|
|
432
|
+
this._frameTimerAt = at;
|
|
433
|
+
this._frameTimer = setTimeout(
|
|
434
|
+
() => {
|
|
435
|
+
this._frameTimer = null;
|
|
436
|
+
if (this._closed) return;
|
|
437
|
+
this._tickFrames();
|
|
438
|
+
this._presentAll();
|
|
439
|
+
},
|
|
440
|
+
Math.max(1, Math.round(wait)),
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
312
444
|
_tickFrames() {
|
|
313
445
|
if (!this._rafQueue.length) return;
|
|
314
|
-
const now =
|
|
315
|
-
|
|
316
|
-
|
|
446
|
+
const now = performance.now();
|
|
447
|
+
// Each window keeps its own clock, so a window on a 120Hz panel paints
|
|
448
|
+
// every refresh while one on a 60Hz monitor paints every other pump
|
|
449
|
+
// tick. Decided once per clock per tick: every frame a window queued
|
|
450
|
+
// runs when it is due, not just the first.
|
|
451
|
+
const due = new Map();
|
|
317
452
|
const queue = this._rafQueue;
|
|
318
453
|
this._rafQueue = [];
|
|
319
|
-
|
|
454
|
+
let soonest = Infinity;
|
|
455
|
+
for (const entry of queue) {
|
|
456
|
+
const clock = entry.wnd ?? this;
|
|
457
|
+
if (!due.has(clock)) due.set(clock, this._frameDue(clock, now));
|
|
458
|
+
if (!due.get(clock)) {
|
|
459
|
+
this._rafQueue.push(entry);
|
|
460
|
+
soonest = Math.min(soonest, this._frameWait(clock, now));
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
// A window nobody can see owes no frame: its callback waits here until
|
|
464
|
+
// it is back on glass, and the damage it answers accumulates on the
|
|
465
|
+
// node — one catch-up frame then, instead of a full paint per tick
|
|
466
|
+
// into a backing store no one reads. `_visible` is the window's own
|
|
467
|
+
// rule (mapped, not ordered out, not miniaturized, not entirely
|
|
468
|
+
// behind another application's window).
|
|
469
|
+
if (entry.wnd && !entry.wnd._visible()) {
|
|
470
|
+
this._rafQueue.push(entry);
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
320
473
|
try {
|
|
321
|
-
cb(now);
|
|
474
|
+
entry.cb(now);
|
|
322
475
|
} catch (err) {
|
|
323
476
|
queueMicrotask(() => {
|
|
324
477
|
throw err;
|
|
325
478
|
});
|
|
326
479
|
}
|
|
327
480
|
}
|
|
481
|
+
this._armFrameTimer(soonest, now);
|
|
328
482
|
}
|
|
329
483
|
|
|
330
484
|
_presentAll() {
|
|
@@ -366,6 +520,8 @@ export class CocoaApp {
|
|
|
366
520
|
return this._routeFocus(ev, 'focus');
|
|
367
521
|
case 'window-blur':
|
|
368
522
|
return this._routeFocus(ev, 'blur');
|
|
523
|
+
case 'window-occlusion':
|
|
524
|
+
return this._routeOcclusion(ev);
|
|
369
525
|
case 'menu-activate':
|
|
370
526
|
this._activeGlobalMenu?.activate(ev.id);
|
|
371
527
|
return this._afterInput();
|
|
@@ -459,6 +615,13 @@ export class CocoaApp {
|
|
|
459
615
|
smooth: Boolean(ev.precise),
|
|
460
616
|
source: ev.precise ? 'valuator' : 'button',
|
|
461
617
|
});
|
|
618
|
+
// Painted now, like a press. On X11 the wheel is paced on the frame
|
|
619
|
+
// clock because ntk coalesces a touchpad's dozens of reports per frame
|
|
620
|
+
// into one event; AppKit already delivers scroll events at the
|
|
621
|
+
// display's rate, so answering each one is answering once per refresh
|
|
622
|
+
// — and answering it on the next frame tick instead was a 15ms median
|
|
623
|
+
// between the notch and the scroll, most of a refresh period of nothing.
|
|
624
|
+
this._afterInput();
|
|
462
625
|
}
|
|
463
626
|
|
|
464
627
|
_routeKey(ev) {
|
|
@@ -499,9 +662,31 @@ export class CocoaApp {
|
|
|
499
662
|
// handler returns, and the pump that would paint it is the thing the
|
|
500
663
|
// modal loop stalled. A second flush queued BEHIND that commit is what
|
|
501
664
|
// lets an open menu resize with the drag instead of on release.
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
665
|
+
//
|
|
666
|
+
// One, not one per event: inside the modal loop no microtask runs until
|
|
667
|
+
// the drag ends, so a drag's every tick queued another — and they all
|
|
668
|
+
// ran on the release, each finding the frame the previous one had just
|
|
669
|
+
// paid. Coalesced, the release owes at most one flush.
|
|
670
|
+
if (!this._geometryFlushQueued) {
|
|
671
|
+
this._geometryFlushQueued = true;
|
|
672
|
+
queueMicrotask(() => {
|
|
673
|
+
this._geometryFlushQueued = false;
|
|
674
|
+
if (!this._closed) this._afterInput();
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* `windowDidChangeOcclusionState`: `visible` is "some pixel of the window
|
|
681
|
+
* is on glass". Off, the window's frames wait in the queue and its
|
|
682
|
+
* present waits with them (`CocoaWindow._visible`); on, the next pump
|
|
683
|
+
* tick runs the catch-up frame and puts it on glass — no early flush,
|
|
684
|
+
* since nothing was asked for and the pump is at most one interval away.
|
|
685
|
+
*/
|
|
686
|
+
_routeOcclusion(ev) {
|
|
687
|
+
const wnd = this._window(ev);
|
|
688
|
+
if (!wnd || wnd.destroyed) return;
|
|
689
|
+
wnd._occluded = ev.visible === false;
|
|
505
690
|
}
|
|
506
691
|
|
|
507
692
|
_routeClose(ev) {
|
|
@@ -528,6 +713,8 @@ export class CocoaApp {
|
|
|
528
713
|
this._closed = true;
|
|
529
714
|
if (this._pump) clearInterval(this._pump);
|
|
530
715
|
this._pump = null;
|
|
716
|
+
if (this._frameTimer) clearTimeout(this._frameTimer);
|
|
717
|
+
this._frameTimer = null;
|
|
531
718
|
this._cocoaGL?.destroy();
|
|
532
719
|
this._cocoaGL = null;
|
|
533
720
|
this._native.setBackendEventCallback(null);
|
|
@@ -536,6 +723,46 @@ export class CocoaApp {
|
|
|
536
723
|
}
|
|
537
724
|
}
|
|
538
725
|
|
|
726
|
+
/**
|
|
727
|
+
* `listScreens()` turned into the screen layout `src/screens.js` publishes.
|
|
728
|
+
*
|
|
729
|
+
* **One scale for every screen, and it is the app's.** macOS lays all the
|
|
730
|
+
* displays out in a single global point space, and `app.scale` is this
|
|
731
|
+
* app's points-to-device-pixels factor for the whole of it — window
|
|
732
|
+
* origins, event coordinates, these rects. Converting a 1x external
|
|
733
|
+
* display by *its own* 1 while windows on it still report `points * 2`
|
|
734
|
+
* would put the monitor somewhere no window ever is, and `monitorAt()`
|
|
735
|
+
* would answer with the wrong head. (What backing scale a window on a
|
|
736
|
+
* mixed-DPI desk should raster at is a real and separate question; the
|
|
737
|
+
* layout is not where it is answered.)
|
|
738
|
+
*
|
|
739
|
+
* **A usable rect per monitor.** `NSScreen.visibleFrame` is per screen —
|
|
740
|
+
* that display's own menu bar and Dock taken off — so each monitor carries
|
|
741
|
+
* its `visible` and `usable()` takes it as a rect. Publishing only the
|
|
742
|
+
* primary's, the way `_NET_WORKAREA` forces on X11, applied the primary's
|
|
743
|
+
* *width* as a bound to every other head: a second display wider than the
|
|
744
|
+
* built-in had its right edge pulled in by the difference, and every
|
|
745
|
+
* anchored popup that reached past it was clamped back (issue #453).
|
|
746
|
+
*/
|
|
747
|
+
export function screenLayout(screens, scale) {
|
|
748
|
+
const rect = (r) => ({
|
|
749
|
+
x: Math.round(r.x * scale),
|
|
750
|
+
y: Math.round(r.y * scale),
|
|
751
|
+
width: Math.round(r.width * scale),
|
|
752
|
+
height: Math.round(r.height * scale),
|
|
753
|
+
});
|
|
754
|
+
const primary = screens?.[0];
|
|
755
|
+
return {
|
|
756
|
+
monitors: (screens ?? []).map((screen) => ({
|
|
757
|
+
...rect(screen),
|
|
758
|
+
...(screen.visible ? { visible: rect(screen.visible) } : null),
|
|
759
|
+
})),
|
|
760
|
+
// Still published for `useScreens().workArea`, which is one rect for
|
|
761
|
+
// the desktop by definition; the primary's is the closest macOS has.
|
|
762
|
+
workArea: primary?.visible ? rect(primary.visible) : null,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
|
|
539
766
|
/**
|
|
540
767
|
* Build the app and seed the platform stores the way the mock seeds them —
|
|
541
768
|
* `beginScale`/`beginScreens`/`beginCompositing` find a session already
|
|
@@ -546,25 +773,7 @@ export async function createCocoaApp(options = {}) {
|
|
|
546
773
|
const app = new CocoaApp(native, options);
|
|
547
774
|
|
|
548
775
|
setScaleForTests(app, app.scale, 'cocoa');
|
|
549
|
-
|
|
550
|
-
const monitors = app._screens.map((screen) => ({
|
|
551
|
-
x: Math.round(screen.x * s),
|
|
552
|
-
y: Math.round(screen.y * s),
|
|
553
|
-
width: Math.round(screen.width * s),
|
|
554
|
-
height: Math.round(screen.height * s),
|
|
555
|
-
}));
|
|
556
|
-
const primary = app._screens[0];
|
|
557
|
-
setScreensForTests(app, {
|
|
558
|
-
monitors,
|
|
559
|
-
workArea: primary
|
|
560
|
-
? {
|
|
561
|
-
x: Math.round(primary.visible.x * s),
|
|
562
|
-
y: Math.round(primary.visible.y * s),
|
|
563
|
-
width: Math.round(primary.visible.width * s),
|
|
564
|
-
height: Math.round(primary.visible.height * s),
|
|
565
|
-
}
|
|
566
|
-
: null,
|
|
567
|
-
});
|
|
776
|
+
setScreensForTests(app, screenLayout(app._screens, app.scale));
|
|
568
777
|
setCompositingForTests(app, true);
|
|
569
778
|
|
|
570
779
|
app.start(options.cocoa ?? {});
|