From 01766b4d4d3757816768909620596b256f0a1957 Mon Sep 17 00:00:00 2001
From: Xander Frangos <33106561+xanderfrangos@users.noreply.github.com>
Date: Wed, 2 Oct 2024 22:57:07 -0400
Subject: [PATCH] Preliminary HDR support
https://github.com/xanderfrangos/twinkle-tray/issues/97
---
package-lock.json | 19 +++
package.json | 1 +
src/Monitors.js | 58 +++++++
src/components/MonitorInfo.jsx | 18 +++
src/components/SettingsWindow.jsx | 1 +
src/electron.js | 21 ++-
src/modules/windows-hdr/.gitignore | 5 +
src/modules/windows-hdr/binding.gyp | 18 +++
src/modules/windows-hdr/index.js | 6 +
src/modules/windows-hdr/package.json | 25 +++
src/modules/windows-hdr/windows-hdr.cc | 212 +++++++++++++++++++++++++
src/settings-preload.js | 5 +
12 files changed, 386 insertions(+), 3 deletions(-)
create mode 100644 src/modules/windows-hdr/.gitignore
create mode 100644 src/modules/windows-hdr/binding.gyp
create mode 100644 src/modules/windows-hdr/index.js
create mode 100644 src/modules/windows-hdr/package.json
create mode 100644 src/modules/windows-hdr/windows-hdr.cc
diff --git a/package-lock.json b/package-lock.json
index 22b69e1d..051b611e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,6 +25,7 @@
"suncalc": "^1.9.0",
"win32-displayconfig": "^0.1.0",
"windows-accent-colors": "^1.0.1",
+ "windows-hdr": "file:src/modules/windows-hdr",
"wmi-bridge": "file:src/modules/wmi-bridge",
"wmi-client": "^0.5.0"
},
@@ -20090,6 +20091,10 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="
},
+ "node_modules/windows-hdr": {
+ "resolved": "src/modules/windows-hdr",
+ "link": true
+ },
"node_modules/wmi-bridge": {
"resolved": "src/modules/wmi-bridge",
"link": true
@@ -20493,6 +20498,20 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="
},
+ "src/modules/windows-hdr": {
+ "version": "1.0.0",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "bindings": "1.5.0",
+ "node-addon-api": "^7.0.0"
+ }
+ },
+ "src/modules/windows-hdr/node_modules/node-addon-api": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="
+ },
"src/modules/wmi-bridge": {
"version": "1.0.0",
"hasInstallScript": true,
diff --git a/package.json b/package.json
index f79558cc..d8f249f7 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,7 @@
"suncalc": "^1.9.0",
"win32-displayconfig": "^0.1.0",
"windows-accent-colors": "^1.0.1",
+ "windows-hdr": "file:src/modules/windows-hdr",
"wmi-bridge": "file:src/modules/wmi-bridge",
"wmi-client": "^0.5.0"
},
diff --git a/src/Monitors.js b/src/Monitors.js
index 8cdb74fe..50cd8ccb 100644
--- a/src/Monitors.js
+++ b/src/Monitors.js
@@ -4,6 +4,7 @@ console.log = (...args) => { args.unshift(tag); oLog(...args) }
console.log("Monitors.js starting. If you see this again, something bad happened!")
const w32disp = require("win32-displayconfig");
const wmibridge = require("wmi-bridge");
+const hdr = require("windows-hdr");
const { exec } = require('child_process');
require("os").setPriority(0, require("os").constants.priority.PRIORITY_BELOW_NORMAL)
@@ -37,6 +38,8 @@ process.on('message', async (data) => {
})
} else if (data.type === "brightness") {
setBrightness(data.brightness, data.id)
+ } else if (data.type === "sdr") {
+ setSDRBrightness(data.brightness, data.id)
} else if (data.type === "settings") {
settings = data.settings
@@ -187,6 +190,17 @@ refreshMonitors = async (fullRefresh = false, ddcciType = "default", alwaysSendU
}
}
+ // HDR
+ if (settings.enableHDR) {
+ try {
+ startTime = process.hrtime.bigint()
+ monitorsHDR = await getHDRDisplays(monitors);
+ console.log(`getHDRDisplays() Total: ${(startTime - process.hrtime.bigint()) / BigInt(-1000000)}ms`)
+ } catch (e) {
+ console.log("\x1b[41m" + "getHDRDisplays() failed!" + "\x1b[0m", e)
+ }
+ }
+
// Hide internal
if (settings?.hideClosedLid) {
const wmiMonitor = Object.values(monitors).find(mon => mon.type === "wmi")
@@ -363,6 +377,17 @@ getAllMonitors = async (ddcciMethod = "default") => {
console.log("getBrightnessWMI() skipped due to previous failure.")
}
+ // HDR
+ if (settings.enableHDR) {
+ try {
+ startTime = process.hrtime.bigint()
+ monitorsHDR = await getHDRDisplays(foundMonitors);
+ console.log(`getHDRDisplays() Total: ${(startTime - process.hrtime.bigint()) / BigInt(-1000000)}ms`)
+ } catch (e) {
+ console.log("\x1b[41m" + "getHDRDisplays() failed!" + "\x1b[0m", e)
+ }
+ }
+
// Hide internal
if (settings?.hideClosedLid) {
const wmiMonitor = Object.values(foundMonitors).find(mon => mon.type === "wmi")
@@ -447,6 +472,29 @@ setStudioDisplayBrightness = async (serial, brightness) => {
}
}
+getHDRDisplays = async (monitors) => {
+ try {
+ const displays = hdr.getDisplays()
+ for(const display of displays) {
+ const hwid = display.path.split("#")
+ updateDisplay(monitors, hwid[2], {
+ name: display.name,
+ key: hwid[2],
+ id: display.path,
+ hwid,
+ sdrNits: display.nits,
+ sdrLevel: parseInt((display.nits - 80) / 4),
+ hdr: "supported"
+ });
+ displays[hwid[2]] = display
+ }
+ } catch(e) {
+ console.log("\x1b[41m" + "getHDRDisplays(): failed to access displays" + "\x1b[0m", e)
+ }
+
+ return monitors
+}
+
let wmiFailed = false
getMonitorsWMI = () => {
return new Promise(async (resolve, reject) => {
@@ -825,6 +873,16 @@ updateDisplay = (monitors, hwid2, info = {}) => {
return true
}
+function setSDRBrightness(brightness, id) {
+ if(!settings.enableHDR) return false;
+ try {
+ console.log("sdr", brightness, id)
+ hdr.setSDRBrightness(id, (brightness * 0.01 * 400) + 80)
+ } catch(e) {
+ console.log(`Couldn't update SDR brightness! [${id}]`, e);
+ }
+}
+
function setBrightness(brightness, id) {
try {
if (id) {
diff --git a/src/components/MonitorInfo.jsx b/src/components/MonitorInfo.jsx
index dd604eaa..b33d61c3 100644
--- a/src/components/MonitorInfo.jsx
+++ b/src/components/MonitorInfo.jsx
@@ -7,6 +7,7 @@ export default function MonitorInfo(props) {
const [contrast, setContrast] = useState(monitor?.features?.["0x12"] ? monitor?.features?.["0x12"][0] : 50)
const [volume, setVolume] = useState(monitor?.features?.["0x62"] ? monitor?.features?.["0x62"][0] : 50)
const [powerState, setPowerState] = useState(monitor?.features?.["0xD6"] ? monitor?.features?.["0xD6"][0] : 50)
+ const [sdr, setSDR] = useState(monitor.sdrLevel >= 0 ? monitor.sdrLevel : 50)
const [manualVCP, setManualVCP] = useState("")
const [manualValue, setManualValue] = useState("")
@@ -75,6 +76,14 @@ export default function MonitorInfo(props) {
)
+ // SDR test
+ extraHTML.push(
+
+
SDR
+
{ setSDR(val); setSDRBrightness(monitor.id, val) }} scrolling={false} />
+
+ )
+
return (
@@ -101,6 +110,15 @@ function setVCP(monitor, code, value) {
}))
}
+function setSDRBrightness(monitor, value) {
+ window.dispatchEvent(new CustomEvent("set-sdr-brightness", {
+ detail: {
+ monitor,
+ value
+ }
+ }))
+}
+
function getDebugMonitorType(type) {
if (type == "none") {
return (<>None >)
diff --git a/src/components/SettingsWindow.jsx b/src/components/SettingsWindow.jsx
index 184fbdbc..44ef9dee 100644
--- a/src/components/SettingsWindow.jsx
+++ b/src/components/SettingsWindow.jsx
@@ -1431,6 +1431,7 @@ export default class SettingsWindow extends PureComponent {
+
diff --git a/src/electron.js b/src/electron.js
index 874477a5..e5dbc3f8 100644
--- a/src/electron.js
+++ b/src/electron.js
@@ -465,6 +465,7 @@ const defaultSettings = {
disableWMIC: false,
disableWMI: false,
disableWin32: false,
+ enableHDR: false,
autoDisabledWMI: false,
useWin32Event: true,
useElectronEvents: true,
@@ -1875,7 +1876,12 @@ let ignoreBrightnessEventTimeout = false
function updateBrightness(index, newLevel, useCap = true, vcpValue = "brightness", clearTransition = true) {
try {
let level = newLevel
- let vcp = (vcpValue === "brightness" ? "brightness" : `0x${parseInt(vcpValue).toString(16)}`)
+ let vcp = "brightness"
+ switch(vcpValue) {
+ case "brightness": vcp = "brightness"; break;
+ case "sdr": vcp = "sdr"; break;
+ default: vcp = `0x${parseInt(vcpValue).toString(16)}`;
+ }
let monitor = false
if (typeof index == "string" && index * 1 != index) {
@@ -1909,9 +1915,15 @@ function updateBrightness(index, newLevel, useCap = true, vcpValue = "brightness
return false
}
- const normalized = normalizeBrightness(level, false, (useCap ? monitor.min : 0), (useCap ? monitor.max : 100))
+ const normalized = normalizeBrightness(level, false, 0, 100)
- if (monitor.type == "ddcci") {
+ if (vcp === "sdr") {
+ monitorsThread.send({
+ type: "sdr",
+ brightness: normalized,
+ id: monitor.id
+ })
+ } else if (monitor.type == "ddcci") {
if (vcp === "brightness") {
monitor.brightness = level
monitor.brightnessRaw = normalized
@@ -2295,6 +2307,9 @@ ipcMain.on('sleep-display', (e, hwid) => turnOffDisplayDDC(hwid, true))
ipcMain.on('set-vcp', (e, values) => {
updateBrightnessThrottle(values.monitor, values.value, false, true, values.code)
})
+ipcMain.on('set-sdr-brightness', (e, values) => {
+ updateBrightnessThrottle(values.monitor, values.value, false, true, "sdr")
+})
ipcMain.on('get-window-history', () => sendToAllWindows('window-history', windowHistory))
diff --git a/src/modules/windows-hdr/.gitignore b/src/modules/windows-hdr/.gitignore
new file mode 100644
index 00000000..86ff847a
--- /dev/null
+++ b/src/modules/windows-hdr/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+*.log*
+build
+.vscode/ipch
+.history
\ No newline at end of file
diff --git a/src/modules/windows-hdr/binding.gyp b/src/modules/windows-hdr/binding.gyp
new file mode 100644
index 00000000..811eeb6e
--- /dev/null
+++ b/src/modules/windows-hdr/binding.gyp
@@ -0,0 +1,18 @@
+{
+ "targets": [
+ {
+ "target_name": "windows-hdr",
+ "cflags!": [ "-fno-exceptions" ],
+ "cflags_cc!": [ "-fno-exceptions" ],
+ "conditions": [
+ ["OS=='win'", {
+ "sources": [ "windows-hdr.cc" ]
+ }],
+ ],
+ "include_dirs": [
+ "
+#include
+#include
+#include
+#include