mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
test: multi monitor tests for save/restore window state (#48048)
* feat: save window state (#47425) * feat: save/restore window state * cleanup * remove constructor option * refactor: apply suggestions from code review Co-authored-by: Charles Kerr <charles@charleskerr.com> * refactor: forward declare prefservice * refactor: remove constructor option * refactor: save window state on move/resize instead of moved/resized * feat: resave window state after construction * test: add basic window save tests * test: add work area tests * test: asynchronous batching behavior * docs: add windowStateRestoreOptions to BaseWindowConstructorOptions * chore: move includes to main block * Update spec/api-browser-window-spec.ts Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * docs: update docs/api/structures/base-window-options.md Co-authored-by: Erick Zhao <erick@hotmail.ca> * fix: preserve original bounds during window state save in special modes * feat: save kiosk state in window preferences * chore: remove ts-expect-error * test: check hasCapturableScreen before running tests * test: remove multimonitor tests * test: add missing hasCapturableScreen checks before tests * docs: add blurb on saving mechanism * feat: add debounce window of 200ms to saveWindowState * docs: remove blurb until finalized * style: convert constants from snake_case to camelCase * refactor: initialize prefs_ only if window state is configured to be saved/restored * refactor: rename window states key * refactor: store in application-level Local State instead of browser context * refactor: switch to more accurate function names * fix: add dcheck for browser_process * fix: flush window state to avoid race condition * refactor: change stateId to name * refactor: change windowStateRestoreOptions to windowStatePersistence * Update docs/api/structures/base-window-options.md Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * fix: add warning when window state persistence enabled without window name * docs: lowercase capital B for consistency --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> * feat: restore window state * feat: flush display modes on show * refactor: move utility functions to common area * feat: clear window state * fix: wait for the prefs to update * test: clearWindowState extra test * test: refine clear window state tests * test: single monitor restore window tests chore: rebase on gsoc-2025 * refactor: refine clearWindowState test * fix: revert default_app back to original * docs: add comment linking AdjustBoundsToBeVisibleOnDisplay to Chromium code * fix: add correct permalink * refactor: ci friendly * fix: disable windowStatePersistence when no display * refactor: use reference instead pointer * fix: skip window state persistence for invalid/fake displays * refactor: better flag placement * test: add test to verify window state is not saved when no display * fix: restore display mode inside show() * feat: support for multimonitor tests * fix: update yarn.lock file * feat: support any resolution for new displays * feat: support display positioning * docs: multi-monitor tests * test: remove dummy test * feat: save window state (#47425) * feat: save/restore window state * cleanup * remove constructor option * refactor: apply suggestions from code review Co-authored-by: Charles Kerr <charles@charleskerr.com> * refactor: forward declare prefservice * refactor: remove constructor option * refactor: save window state on move/resize instead of moved/resized * feat: resave window state after construction * test: add basic window save tests * test: add work area tests * test: asynchronous batching behavior * docs: add windowStateRestoreOptions to BaseWindowConstructorOptions * chore: move includes to main block * Update spec/api-browser-window-spec.ts Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * docs: update docs/api/structures/base-window-options.md Co-authored-by: Erick Zhao <erick@hotmail.ca> * fix: preserve original bounds during window state save in special modes * feat: save kiosk state in window preferences * chore: remove ts-expect-error * test: check hasCapturableScreen before running tests * test: remove multimonitor tests * test: add missing hasCapturableScreen checks before tests * docs: add blurb on saving mechanism * feat: add debounce window of 200ms to saveWindowState * docs: remove blurb until finalized * style: convert constants from snake_case to camelCase * refactor: initialize prefs_ only if window state is configured to be saved/restored * refactor: rename window states key * refactor: store in application-level Local State instead of browser context * refactor: switch to more accurate function names * fix: add dcheck for browser_process * fix: flush window state to avoid race condition * refactor: change stateId to name * refactor: change windowStateRestoreOptions to windowStatePersistence * Update docs/api/structures/base-window-options.md Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * fix: add warning when window state persistence enabled without window name * docs: lowercase capital B for consistency --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> * test: clear sharedUserPath before and test * refactor: hasInvalidDisplay function * debug: add display info logging for CI * fix: do not save/restore when window is 0x0 * test: support for multimonitor tests (#47911) * test: support for multimonitor tests * fix: update yarn.lock file * test: support any resolution for new displays * test: support display positioning * docs: multi-monitor tests * test: remove dummy test * fix: native-addon forceCleanup * docs: add forceCleanup description * test: add two basic multi-monitor tests * fix: find the closest display for non-overlapping saved bounds * test: windowStatePersistence multi-monitor tests * docs: add note on display APIs in CI * fix: remove duplicate destroy registration * feat: enforce unique window names across BaseWindow and BrowserWindow (#47764) * feat: save window state (#47425) * feat: save/restore window state * cleanup * remove constructor option * refactor: apply suggestions from code review Co-authored-by: Charles Kerr <charles@charleskerr.com> * refactor: forward declare prefservice * refactor: remove constructor option * refactor: save window state on move/resize instead of moved/resized * feat: resave window state after construction * test: add basic window save tests * test: add work area tests * test: asynchronous batching behavior * docs: add windowStateRestoreOptions to BaseWindowConstructorOptions * chore: move includes to main block * Update spec/api-browser-window-spec.ts Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * docs: update docs/api/structures/base-window-options.md Co-authored-by: Erick Zhao <erick@hotmail.ca> * fix: preserve original bounds during window state save in special modes * feat: save kiosk state in window preferences * chore: remove ts-expect-error * test: check hasCapturableScreen before running tests * test: remove multimonitor tests * test: add missing hasCapturableScreen checks before tests * docs: add blurb on saving mechanism * feat: add debounce window of 200ms to saveWindowState * docs: remove blurb until finalized * style: convert constants from snake_case to camelCase * refactor: initialize prefs_ only if window state is configured to be saved/restored * refactor: rename window states key * refactor: store in application-level Local State instead of browser context * refactor: switch to more accurate function names * fix: add dcheck for browser_process * fix: flush window state to avoid race condition * refactor: change stateId to name * refactor: change windowStateRestoreOptions to windowStatePersistence * Update docs/api/structures/base-window-options.md Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * fix: add warning when window state persistence enabled without window name * docs: lowercase capital B for consistency --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> * feat: enforce unique window names across BaseWindow and BrowserWindow * docs: update docs for name property * fix: linter issue with symbol --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> * docs: remove inaccurate comment * fix: move expect blocks outside beforeEach * test: exclude macOS-x64 for now * test: remove invalid display test * test: remove invalid display test * feat: save window state (#47425) * feat: save/restore window state * cleanup * remove constructor option * refactor: apply suggestions from code review Co-authored-by: Charles Kerr <charles@charleskerr.com> * refactor: forward declare prefservice * refactor: remove constructor option * refactor: save window state on move/resize instead of moved/resized * feat: resave window state after construction * test: add basic window save tests * test: add work area tests * test: asynchronous batching behavior * docs: add windowStateRestoreOptions to BaseWindowConstructorOptions * chore: move includes to main block * Update spec/api-browser-window-spec.ts Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * docs: update docs/api/structures/base-window-options.md Co-authored-by: Erick Zhao <erick@hotmail.ca> * fix: preserve original bounds during window state save in special modes * feat: save kiosk state in window preferences * chore: remove ts-expect-error * test: check hasCapturableScreen before running tests * test: remove multimonitor tests * test: add missing hasCapturableScreen checks before tests * docs: add blurb on saving mechanism * feat: add debounce window of 200ms to saveWindowState * docs: remove blurb until finalized * style: convert constants from snake_case to camelCase * refactor: initialize prefs_ only if window state is configured to be saved/restored * refactor: rename window states key * refactor: store in application-level Local State instead of browser context * refactor: switch to more accurate function names * fix: add dcheck for browser_process * fix: flush window state to avoid race condition * refactor: change stateId to name * refactor: change windowStateRestoreOptions to windowStatePersistence * Update docs/api/structures/base-window-options.md Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * fix: add warning when window state persistence enabled without window name * docs: lowercase capital B for consistency --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> * test: support for multimonitor tests (#47911) * test: support for multimonitor tests * fix: update yarn.lock file * test: support any resolution for new displays * test: support display positioning * docs: multi-monitor tests * test: remove dummy test * feat: enforce unique window names across BaseWindow and BrowserWindow (#47764) * feat: save window state (#47425) * feat: save/restore window state * cleanup * remove constructor option * refactor: apply suggestions from code review Co-authored-by: Charles Kerr <charles@charleskerr.com> * refactor: forward declare prefservice * refactor: remove constructor option * refactor: save window state on move/resize instead of moved/resized * feat: resave window state after construction * test: add basic window save tests * test: add work area tests * test: asynchronous batching behavior * docs: add windowStateRestoreOptions to BaseWindowConstructorOptions * chore: move includes to main block * Update spec/api-browser-window-spec.ts Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * docs: update docs/api/structures/base-window-options.md Co-authored-by: Erick Zhao <erick@hotmail.ca> * fix: preserve original bounds during window state save in special modes * feat: save kiosk state in window preferences * chore: remove ts-expect-error * test: check hasCapturableScreen before running tests * test: remove multimonitor tests * test: add missing hasCapturableScreen checks before tests * docs: add blurb on saving mechanism * feat: add debounce window of 200ms to saveWindowState * docs: remove blurb until finalized * style: convert constants from snake_case to camelCase * refactor: initialize prefs_ only if window state is configured to be saved/restored * refactor: rename window states key * refactor: store in application-level Local State instead of browser context * refactor: switch to more accurate function names * fix: add dcheck for browser_process * fix: flush window state to avoid race condition * refactor: change stateId to name * refactor: change windowStateRestoreOptions to windowStatePersistence * Update docs/api/structures/base-window-options.md Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * fix: add warning when window state persistence enabled without window name * docs: lowercase capital B for consistency --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> * feat: enforce unique window names across BaseWindow and BrowserWindow * docs: update docs for name property * fix: linter issue with symbol --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> * feat: clear and restore window state (#47781) * feat: save window state (#47425) * feat: save/restore window state * cleanup * remove constructor option * refactor: apply suggestions from code review Co-authored-by: Charles Kerr <charles@charleskerr.com> * refactor: forward declare prefservice * refactor: remove constructor option * refactor: save window state on move/resize instead of moved/resized * feat: resave window state after construction * test: add basic window save tests * test: add work area tests * test: asynchronous batching behavior * docs: add windowStateRestoreOptions to BaseWindowConstructorOptions * chore: move includes to main block * Update spec/api-browser-window-spec.ts Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * docs: update docs/api/structures/base-window-options.md Co-authored-by: Erick Zhao <erick@hotmail.ca> * fix: preserve original bounds during window state save in special modes * feat: save kiosk state in window preferences * chore: remove ts-expect-error * test: check hasCapturableScreen before running tests * test: remove multimonitor tests * test: add missing hasCapturableScreen checks before tests * docs: add blurb on saving mechanism * feat: add debounce window of 200ms to saveWindowState * docs: remove blurb until finalized * style: convert constants from snake_case to camelCase * refactor: initialize prefs_ only if window state is configured to be saved/restored * refactor: rename window states key * refactor: store in application-level Local State instead of browser context * refactor: switch to more accurate function names * fix: add dcheck for browser_process * fix: flush window state to avoid race condition * refactor: change stateId to name * refactor: change windowStateRestoreOptions to windowStatePersistence * Update docs/api/structures/base-window-options.md Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * fix: add warning when window state persistence enabled without window name * docs: lowercase capital B for consistency --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> * feat: restore window state * feat: flush display modes on show * refactor: move utility functions to common area * feat: clear window state * fix: wait for the prefs to update * test: clearWindowState extra test * test: refine clear window state tests * test: single monitor restore window tests chore: rebase on gsoc-2025 * refactor: refine clearWindowState test * fix: revert default_app back to original * docs: add comment linking AdjustBoundsToBeVisibleOnDisplay to Chromium code * fix: add correct permalink * refactor: ci friendly * fix: disable windowStatePersistence when no display * refactor: use reference instead pointer * fix: skip window state persistence for invalid/fake displays * refactor: better flag placement * test: add test to verify window state is not saved when no display * fix: restore display mode inside show() * feat: save window state (#47425) * feat: save/restore window state * cleanup * remove constructor option * refactor: apply suggestions from code review Co-authored-by: Charles Kerr <charles@charleskerr.com> * refactor: forward declare prefservice * refactor: remove constructor option * refactor: save window state on move/resize instead of moved/resized * feat: resave window state after construction * test: add basic window save tests * test: add work area tests * test: asynchronous batching behavior * docs: add windowStateRestoreOptions to BaseWindowConstructorOptions * chore: move includes to main block * Update spec/api-browser-window-spec.ts Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * docs: update docs/api/structures/base-window-options.md Co-authored-by: Erick Zhao <erick@hotmail.ca> * fix: preserve original bounds during window state save in special modes * feat: save kiosk state in window preferences * chore: remove ts-expect-error * test: check hasCapturableScreen before running tests * test: remove multimonitor tests * test: add missing hasCapturableScreen checks before tests * docs: add blurb on saving mechanism * feat: add debounce window of 200ms to saveWindowState * docs: remove blurb until finalized * style: convert constants from snake_case to camelCase * refactor: initialize prefs_ only if window state is configured to be saved/restored * refactor: rename window states key * refactor: store in application-level Local State instead of browser context * refactor: switch to more accurate function names * fix: add dcheck for browser_process * fix: flush window state to avoid race condition * refactor: change stateId to name * refactor: change windowStateRestoreOptions to windowStatePersistence * Update docs/api/structures/base-window-options.md Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * fix: add warning when window state persistence enabled without window name * docs: lowercase capital B for consistency --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> * test: clear sharedUserPath before and test * refactor: hasInvalidDisplay function * debug: add display info logging for CI * fix: do not save/restore when window is 0x0 * test: support for multimonitor tests (#47911) * test: support for multimonitor tests * fix: update yarn.lock file * test: support any resolution for new displays * test: support display positioning * docs: multi-monitor tests * test: remove dummy test * feat: enforce unique window names across BaseWindow and BrowserWindow (#47764) * feat: save window state (#47425) * feat: save/restore window state * cleanup * remove constructor option * refactor: apply suggestions from code review Co-authored-by: Charles Kerr <charles@charleskerr.com> * refactor: forward declare prefservice * refactor: remove constructor option * refactor: save window state on move/resize instead of moved/resized * feat: resave window state after construction * test: add basic window save tests * test: add work area tests * test: asynchronous batching behavior * docs: add windowStateRestoreOptions to BaseWindowConstructorOptions * chore: move includes to main block * Update spec/api-browser-window-spec.ts Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * docs: update docs/api/structures/base-window-options.md Co-authored-by: Erick Zhao <erick@hotmail.ca> * fix: preserve original bounds during window state save in special modes * feat: save kiosk state in window preferences * chore: remove ts-expect-error * test: check hasCapturableScreen before running tests * test: remove multimonitor tests * test: add missing hasCapturableScreen checks before tests * docs: add blurb on saving mechanism * feat: add debounce window of 200ms to saveWindowState * docs: remove blurb until finalized * style: convert constants from snake_case to camelCase * refactor: initialize prefs_ only if window state is configured to be saved/restored * refactor: rename window states key * refactor: store in application-level Local State instead of browser context * refactor: switch to more accurate function names * fix: add dcheck for browser_process * fix: flush window state to avoid race condition * refactor: change stateId to name * refactor: change windowStateRestoreOptions to windowStatePersistence * Update docs/api/structures/base-window-options.md Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * fix: add warning when window state persistence enabled without window name * docs: lowercase capital B for consistency --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> * feat: enforce unique window names across BaseWindow and BrowserWindow * docs: update docs for name property * fix: linter issue with symbol --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> * test: remove invalid display test --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> Co-authored-by: Keeley Hammond <vertedinde@electronjs.org> --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> Co-authored-by: Keeley Hammond <vertedinde@electronjs.org>
This commit is contained in:
@@ -37,6 +37,21 @@ const success = virtualDisplay.destroy(displayId)
|
||||
|
||||
**Returns:** `boolean` - Success status
|
||||
|
||||
#### `virtualDisplay.forceCleanup()`
|
||||
|
||||
Performs a complete cleanup of all virtual displays and resets the macOS CoreGraphics display system.
|
||||
|
||||
It is recommended to call this before every test to prevent test failures. macOS CoreGraphics maintains an internal display ID allocation pool that can become corrupted when virtual displays are created and destroyed rapidly during testing. Without proper cleanup, subsequent display creation may fail with inconsistent display IDs, resulting in test flakiness.
|
||||
|
||||
```js @ts-nocheck
|
||||
// Recommended test pattern
|
||||
beforeEach(() => {
|
||||
virtualDisplay.forceCleanup()
|
||||
})
|
||||
```
|
||||
|
||||
**Returns:** `boolean` - Success status
|
||||
|
||||
## Display Constraints
|
||||
|
||||
### Size Limits
|
||||
|
||||
@@ -976,10 +976,25 @@ void NativeWindow::RestoreWindowState(const gin_helper::Dictionary& options) {
|
||||
|
||||
display::Screen* screen = display::Screen::GetScreen();
|
||||
DCHECK(screen);
|
||||
// GetDisplayMatching returns a fake display with 1920x1080 resolution at
|
||||
// (0,0) when no physical displays are attached.
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:ui/display/display.cc;l=184;drc=e4f1aef5f3ec30a28950d766612cc2c04c822c71
|
||||
const display::Display display = screen->GetDisplayMatching(saved_bounds);
|
||||
|
||||
// Set the primary display as the target display for restoration.
|
||||
display::Display display = screen->GetPrimaryDisplay();
|
||||
|
||||
// We identify the display with the minimal Manhattan distance to the saved
|
||||
// bounds and set it as the target display for restoration.
|
||||
int min_displacement = std::numeric_limits<int>::max();
|
||||
|
||||
for (const auto& candidate : screen->GetAllDisplays()) {
|
||||
gfx::Rect test_bounds = saved_bounds;
|
||||
test_bounds.AdjustToFit(candidate.work_area());
|
||||
int displacement = std::abs(test_bounds.x() - saved_bounds.x()) +
|
||||
std::abs(test_bounds.y() - saved_bounds.y());
|
||||
|
||||
if (displacement < min_displacement) {
|
||||
min_displacement = displacement;
|
||||
display = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip window state restoration if current display has invalid dimensions or
|
||||
// is fake. Restoring from invalid displays (0x0) or fake displays (ID 0xFF)
|
||||
|
||||
@@ -7985,6 +7985,567 @@ describe('BrowserWindow module', () => {
|
||||
w.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
// FIXME(nilayarya): Figure out why these tests fail on macOS-x64
|
||||
// virtualDisplay.create() is creating double displays on macOS-x64
|
||||
ifdescribe(process.platform === 'darwin' && process.arch === 'arm64')('multi-monitor tests', () => {
|
||||
const virtualDisplay = require('@electron-ci/virtual-display');
|
||||
const primaryDisplay = screen.getPrimaryDisplay();
|
||||
|
||||
beforeEach(() => {
|
||||
virtualDisplay.forceCleanup();
|
||||
});
|
||||
|
||||
it('should restore window bounds correctly on a secondary display', async () => {
|
||||
const targetDisplayX = primaryDisplay.bounds.x + primaryDisplay.bounds.width;
|
||||
const targetDisplayY = primaryDisplay.bounds.y;
|
||||
// We expect only the primary display to be present before the tests start
|
||||
expect(screen.getAllDisplays().length).to.equal(1);
|
||||
|
||||
// Create a new virtual target display to the right of the primary display
|
||||
const targetDisplayId = virtualDisplay.create({
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
x: targetDisplayX,
|
||||
y: targetDisplayY
|
||||
});
|
||||
|
||||
// Verify the virtual display is created correctly
|
||||
const targetDisplay = screen.getDisplayNearestPoint({ x: targetDisplayX, y: targetDisplayY });
|
||||
expect(targetDisplay.bounds.x).to.equal(targetDisplayX);
|
||||
expect(targetDisplay.bounds.y).to.equal(targetDisplayY);
|
||||
expect(targetDisplay.bounds.width).to.equal(1920);
|
||||
expect(targetDisplay.bounds.height).to.equal(1080);
|
||||
|
||||
// Bounds for the test window on the virtual target display
|
||||
const boundsOnTargetDisplay = {
|
||||
width: 400,
|
||||
height: 300,
|
||||
x: targetDisplay.workArea.x + 100,
|
||||
y: targetDisplay.workArea.y + 100
|
||||
};
|
||||
|
||||
await createAndSaveWindowState(boundsOnTargetDisplay);
|
||||
|
||||
// Restore the window state by creating a new window with the same name
|
||||
const w = new BrowserWindow({
|
||||
name: windowName,
|
||||
windowStatePersistence: true,
|
||||
show: false
|
||||
});
|
||||
|
||||
const restoredBounds = w.getBounds();
|
||||
expectBoundsEqual(restoredBounds, boundsOnTargetDisplay);
|
||||
|
||||
w.destroy();
|
||||
virtualDisplay.destroy(targetDisplayId);
|
||||
});
|
||||
|
||||
it('should restore window to a visible location when saved display no longer exists', async () => {
|
||||
const targetDisplayX = primaryDisplay.bounds.x + primaryDisplay.bounds.width;
|
||||
const targetDisplayY = primaryDisplay.bounds.y;
|
||||
// We expect only the primary display to be present before the tests start
|
||||
expect(screen.getAllDisplays().length).to.equal(1);
|
||||
|
||||
// Create a new virtual target display to the right of the primary display
|
||||
const targetDisplayId = virtualDisplay.create({
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
x: targetDisplayX,
|
||||
y: targetDisplayY
|
||||
});
|
||||
|
||||
// Verify the virtual display is created correctly - single check for targetDisplay.bounds.x
|
||||
const targetDisplay = screen.getDisplayNearestPoint({ x: targetDisplayX, y: targetDisplayY });
|
||||
expect(targetDisplay.bounds.x).to.equal(targetDisplayX);
|
||||
|
||||
// Bounds for the test window on the virtual target display
|
||||
const boundsOnTargetDisplay = {
|
||||
width: 400,
|
||||
height: 300,
|
||||
x: targetDisplay.workArea.x + 100,
|
||||
y: targetDisplay.workArea.y + 100
|
||||
};
|
||||
|
||||
// Save window state on the virtual display
|
||||
await createAndSaveWindowState(boundsOnTargetDisplay);
|
||||
|
||||
virtualDisplay.destroy(targetDisplayId);
|
||||
// Wait for the target virtual display to be destroyed
|
||||
while (screen.getAllDisplays().length > 1) await setTimeout(1000);
|
||||
|
||||
const w = new BrowserWindow({
|
||||
name: windowName,
|
||||
windowStatePersistence: true,
|
||||
show: false
|
||||
});
|
||||
|
||||
const restoredBounds = w.getBounds();
|
||||
const primaryWorkArea = primaryDisplay.workArea;
|
||||
|
||||
// Window should be fully visible on the primary display
|
||||
expect(restoredBounds.x).to.be.at.least(primaryWorkArea.x);
|
||||
expect(restoredBounds.y).to.be.at.least(primaryWorkArea.y);
|
||||
expect(restoredBounds.x + restoredBounds.width).to.be.at.most(primaryWorkArea.x + primaryWorkArea.width);
|
||||
expect(restoredBounds.y + restoredBounds.height).to.be.at.most(primaryWorkArea.y + primaryWorkArea.height);
|
||||
|
||||
// Window should maintain its original size
|
||||
expect(restoredBounds.width).to.equal(boundsOnTargetDisplay.width);
|
||||
expect(restoredBounds.height).to.equal(boundsOnTargetDisplay.height);
|
||||
|
||||
w.destroy();
|
||||
});
|
||||
|
||||
it('should fallback to nearest display when saved display no longer exists', async () => {
|
||||
const targetDisplayX = primaryDisplay.bounds.x + primaryDisplay.bounds.width;
|
||||
const targetDisplayY = primaryDisplay.bounds.y;
|
||||
// We expect only the primary display to be present before the tests start
|
||||
expect(screen.getAllDisplays().length).to.equal(1);
|
||||
|
||||
// Create first virtual display to the right of primary
|
||||
const middleDisplayId = virtualDisplay.create({
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
x: targetDisplayX,
|
||||
y: targetDisplayY
|
||||
});
|
||||
|
||||
// Create second virtual display to the right of the first (rightmost)
|
||||
const rightmostDisplayX = targetDisplayX + 1920;
|
||||
const rightmostDisplayId = virtualDisplay.create({
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
x: rightmostDisplayX,
|
||||
y: targetDisplayY
|
||||
});
|
||||
|
||||
// Verify the virtual displays are created correctly - single check for origin x values
|
||||
const middleDisplay = screen.getDisplayNearestPoint({ x: targetDisplayX, y: targetDisplayY });
|
||||
expect(middleDisplay.bounds.x).to.equal(targetDisplayX);
|
||||
|
||||
const rightmostDisplay = screen.getDisplayNearestPoint({ x: rightmostDisplayX, y: targetDisplayY });
|
||||
expect(rightmostDisplay.bounds.x).to.equal(rightmostDisplayX);
|
||||
|
||||
// Bounds for the test window on the rightmost display
|
||||
const boundsOnRightmostDisplay = {
|
||||
width: 400,
|
||||
height: 300,
|
||||
x: rightmostDisplay.workArea.x + 100,
|
||||
y: rightmostDisplay.workArea.y + 100
|
||||
};
|
||||
|
||||
// Save window state on the rightmost display
|
||||
await createAndSaveWindowState(boundsOnRightmostDisplay);
|
||||
|
||||
// Destroy the rightmost display (where window was saved)
|
||||
virtualDisplay.destroy(rightmostDisplayId);
|
||||
// Wait for the rightmost display to be destroyed
|
||||
while (screen.getAllDisplays().length > 2) await setTimeout(1000);
|
||||
|
||||
const w = new BrowserWindow({
|
||||
name: windowName,
|
||||
windowStatePersistence: true,
|
||||
show: false
|
||||
});
|
||||
|
||||
const restoredBounds = w.getBounds();
|
||||
|
||||
// Window should be restored on the middle display (nearest remaining display)
|
||||
expect(restoredBounds.x).to.be.at.least(middleDisplay.workArea.x);
|
||||
expect(restoredBounds.y).to.be.at.least(middleDisplay.workArea.y);
|
||||
expect(restoredBounds.x + restoredBounds.width).to.be.at.most(middleDisplay.workArea.x + middleDisplay.workArea.width);
|
||||
expect(restoredBounds.y + restoredBounds.height).to.be.at.most(middleDisplay.workArea.y + middleDisplay.workArea.height);
|
||||
|
||||
// Window should maintain its original size
|
||||
expect(restoredBounds.width).to.equal(boundsOnRightmostDisplay.width);
|
||||
expect(restoredBounds.height).to.equal(boundsOnRightmostDisplay.height);
|
||||
|
||||
w.destroy();
|
||||
virtualDisplay.destroy(middleDisplayId);
|
||||
});
|
||||
|
||||
it('should restore multiple named windows independently across displays', async () => {
|
||||
const targetDisplayX = primaryDisplay.bounds.x + primaryDisplay.bounds.width;
|
||||
const targetDisplayY = primaryDisplay.bounds.y;
|
||||
// We expect only the primary display to be present before the tests start
|
||||
expect(screen.getAllDisplays().length).to.equal(1);
|
||||
|
||||
// Create a first virtual display to the right of the primary display
|
||||
const targetDisplayId1 = virtualDisplay.create({
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
x: targetDisplayX,
|
||||
y: targetDisplayY
|
||||
});
|
||||
// Create a second virtual display to the right of the first
|
||||
const targetDisplayId2 = virtualDisplay.create({
|
||||
width: 1600,
|
||||
height: 900,
|
||||
x: targetDisplayX + 1920,
|
||||
y: targetDisplayY
|
||||
});
|
||||
|
||||
// Verify the virtual displays are created correctly - single check for origin x values
|
||||
const targetDisplay1 = screen.getDisplayNearestPoint({ x: targetDisplayX, y: targetDisplayY });
|
||||
expect(targetDisplay1.bounds.x).to.equal(targetDisplayX);
|
||||
|
||||
const targetDisplay2 = screen.getDisplayNearestPoint({ x: targetDisplayX + 1920, y: targetDisplayY });
|
||||
expect(targetDisplay2.bounds.x).to.equal(targetDisplayX + 1920);
|
||||
|
||||
// Window 1 on primary display
|
||||
const window1Name = 'test-multi-window-1';
|
||||
const bounds1 = {
|
||||
width: 300,
|
||||
height: 200,
|
||||
x: primaryDisplay.workArea.x + 50,
|
||||
y: primaryDisplay.workArea.y + 50
|
||||
};
|
||||
|
||||
// Window 2 on second display (first virtual)
|
||||
const window2Name = 'test-multi-window-2';
|
||||
const bounds2 = {
|
||||
width: 400,
|
||||
height: 300,
|
||||
x: targetDisplay1.workArea.x + 100,
|
||||
y: targetDisplay1.workArea.y + 100
|
||||
};
|
||||
|
||||
// Window 3 on third display (second virtual)
|
||||
const window3Name = 'test-multi-window-3';
|
||||
const bounds3 = {
|
||||
width: 350,
|
||||
height: 250,
|
||||
x: targetDisplay2.workArea.x + 150,
|
||||
y: targetDisplay2.workArea.y + 150
|
||||
};
|
||||
|
||||
// Clear window state for all three windows from previous tests
|
||||
BrowserWindow.clearWindowState(window1Name);
|
||||
BrowserWindow.clearWindowState(window2Name);
|
||||
BrowserWindow.clearWindowState(window3Name);
|
||||
|
||||
// Create and save state for all three windows
|
||||
const w1 = new BrowserWindow({
|
||||
name: window1Name,
|
||||
windowStatePersistence: true,
|
||||
show: false,
|
||||
...bounds1
|
||||
});
|
||||
const w2 = new BrowserWindow({
|
||||
name: window2Name,
|
||||
windowStatePersistence: true,
|
||||
show: false,
|
||||
...bounds2
|
||||
});
|
||||
const w3 = new BrowserWindow({
|
||||
name: window3Name,
|
||||
windowStatePersistence: true,
|
||||
show: false,
|
||||
...bounds3
|
||||
});
|
||||
|
||||
w1.destroy();
|
||||
w2.destroy();
|
||||
w3.destroy();
|
||||
|
||||
await setTimeout(2000);
|
||||
|
||||
// Restore all three windows
|
||||
const restoredW1 = new BrowserWindow({
|
||||
name: window1Name,
|
||||
windowStatePersistence: true,
|
||||
show: false
|
||||
});
|
||||
const restoredW2 = new BrowserWindow({
|
||||
name: window2Name,
|
||||
windowStatePersistence: true,
|
||||
show: false
|
||||
});
|
||||
const restoredW3 = new BrowserWindow({
|
||||
name: window3Name,
|
||||
windowStatePersistence: true,
|
||||
show: false
|
||||
});
|
||||
|
||||
// Check that each window restored to its correct display and position
|
||||
expectBoundsEqual(restoredW1.getBounds(), bounds1);
|
||||
expectBoundsEqual(restoredW2.getBounds(), bounds2);
|
||||
expectBoundsEqual(restoredW3.getBounds(), bounds3);
|
||||
|
||||
restoredW1.destroy();
|
||||
restoredW2.destroy();
|
||||
restoredW3.destroy();
|
||||
virtualDisplay.destroy(targetDisplayId1);
|
||||
virtualDisplay.destroy(targetDisplayId2);
|
||||
});
|
||||
|
||||
it('should restore fullscreen state on correct display', async () => {
|
||||
const targetDisplayX = primaryDisplay.bounds.x + primaryDisplay.bounds.width;
|
||||
const targetDisplayY = primaryDisplay.bounds.y;
|
||||
// We expect only the primary display to be present before the tests start
|
||||
expect(screen.getAllDisplays().length).to.equal(1);
|
||||
|
||||
// Create a new virtual target display to the right of the primary display
|
||||
const targetDisplayId = virtualDisplay.create({
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
x: targetDisplayX,
|
||||
y: targetDisplayY
|
||||
});
|
||||
|
||||
const targetDisplay = screen.getDisplayNearestPoint({ x: targetDisplayX, y: targetDisplayY });
|
||||
expect(targetDisplay.bounds.x).to.equal(targetDisplayX);
|
||||
|
||||
// Create window on target display and set fullscreen
|
||||
const initialBounds = {
|
||||
width: 400,
|
||||
height: 300,
|
||||
x: targetDisplay.workArea.x + 100,
|
||||
y: targetDisplay.workArea.y + 100,
|
||||
fullscreen: true
|
||||
};
|
||||
|
||||
await createAndSaveWindowState(initialBounds);
|
||||
|
||||
const w = new BrowserWindow({
|
||||
name: windowName,
|
||||
windowStatePersistence: true
|
||||
});
|
||||
|
||||
const enterFullScreen = once(w, 'enter-full-screen');
|
||||
if (!w.isFullScreen()) await enterFullScreen;
|
||||
|
||||
expect(w.isFullScreen()).to.equal(true);
|
||||
|
||||
// Check that fullscreen window is on the correct display
|
||||
const fsBounds = w.getBounds();
|
||||
expect(fsBounds.x).to.be.at.least(targetDisplay.bounds.x);
|
||||
expect(fsBounds.y).to.be.at.least(targetDisplay.bounds.y);
|
||||
expect(fsBounds.x + fsBounds.width).to.be.at.most(targetDisplay.bounds.x + targetDisplay.bounds.width);
|
||||
expect(fsBounds.y + fsBounds.height).to.be.at.most(targetDisplay.bounds.y + targetDisplay.bounds.height);
|
||||
|
||||
w.destroy();
|
||||
virtualDisplay.destroy(targetDisplayId);
|
||||
});
|
||||
|
||||
it('should restore maximized state on correct display', async () => {
|
||||
const targetDisplayX = primaryDisplay.bounds.x + primaryDisplay.bounds.width;
|
||||
const targetDisplayY = primaryDisplay.bounds.y;
|
||||
// We expect only the primary display to be present before the tests start
|
||||
expect(screen.getAllDisplays().length).to.equal(1);
|
||||
|
||||
// Create a new virtual target display to the right of the primary display
|
||||
const targetDisplayId = virtualDisplay.create({
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
x: targetDisplayX,
|
||||
y: targetDisplayY
|
||||
});
|
||||
|
||||
const targetDisplay = screen.getDisplayNearestPoint({ x: targetDisplayX, y: targetDisplayY });
|
||||
expect(targetDisplay.bounds.x).to.equal(targetDisplayX);
|
||||
|
||||
// Create window on target display and maximize it
|
||||
const w1 = new BrowserWindow({
|
||||
name: windowName,
|
||||
windowStatePersistence: {
|
||||
displayMode: false
|
||||
},
|
||||
x: targetDisplay.workArea.x,
|
||||
y: targetDisplay.workArea.y
|
||||
});
|
||||
|
||||
const maximized = once(w1, 'maximize');
|
||||
w1.maximize();
|
||||
if (!w1.isMaximized()) await maximized;
|
||||
|
||||
w1.destroy();
|
||||
await setTimeout(2000);
|
||||
|
||||
const w2 = new BrowserWindow({
|
||||
name: windowName,
|
||||
windowStatePersistence: true,
|
||||
show: true
|
||||
});
|
||||
|
||||
const maximized_ = once(w2, 'maximize');
|
||||
if (!w2.isMaximized()) await maximized_;
|
||||
|
||||
expect(w2.isMaximized()).to.equal(true);
|
||||
// Check that maximized window is on the correct display
|
||||
const maximizedBounds = w2.getBounds();
|
||||
expect(maximizedBounds.x).to.be.at.least(targetDisplay.bounds.x);
|
||||
expect(maximizedBounds.y).to.be.at.least(targetDisplay.bounds.y);
|
||||
expect(maximizedBounds.x + maximizedBounds.width).to.be.at.most(targetDisplay.bounds.x + targetDisplay.bounds.width);
|
||||
expect(maximizedBounds.y + maximizedBounds.height).to.be.at.most(targetDisplay.bounds.y + targetDisplay.bounds.height);
|
||||
|
||||
w2.destroy();
|
||||
virtualDisplay.destroy(targetDisplayId);
|
||||
});
|
||||
|
||||
it('should restore kiosk state on correct display', async () => {
|
||||
const targetDisplayX = primaryDisplay.bounds.x + primaryDisplay.bounds.width;
|
||||
const targetDisplayY = primaryDisplay.bounds.y;
|
||||
// We expect only the primary display to be present before the tests start
|
||||
expect(screen.getAllDisplays().length).to.equal(1);
|
||||
|
||||
// Create a new virtual target display to the right of the primary display
|
||||
const targetDisplayId = virtualDisplay.create({
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
x: targetDisplayX,
|
||||
y: targetDisplayY
|
||||
});
|
||||
|
||||
const targetDisplay = screen.getDisplayNearestPoint({ x: targetDisplayX, y: targetDisplayY });
|
||||
|
||||
// Create window on target display and set kiosk: true
|
||||
const initialBounds = {
|
||||
width: 400,
|
||||
height: 300,
|
||||
x: targetDisplay.workArea.x + 100,
|
||||
y: targetDisplay.workArea.y + 100,
|
||||
kiosk: true
|
||||
};
|
||||
|
||||
await createAndSaveWindowState(initialBounds);
|
||||
|
||||
const w = new BrowserWindow({
|
||||
name: windowName,
|
||||
windowStatePersistence: true
|
||||
});
|
||||
|
||||
const enterFullScreen = once(w, 'enter-full-screen');
|
||||
if (!w.isFullScreen()) await enterFullScreen;
|
||||
|
||||
expect(w.isFullScreen()).to.equal(true);
|
||||
expect(w.isKiosk()).to.equal(true);
|
||||
|
||||
// Check that kiosk window is on the correct display
|
||||
const kioskBounds = w.getBounds();
|
||||
expect(kioskBounds.x).to.be.at.least(targetDisplay.bounds.x);
|
||||
expect(kioskBounds.y).to.be.at.least(targetDisplay.bounds.y);
|
||||
expect(kioskBounds.x + kioskBounds.width).to.be.at.most(targetDisplay.bounds.x + targetDisplay.bounds.width);
|
||||
expect(kioskBounds.y + kioskBounds.height).to.be.at.most(targetDisplay.bounds.y + targetDisplay.bounds.height);
|
||||
|
||||
w.destroy();
|
||||
virtualDisplay.destroy(targetDisplayId);
|
||||
});
|
||||
|
||||
it('should maintain same bounds when target display resolution increases', async () => {
|
||||
const targetDisplayX = primaryDisplay.bounds.x + primaryDisplay.bounds.width;
|
||||
const targetDisplayY = primaryDisplay.bounds.y;
|
||||
// We expect only the primary display to be present before the tests start
|
||||
expect(screen.getAllDisplays().length).to.equal(1);
|
||||
|
||||
// Create initial virtual display
|
||||
const targetDisplayId = virtualDisplay.create({
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
x: targetDisplayX,
|
||||
y: targetDisplayY
|
||||
});
|
||||
|
||||
const targetDisplay = screen.getDisplayNearestPoint({ x: targetDisplayX, y: targetDisplayY });
|
||||
|
||||
// Create a new virtual display with double the resolution of the target display
|
||||
const higherResDisplayId = virtualDisplay.create({
|
||||
width: targetDisplay.bounds.width * 2,
|
||||
height: targetDisplay.bounds.height * 2,
|
||||
x: targetDisplayX + targetDisplay.bounds.width,
|
||||
y: targetDisplayY
|
||||
});
|
||||
|
||||
// Bounds for the test window on the virtual target display
|
||||
const initialBounds = {
|
||||
width: 400,
|
||||
height: 300,
|
||||
x: targetDisplay.workArea.x + 100,
|
||||
y: targetDisplay.workArea.y + 100
|
||||
};
|
||||
|
||||
await createAndSaveWindowState(initialBounds);
|
||||
|
||||
// Destroy the target display and wait for the higher resolution display to take its place
|
||||
virtualDisplay.destroy(targetDisplayId);
|
||||
while (screen.getAllDisplays().length > 2) await setTimeout(1000);
|
||||
|
||||
// Restore window
|
||||
const w = new BrowserWindow({
|
||||
name: windowName,
|
||||
windowStatePersistence: true,
|
||||
show: false
|
||||
});
|
||||
|
||||
const restoredBounds = w.getBounds();
|
||||
|
||||
// Window should maintain same x, y, width, height as the display got bigger
|
||||
expectBoundsEqual(restoredBounds, initialBounds);
|
||||
|
||||
w.destroy();
|
||||
virtualDisplay.destroy(higherResDisplayId);
|
||||
});
|
||||
|
||||
it('should reposition and resize window when target display resolution decreases', async () => {
|
||||
const targetDisplayX = primaryDisplay.bounds.x + primaryDisplay.bounds.width;
|
||||
const targetDisplayY = primaryDisplay.bounds.y;
|
||||
// We expect only the primary display to be present before the tests start
|
||||
expect(screen.getAllDisplays().length).to.equal(1);
|
||||
|
||||
// Create initial virtual display with high resolution
|
||||
const targetDisplayId = virtualDisplay.create({
|
||||
width: 2560,
|
||||
height: 1440,
|
||||
x: targetDisplayX,
|
||||
y: targetDisplayY
|
||||
});
|
||||
|
||||
const targetDisplay = screen.getDisplayNearestPoint({ x: targetDisplayX, y: targetDisplayY });
|
||||
|
||||
// Create a new virtual display with half the resolution of the target display shifted down
|
||||
const lowerResDisplayId = virtualDisplay.create({
|
||||
width: targetDisplay.bounds.width / 2,
|
||||
height: targetDisplay.bounds.height / 2,
|
||||
x: targetDisplayX + targetDisplay.bounds.width,
|
||||
y: targetDisplay.bounds.height / 2
|
||||
});
|
||||
|
||||
// Bounds that would overflow on a smaller display
|
||||
const initialBounds = {
|
||||
x: targetDisplay.workArea.x,
|
||||
y: targetDisplay.workArea.y,
|
||||
width: targetDisplay.bounds.width,
|
||||
height: targetDisplay.bounds.height
|
||||
};
|
||||
|
||||
await createAndSaveWindowState(initialBounds);
|
||||
|
||||
// Destroy and and wait for the lower resolution display to take its place
|
||||
virtualDisplay.destroy(targetDisplayId);
|
||||
while (screen.getAllDisplays().length > 2) await setTimeout(1000);
|
||||
|
||||
// We expect the display to be shifted down as we set y: targetDisplay.bounds.height / 2 earlier
|
||||
const smallerDisplay = screen.getDisplayNearestPoint({ x: targetDisplayX, y: targetDisplay.bounds.height / 2 });
|
||||
|
||||
// Restore window
|
||||
const w = new BrowserWindow({
|
||||
name: windowName,
|
||||
windowStatePersistence: true,
|
||||
show: false
|
||||
});
|
||||
|
||||
const restoredBounds = w.getBounds();
|
||||
|
||||
// Window should be repositioned to be entirely visible on smaller display
|
||||
expect(restoredBounds.x).to.be.at.least(smallerDisplay.workArea.x);
|
||||
expect(restoredBounds.y).to.be.at.least(smallerDisplay.workArea.y);
|
||||
expect(restoredBounds.x + restoredBounds.width).to.be.at.most(smallerDisplay.workArea.x + smallerDisplay.workArea.width);
|
||||
expect(restoredBounds.y + restoredBounds.height).to.be.at.most(smallerDisplay.workArea.y + smallerDisplay.workArea.height);
|
||||
|
||||
w.destroy();
|
||||
virtualDisplay.destroy(lowerResDisplayId);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
+ (NSInteger)create:(int)width height:(int)height x:(int)x y:(int)y;
|
||||
+ (BOOL)destroy:(NSInteger)displayId;
|
||||
+ (BOOL)forceCleanup;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@ class DummyManager {
|
||||
|
||||
static func createDummy(_ dummyDefinition: DummyDefinition, isPortrait _: Bool = false, serialNum: UInt32 = 0, doConnect: Bool = true) -> Int? {
|
||||
let dummy = Dummy(dummyDefinition: dummyDefinition, serialNum: serialNum, doConnect: doConnect)
|
||||
|
||||
if !dummy.isConnected {
|
||||
print("[DummyManager.createDummy:\(#line)] Failed to create virtual display - not connected")
|
||||
return nil
|
||||
}
|
||||
self.dummyCounter += 1
|
||||
self.definedDummies[self.dummyCounter] = DefinedDummy(dummy: dummy)
|
||||
return self.dummyCounter
|
||||
@@ -25,6 +30,30 @@ class DummyManager {
|
||||
}
|
||||
self.definedDummies[number] = nil
|
||||
}
|
||||
|
||||
static func forceCleanup() {
|
||||
for (_, definedDummy) in self.definedDummies {
|
||||
if definedDummy.dummy.isConnected {
|
||||
definedDummy.dummy.virtualDisplay = nil
|
||||
definedDummy.dummy.displayIdentifier = 0
|
||||
definedDummy.dummy.isConnected = false
|
||||
}
|
||||
}
|
||||
|
||||
self.definedDummies.removeAll()
|
||||
self.dummyCounter = 0
|
||||
|
||||
var config: CGDisplayConfigRef? = nil
|
||||
if CGBeginDisplayConfiguration(&config) == .success {
|
||||
CGCompleteDisplayConfiguration(config, .permanently)
|
||||
}
|
||||
|
||||
usleep(2000000)
|
||||
|
||||
if CGBeginDisplayConfiguration(&config) == .success {
|
||||
CGCompleteDisplayConfiguration(config, .forSession)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DummyDefinition {
|
||||
@@ -88,10 +117,10 @@ class Dummy: Equatable {
|
||||
self.virtualDisplay = virtualDisplay
|
||||
self.displayIdentifier = virtualDisplay.displayID
|
||||
self.isConnected = true
|
||||
os_log("Display %{public}@ successfully connected", type: .info, "\(name)")
|
||||
print("[Dummy.connect:\(#line)] Successfully connected virtual display: \(name)")
|
||||
return true
|
||||
} else {
|
||||
os_log("Failed to connect display %{public}@", type: .info, "\(name)")
|
||||
print("[Dummy.connect:\(#line)] Failed to connect virtual display: \(name)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -99,7 +128,7 @@ class Dummy: Equatable {
|
||||
func disconnect() {
|
||||
self.virtualDisplay = nil
|
||||
self.isConnected = false
|
||||
os_log("Disconnected virtual display: %{public}@", type: .info, "\(self.getName())")
|
||||
print("[Dummy.disconnect:\(#line)] Disconnected virtual display: \(self.getName())")
|
||||
}
|
||||
|
||||
private static func waitForDisplayRegistration(_ displayId: CGDirectDisplayID) -> Bool {
|
||||
@@ -110,6 +139,7 @@ class Dummy: Equatable {
|
||||
}
|
||||
usleep(100000)
|
||||
}
|
||||
print("[Dummy.waitForDisplayRegistration:\(#line)] Failed to register virtual display: \(displayId)")
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,11 @@ import os.log
|
||||
return true
|
||||
}
|
||||
|
||||
@objc public static func forceCleanup() -> Bool {
|
||||
DummyManager.forceCleanup()
|
||||
return true
|
||||
}
|
||||
|
||||
private static func positionDisplay(displayId: Int, x: Int, y: Int) {
|
||||
guard let definedDummy = DummyManager.definedDummies[displayId],
|
||||
definedDummy.dummy.isConnected else {
|
||||
|
||||
@@ -11,4 +11,8 @@
|
||||
return [VirtualDisplay destroyWithId:(int)displayId];
|
||||
}
|
||||
|
||||
+ (BOOL)forceCleanup {
|
||||
return [VirtualDisplay forceCleanup];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -134,6 +134,18 @@ napi_value create(napi_env env, napi_callback_info info) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// virtualDisplay.forceCleanup()
|
||||
napi_value forceCleanup(napi_env env, napi_callback_info info) {
|
||||
BOOL result = [VirtualDisplayBridge forceCleanup];
|
||||
|
||||
napi_value js_result;
|
||||
if (napi_get_boolean(env, result, &js_result) != napi_ok) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return js_result;
|
||||
}
|
||||
|
||||
// virtualDisplay.destroy()
|
||||
napi_value destroy(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 1;
|
||||
@@ -167,7 +179,9 @@ napi_value destroy(napi_env env, napi_callback_info info) {
|
||||
napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_property_descriptor descriptors[] = {
|
||||
{"create", NULL, create, NULL, NULL, NULL, napi_default, NULL},
|
||||
{"destroy", NULL, destroy, NULL, NULL, NULL, napi_default, NULL}};
|
||||
{"destroy", NULL, destroy, NULL, NULL, NULL, napi_default, NULL},
|
||||
{"forceCleanup", NULL, forceCleanup, NULL, NULL, NULL, napi_default,
|
||||
NULL}};
|
||||
|
||||
if (napi_define_properties(env, exports,
|
||||
sizeof(descriptors) / sizeof(*descriptors),
|
||||
|
||||
Reference in New Issue
Block a user