Skip to content

Commit

Permalink
Use local copy of win32-displayconfig
Browse files Browse the repository at this point in the history
  • Loading branch information
xanderfrangos committed Nov 23, 2024
1 parent 9e234bb commit b782ffe
Show file tree
Hide file tree
Showing 15 changed files with 6,207 additions and 909 deletions.
2,850 changes: 1,947 additions & 903 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 1 addition & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"stackblur-canvas": "^2.7.0",
"studio-display-control": "^0.2.0",
"suncalc": "^1.9.0",
"win32-displayconfig": "^0.1.0",
"win32-displayconfig": "file:src/modules/win32-displayconfig",
"windows-accent-colors": "^1.0.1",
"windows-hdr": "file:src/modules/windows-hdr",
"wmi-bridge": "file:src/modules/wmi-bridge",
Expand Down Expand Up @@ -187,11 +187,6 @@
"overrides": {
"node-gyp": "$node-gyp",
"bindings@<1.5.0": "1.5.0",
"win32-displayconfig": {
"node-gyp": "$node-gyp",
"bindings": "$bindings",
"node-addon-api": "$node-addon-api"
},
"node-addon-api@<8.2.2": "8.2.2",
"node-active-window": {
"node-gyp": "$node-gyp",
Expand Down
25 changes: 25 additions & 0 deletions src/modules/win32-displayconfig/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# dependencies
node_modules/

# production
build/
dist/

# misc
.cache
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea

.log
logs/
*.pfx
.env
cache/
1 change: 1 addition & 0 deletions src/modules/win32-displayconfig/CONTRIBUTORS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Daniel Sweet (@djsweet)
19 changes: 19 additions & 0 deletions src/modules/win32-displayconfig/COPYRIGHT
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright 2020 individuals listed in the CONTRIBUTORS file of this distribution.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTIBILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
299 changes: 299 additions & 0 deletions src/modules/win32-displayconfig/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
# win32-displayconfig

Simplified Node bindings to the Win32 [Display Device Reference API](https://docs.microsoft.com/en-us/windows/win32/api/_display/).

## Supported Windows Versions

This module has currently only received testing on Windows 10 version 2004.
Additional testing on other versions of Windows 10 and Windows 8.1 would be appreciated.

We are not planning on supporting Windows 7 or Windows 8, as they are generally unsupported
by Microsoft. However, patches to support Windows 7 or Windows 8 are welcome.

## Supported Node Versions

This module requires N-API Version 4,
[bounding the minimum supported Node versions](https://nodejs.org/api/n-api.html#n_api_n_api_version_matrix)
to 8.16.0 (without any support for Node 9), Node 10.16.0, and Node 11.8.0. We are only actively testing
on Node 10.21 and above. We'll note incompatibilities and accept patches for Node 8 if the need arises.

## Functionality

- Querying display devices, at a low and higher
- Observing display device layout and output changes
- Querying the vertical refresh rate at a given display point
- Saving and restoring layouts via the Display Device Reference API

## Examples

### Querying Low-Level Display Device Information

The `queryDisplayConfig` function returns data somewhat similar to the outputs of the
[`QueryDisplayConfig` function](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-querydisplayconfig)
via the `pathArray` and `modeInfoArray` properties, and naming and manufacturer info via its
`nameArray` properties.

From [`scripts/dumpquery.js`](scripts/dumpquery.js):

```javascript
const w32disp = require("win32-displayconfig");
const util = require("util");

w32disp.queryDisplayConfig().then((config) => {
// The outputs of `pathArray` and `modeArray` refer to
// both parsed values and raw buffer values. The raw buffers
// are the exact structs returned by the Win32 API, and are
// used to save and restore display positioning configuration.
const pathArray = config.pathArray.map((pa) => pa.value);
const modeArray = config.modeArray.map((ma) => ma.value);
console.log(
util.inspect(
// However, the `nameArray` only refers to parsed values.
{ pathArray, modeArray, nameArray: config.nameArray },
{ depth: 10 }
)
);
});
```

Which outputs:

```
{ pathArray:
[ { flags: 1,
sourceInfo:
{ adapterId: { LowPart: 945385802, HighPart: 0 },
id: 0,
statusFlags: 1,
modeInfoIdx: 1 },
targetInfo:
{ adapterId: { LowPart: 945385802, HighPart: 0 },
id: 114948,
statusFlags: 1,
outputTechnology: 'dvi',
rotation: 0,
scaling: 'identity',
refreshRate: { Numerator: 60000, Denominator: 1000 },
scanLineOrdering: 'progressive',
targetAvailable: 1,
modeInfoIdx: 0 } },
...
],
modeArray:
[ { infoType: 'target',
id: 114948,
adapterId: { LowPart: 945385802, HighPart: 0 },
targetMode:
{ targetVideoSignalInfo:
{ pixelRate: { lowPart: 148500000, highPart: 0 },
hSyncFreq: { Numerator: 148500000, Denominator: 2200 },
vSyncFreq: { Numerator: 60000, Denominator: 1000 },
activeSize: { cx: 1920, cy: 1080 },
totalSize: { cx: 2200, cy: 1125 },
videoStandard: 255,
scanlineOrdering: 'progressive' } } },
...
],
nameArray:
[ { adapterId: { LowPart: 945385802, HighPart: 0 },
id: 114948,
deviceFlags: 5,
outputTechnology: 'dvi',
edidManufactureId: 29188,
edidProductCodeId: 147,
connectorInstance: 2,
monitorFriendlyDeviceName: 'Acer X233H',
monitorDevicePath:
'\\\\?\\DISPLAY#ACR0093#5&326e05e0&0&UID114948#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}' },
...
] }
```

This is generally lower-level output than you want. Consider using `extractDisplayConfig` instead.

### Querying Higher-Level Display Device Information

The `extractDisplayConfig` function reshapes the output of `queryDisplayConfig` to be more useful
to application programmers. Information from the `pathArray`, `modeInfoArray`, and `nameArray` properties
of the output of `queryDisplayConfig` are all joined according to their IDs.

From [`scripts/dumpextract.js`](scripts/dumpextract.js):

```javascript
const w32disp = require("win32-displayconfig");
const util = require("util");

w32disp.extractDisplayConfig().then((output) => {
console.log(util.inspect(output, { depth: 10 }));
});
```

Which outputs:

```
[ { displayName: 'Acer X233H',
devicePath:
'\\\\?\\DISPLAY#ACR0093#5&326e05e0&0&UID114948#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}',
sourceConfigId: { adapterId: { LowPart: 945385802, HighPart: 0 }, id: 1 },
targetConfigId:
{ adapterId: { LowPart: 945385802, HighPart: 0 }, id: 114948 },
inUse: false,
outputTechnology: 'dvi',
rotation: 0,
scaling: 'preferred',
sourceMode:
{ width: 1920,
height: 1080,
pixelFormat: 32,
position: { x: 0, y: 0 } },
pathBuffer:
<Buffer ... >,
sourceModeBuffer:
<Buffer ... >,
targetVideoSignalInfo:
{ pixelRate: { lowPart: 148500000, highPart: 0 },
hSyncFreq: { Numerator: 148500000, Denominator: 2200 },
vSyncFreq: { Numerator: 60000, Denominator: 1000 },
activeSize: { cx: 1920, cy: 1080 },
totalSize: { cx: 2200, cy: 1125 },
videoStandard: 255,
scanlineOrdering: 'progressive' },
targetModeBuffer:
<Buffer ... > },
...
]
```

Note that the low-level buffers are still present, but `sourceMode`, `targetVideoSignalInfo`, `devicePath`,
and `displayName` have all been combined from multiple sources. Also note the `inUse` boolean, which is
derived from comparing against low-level flags in the Display Device Reference API.

You will likely see multiple outputs for a single display device in this output, with only one of them
having `inUse: true`. The `inUse: false` entries correspond to alternative output modes available on the device.

### Observing Display Device Layout and Output Changes

The display geometry can change at any time during the execution of your program, but sometimes you
depend on knowledge of this geometry for the correct behavior. You can observe changes as they
occur using the `addDisplayChangeListener` function:

```javascript
const w32disp = require("win32-displayconfig");
const listener1 = (err, conf) => {
if (err !== null) {
// Report error
console.error(err);
} else {
// conf is the same as the output of extractDisplayConfig
// ...
}
};
w32disp.addDisplayChangeListener(listener1);

// Or equivalently
const listener2 = w32disp.addDisplayChangeListener((err, conf) => {
if (err !== null) {
console.error(err);
} else {
// ...
}
});
```

Adding these change listeners keeps the event loop active, even when all other
activities have been stopped, so for situations where graceful shutdown is required,
remove all of the listeners using the `removeDisplayChangeListener` function:

```javascript
w32disp.removeDisplayChangeListener(listener1);
w32disp.removeDisplayChangeListener(listener2);
// No more display config activities are present on the event loop,
// so Node can now exit cleanly.
```

### Querying the Vertical Refresh Rate at a Display Point

Some operations need to only occur as quickly as the vertical refresh rate
for smooth performance. For example, many Windows positioning APIs cannot
perform well if they are invoked any faster than the frame rate of the GPU.

This module provides a `VerticalRefreshRateContext` class to determine the
refresh rate of the displays servicing a given display point. Note that not
all displays will necessarily have the same refresh rate, and many displays
can be servicing the same display point. When multiple displays are servicing
the same display point, the result is the minimum vertical refresh rate.

```javascript
const { VerticalRefreshRateContext } = require("win32-displayconfig");
const ctx = new VerticalRefreshRateContext();

async function refreshRateForPoint(x, y) {
// Here, x is the vertical offset and y is the horizontal offset.
// These offsets are relative to the upper left hand corner of the
// primary display, so they are allowed and expected to become negative.
//
// The result is either a number indicating the vertical refresh rate in Hz,
// or undefined if the given point is not present in the display bounds of
// the current display configuration.
return await ctx.findVerticalRefreshRateForDisplayPoint(x, y);
}

// ...

// This context reacts to underlying display changes, as a trade-off between
// performance and accuracy. It does so using the `addDisplayChangeListener`
// capabilities, so it needs to be disposed of properly to clear the event
// loop for graceful shutdown.
//
// Call this when you are finished with the context. A good place to do so
// is in the code that handles gracefully exiting your program.
ctx.close();
```

See [`scripts/watchmouse.js`](scripts/watchmouse.js) for a working example, polling
for the refresh rate based on the mouse cursor position.

### Saving and Restoring Device Layouts

This module can save and restore display device layouts (although it cannot directly modify
them yet; patches welcome!). You can use this to implement a display configuration profile
system.

Saving a profile is achieved with the `displayConfigForRestoration` function:

```javascript
const w32disp = require("win32-displayconfig");

async function saveDisplayConfig() {
const conf = await w32disp.displayConfigForRestoration();
// The type of conf is JSON-safe: you can store this on the disk,
// transmit it over a network, etc.
return JSON.stringify(conf);
}
```

And restoring a profile is achieved with the `restoreDisplayConfig` function:

```javascript
const w32disp = require("win32-displayconfig");

async function restoreDisplayConfig(serialized, persistent) {
// If "persistent", then this display configuration will be saved in Windows
// as the default display configuration. This means that restoring to defaults
// or rebooting will result in this exact configuration.
persistent = !!persistent;
const conf = JSON.parse(serialized);

// If portions of the configuration refer to displays that are not actually
// present, we make a best effort to enable all of the displays that are both
// present and enabled in this configuration, and disable all other displays.
//
// Otherwise, the display configuration is set exactly.
await w32disp.restoreDisplayConfig(conf);
}
```

## Copyright

This module is available under the terms of the MIT license. See the [`COPYRIGHT`](COPYRIGHT) file
for more information.
24 changes: 24 additions & 0 deletions src/modules/win32-displayconfig/binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"variables": {
"openssl_fips" : "0"
},
"targets": [
{
"target_name": "win32_displayconfig",
"cflags!": ["-fno-exceptions"],
"cflags_cc!": ["-fno-exceptions"],
"conditions": [
["OS=='win'", {
"sources": ["win32-displayconfig.cc"]
}],
],
"include_dirs": [
"<!@(node -p \"require('node-addon-api').include\")"
],
'msvs_settings': {
'VCCLCompilerTool': { "ExceptionHandling": 1, 'AdditionalOptions': [ '/permissive' ] }
},
'defines': ['NAPI_DISABLE_CPP_EXCEPTIONS'],
}
]
}
Loading

0 comments on commit b782ffe

Please sign in to comment.