├── .babelrc ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .sassrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── resources ├── appx │ ├── SmallTile.scale-100.png │ ├── Square150x150Logo.png │ ├── Square150x150Logo.scale-200.png │ ├── Square44x44Logo.png │ ├── Square44x44Logo.scale-200.png │ ├── Square44x44Logo.targetsize-48.png │ ├── Square44x44Logo.targetsize-48_altform-unplated.png │ ├── StoreLogo.png │ ├── StoreLogo.scale-200.png │ ├── Wide310x150Logo.png │ └── Wide310x150Logo.scale-200.png └── nsis │ └── installer.nsh └── src ├── Monitors.js ├── Translate.js ├── TranslateReact.js ├── Utils.js ├── assets ├── intro-video.mp4 ├── logo-512.png ├── logo-square.ico ├── logo-square.png ├── logo.ico ├── logo.png ├── noise.png └── tray-icons │ ├── dark │ ├── fluent.ico │ ├── fluent@1.25x.png │ ├── fluent@1.5x.png │ ├── fluent@1x.png │ ├── fluent@2x.png │ ├── fluent@4x.png │ ├── icon.ico │ ├── icon@1.25x.png │ ├── icon@1.5x.png │ ├── icon@1x.png │ ├── icon@2x.png │ ├── icon@4x.png │ ├── mdl2.ico │ ├── mdl2@1.25x.png │ ├── mdl2@1.5x.png │ ├── mdl2@1x.png │ ├── mdl2@2x.png │ └── mdl2@4x.png │ └── light │ ├── fluent.ico │ ├── fluent@1.25x.png │ ├── fluent@1.5x.png │ ├── fluent@1x.png │ ├── fluent@2x.png │ ├── fluent@4x.png │ ├── icon.ico │ ├── icon@1.25x.png │ ├── icon@1.5x.png │ ├── icon@1x.png │ ├── icon@2x.png │ ├── mdl2.ico │ ├── mdl2@1.25x.png │ ├── mdl2@1.5x.png │ ├── mdl2@1x.png │ └── mdl2@2x.png ├── components ├── BrightnessPanel.jsx ├── DDCCISliders.jsx ├── IntroWindow.jsx ├── MonitorFeatures.jsx ├── MonitorInfo.jsx ├── SafeRender.jsx ├── SettingsOption.jsx ├── SettingsWindow.jsx ├── Slider.jsx └── Titlebar.jsx ├── css ├── intro.scss ├── mica.scss ├── page.scss ├── panel.scss ├── settings-option.scss ├── slider.scss └── vars.scss ├── electron.js ├── hooks └── useObject.js ├── html ├── index.html ├── intro.html └── settings.html ├── intro-preload.js ├── intro.js ├── localization ├── ar.json ├── az.json ├── bn.json ├── ckb.json ├── cs.json ├── de.json ├── el.json ├── en.json ├── es.json ├── fa.json ├── fi.json ├── fr.json ├── hi.json ├── hr.json ├── hu.json ├── id.json ├── it.json ├── ja.json ├── ko.json ├── lt.json ├── nb.json ├── nl.json ├── pl.json ├── pt-BR.json ├── pt.json ├── ro.json ├── ru.json ├── sk.json ├── sv.json ├── ta.json ├── th.json ├── tr.json ├── uk.json ├── vi.json ├── zh-Hant.json └── zh_Hans.json ├── modules ├── acrylic │ ├── .gitignore │ ├── acrylic.cc │ ├── binding.gyp │ ├── index.js │ └── package.json ├── node-active-window │ ├── .eslintrc.json │ ├── .gitignore │ ├── .prettierrc.json │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── binding.gyp │ ├── dist │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── types.d.ts │ │ └── types.js │ ├── module │ │ └── windows │ │ │ ├── demo │ │ │ ├── Makefile │ │ │ └── main.cpp │ │ │ ├── napi │ │ │ ├── main.cpp │ │ │ ├── module.cpp │ │ │ └── module.h │ │ │ └── src │ │ │ ├── ActiveWindow.cpp │ │ │ └── ActiveWindow.h │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── types.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── node-ddcci │ ├── .clang-format │ ├── .gitignore │ ├── README.md │ ├── binding.gyp │ ├── ddcci.cc │ ├── index.d.ts │ ├── index.js │ ├── package-lock.json │ ├── package.json │ └── vcp.js ├── tt-windows-utils │ ├── .gitignore │ ├── binding.gyp │ ├── index.js │ ├── package.json │ ├── windows_app_startup.cc │ ├── windows_media_status.cc │ ├── windows_power_events.cc │ └── windows_window_utils.cc ├── win32-displayconfig │ ├── .gitignore │ ├── CONTRIBUTORS │ ├── COPYRIGHT │ ├── README.md │ ├── binding.gyp │ ├── index.d.ts │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── scripts │ │ ├── dumpextract.js │ │ ├── dumpquery.js │ │ └── watchmouse.js │ └── win32-displayconfig.cc ├── windows-hdr │ ├── .gitignore │ ├── binding.gyp │ ├── index.js │ ├── package.json │ └── windows-hdr.cc └── wmi-bridge │ ├── binding.gyp │ ├── example.js │ ├── index.js │ ├── package.json │ └── wmi-bridge.cc ├── monitor-rules.json ├── panel-preload.js ├── panel.js ├── parcel.js ├── parcelAPI.js ├── settings-preload.js ├── settings.js ├── vcp-codes.js └── wmi-bridge-test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react"], 3 | "plugins": [] 4 | } 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: "xanderfrangos" # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: "xanderfrangos" # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['paypal.me/xanderfrangos', 'xanderfrangos.com/crypto.md'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI Build 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | BRANCH_NAME: ${{ github.head_ref || github.ref_name }} 7 | 8 | jobs: 9 | build: 10 | name: Build all 11 | runs-on: windows-2022 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | name: Read repository 16 | 17 | - name: Set up Node.js 18 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 18 21 | cache: 'npm' 22 | 23 | - name: Get package.json info 24 | id: info 25 | uses: jaywcjlove/github-action-package@main 26 | 27 | - name: Load cache 28 | id: load-cache-node_modules 29 | uses: actions/cache/restore@v3 30 | with: 31 | path: ./node_modules 32 | key: node_modules-${{ hashFiles('package-lock.json') }} 33 | enableCrossOsArchive: true 34 | 35 | - name: Prepare dependencies for cache 36 | if: steps.load-cache-node_modules.outputs.cache-hit != 'true' 37 | run: npm i --install-links 38 | env: 39 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | 41 | - name: Save cache 42 | if: steps.load-cache-node_modules.outputs.cache-hit != 'true' 43 | id: save-cache-node_modules 44 | uses: actions/cache/save@v3 45 | with: 46 | path: ./node_modules 47 | key: ${{ steps.load-cache-node_modules.outputs.cache-primary-key }} 48 | enableCrossOsArchive: true 49 | 50 | - name: Build assets 51 | run: npm run parcel-build 52 | env: 53 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | 55 | - name: Build Win32 installer 56 | run: npm exec electron-builder -- --x64 --config.extraMetadata.versionBuild="${{ steps.info.outputs.version }}+${{ github.sha }}" --config.win.artifactName="Twinkle.Tray.v${{ steps.info.outputs.version }}.exe" --publish="never" 57 | env: 58 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | 60 | - uses: actions/upload-artifact@v4 61 | name: Upload Win32 installer 62 | with: 63 | name: twinkle-tray-exe-${{ steps.info.outputs.version }}-${{ github.sha }} 64 | path: dist/*.exe 65 | 66 | - name: Get current date 67 | id: date 68 | uses: Kaven-Universe/github-action-current-date-time@v1 69 | with: 70 | format: "YYYY.1MMDD.1HHmm" 71 | 72 | - name: Build x64 AppX 73 | if: ${{ env.BRANCH_NAME == 'master' || env.BRANCH_NAME == 'ci-test' }} 74 | run: npm exec electron-builder -- --x64 --win appx --config.npmRebuild=false --config.extraMetadata.versionBuild="${{ steps.info.outputs.version }}+${{ github.sha }}" --config.extraMetadata.version=${{ steps.date.outputs.time }} --config.extraMetadata.name=twinkle-tray-appx --config.win.artifactName="Twinkle.Tray.v${{ steps.info.outputs.version }}-store.appx" --publish="never" 75 | env: 76 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 77 | 78 | - uses: actions/upload-artifact@v4 79 | name: Upload x64 AppX 80 | if: ${{ env.BRANCH_NAME == 'master' || env.BRANCH_NAME == 'ci-test' }} 81 | with: 82 | name: twinkle-tray-appx-x64-${{ steps.info.outputs.version }}-${{ github.sha }} 83 | path: dist/*-store.appx 84 | 85 | - name: Build ARM64 AppX 86 | if: ${{ env.BRANCH_NAME == 'master' || env.BRANCH_NAME == 'ci-test' }} 87 | run: npm exec electron-builder -- --arm64 --win appx --config.extraMetadata.versionBuild="${{ steps.info.outputs.version }}+${{ github.sha }}" --config.extraMetadata.version=${{ steps.date.outputs.time }} --config.extraMetadata.name=twinkle-tray-appx --config.win.artifactName="Twinkle.Tray.v${{ steps.info.outputs.version }}-store-arm64.appx" --publish="never" 88 | env: 89 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 90 | 91 | - uses: actions/upload-artifact@v4 92 | name: Upload ARM64 AppX 93 | if: ${{ env.BRANCH_NAME == 'master' || env.BRANCH_NAME == 'ci-test' }} 94 | with: 95 | name: twinkle-tray-appx-arm64-${{ steps.info.outputs.version }}-${{ github.sha }} 96 | path: dist/*-store-arm64.appx 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | 4 | # production 5 | build/ 6 | dist/ 7 | 8 | # misc 9 | .cache 10 | .DS_Store 11 | .env.local 12 | .env.development.local 13 | .env.test.local 14 | .env.production.local 15 | 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | .idea 20 | 21 | .log 22 | logs/ 23 | *.pfx 24 | .env 25 | cache/ 26 | debug.log 27 | bin/ 28 | -------------------------------------------------------------------------------- /.sassrc: -------------------------------------------------------------------------------- 1 | { 2 | silenceDeprecations: ["legacy-js-api"] 3 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "algorithm": "cpp", 4 | "array": "cpp", 5 | "atomic": "cpp", 6 | "bit": "cpp", 7 | "cctype": "cpp", 8 | "charconv": "cpp", 9 | "chrono": "cpp", 10 | "clocale": "cpp", 11 | "cmath": "cpp", 12 | "compare": "cpp", 13 | "complex": "cpp", 14 | "concepts": "cpp", 15 | "condition_variable": "cpp", 16 | "coroutine": "cpp", 17 | "csignal": "cpp", 18 | "cstdarg": "cpp", 19 | "cstddef": "cpp", 20 | "cstdint": "cpp", 21 | "cstdio": "cpp", 22 | "cstdlib": "cpp", 23 | "cstring": "cpp", 24 | "ctime": "cpp", 25 | "cwchar": "cpp", 26 | "deque": "cpp", 27 | "exception": "cpp", 28 | "format": "cpp", 29 | "forward_list": "cpp", 30 | "fstream": "cpp", 31 | "functional": "cpp", 32 | "initializer_list": "cpp", 33 | "iomanip": "cpp", 34 | "ios": "cpp", 35 | "iosfwd": "cpp", 36 | "iostream": "cpp", 37 | "istream": "cpp", 38 | "iterator": "cpp", 39 | "limits": "cpp", 40 | "list": "cpp", 41 | "locale": "cpp", 42 | "map": "cpp", 43 | "memory": "cpp", 44 | "mutex": "cpp", 45 | "new": "cpp", 46 | "numeric": "cpp", 47 | "optional": "cpp", 48 | "ostream": "cpp", 49 | "queue": "cpp", 50 | "ratio": "cpp", 51 | "shared_mutex": "cpp", 52 | "sstream": "cpp", 53 | "stdexcept": "cpp", 54 | "stop_token": "cpp", 55 | "streambuf": "cpp", 56 | "string": "cpp", 57 | "system_error": "cpp", 58 | "thread": "cpp", 59 | "tuple": "cpp", 60 | "type_traits": "cpp", 61 | "typeinfo": "cpp", 62 | "unordered_map": "cpp", 63 | "utility": "cpp", 64 | "vector": "cpp", 65 | "xfacet": "cpp", 66 | "xhash": "cpp", 67 | "xiosbase": "cpp", 68 | "xlocale": "cpp", 69 | "xlocbuf": "cpp", 70 | "xlocinfo": "cpp", 71 | "xlocmes": "cpp", 72 | "xlocmon": "cpp", 73 | "xlocnum": "cpp", 74 | "xloctime": "cpp", 75 | "xmemory": "cpp", 76 | "xstddef": "cpp", 77 | "xstring": "cpp", 78 | "xtr1common": "cpp", 79 | "xtree": "cpp", 80 | "xutility": "cpp" 81 | } 82 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Xander Frangos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twinkle-tray", 3 | "version": "1.16.7", 4 | "description": "Manage the brightness of monitors from your system tray", 5 | "main": "src/electron.js", 6 | "scripts": { 7 | "start": "electron . --dev", 8 | "parcel": "node src/parcel.js --mode dev", 9 | "parcel-build": "node src/parcel.js --mode build", 10 | "clean": "npm ci", 11 | "dev": "electron . --dev", 12 | "electron-build": "electron-builder --x64", 13 | "postinstall": "electron-builder install-app-deps", 14 | "appx": "npm run parcel-build && electron-builder --x64 -w appx -c.extraMetadata.name=twinkle-tray-appx -c.win.artifactName=\"${productName} v${version}-store.${ext}\"", 15 | "appx-unsigned": "npm run parcel-build && electron-builder --x64 -w appx -c.appx.publisher=\"CN=AppModelSamples, OID.2.25.311729368913984317654407730594956997722=1\" -c.extraMetadata.name=twinkle-tray-appx -c.win.artifactName=\"${productName} v${version}-unsigned.${ext}\"", 16 | "portable": "npm run parcel-build && electron-builder --x64 -w zip -c.extraMetadata.name=twinkle-tray-portable -c.win.artifactName=\"${productName} v${version}-portable.${ext}\" -c.win.compression=normal", 17 | "arm64": "npm run parcel-build && electron-builder --arm64 -c.extraMetadata.name=twinkle-tray-arm64 -c.win.artifactName=\"${productName} v${version}-arm64.${ext}\" -c.win.compression=normal", 18 | "rebuild-ddcci-dev": "npm rebuild @hensm/ddcci", 19 | "appx-arm64": "npm run parcel-build && electron-builder --arm64 -w appx -c.extraMetadata.name=twinkle-tray-appx -c.win.artifactName=\"${productName} v${version}-store-arm64.${ext}\"", 20 | "build": "npm ci & npm run parcel-build && npm run electron-build", 21 | "build-all": "npm run build && npm run appx && npm run appx-arm64", 22 | "test-wmibridge": "node src/modules/wmi-bridge/example.js" 23 | }, 24 | "keywords": [ 25 | "electron", 26 | "react", 27 | "brightness", 28 | "set-brightness", 29 | "windows" 30 | ], 31 | "author": "Xander Frangos", 32 | "license": "MIT", 33 | "dependencies": { 34 | "@hensm/ddcci": "file:src/modules/node-ddcci", 35 | "@paymoapp/active-window": "file:src/modules/node-active-window", 36 | "acrylic": "file:src/modules/acrylic", 37 | "color": "^4.2.3", 38 | "ga4-mp": "^1.0.3", 39 | "global-mouse-events": "^1.2.0", 40 | "markdown-to-jsx": "^7.5.0", 41 | "native-reg": "^1.1.1", 42 | "tt-windows-utils": "file:src/modules/tt-windows-utils", 43 | "stackblur-canvas": "^2.7.0", 44 | "studio-display-control": "^0.2.0", 45 | "suncalc": "^1.9.0", 46 | "win32-displayconfig": "file:src/modules/win32-displayconfig", 47 | "windows-accent-colors": "^1.0.1", 48 | "windows-hdr": "file:src/modules/windows-hdr", 49 | "wmi-bridge": "file:src/modules/wmi-bridge", 50 | "wmi-client": "^0.5.0" 51 | }, 52 | "build": { 53 | "productName": "Twinkle Tray", 54 | "appId": "com.xanderfrangos.twinkle-tray", 55 | "compression": "maximum", 56 | "directories": { 57 | "buildResources": "resources" 58 | }, 59 | "files": [ 60 | "src/electron.js", 61 | "src/panel-preload.js", 62 | "src/Translate.js", 63 | "src/settings-preload.js", 64 | "src/intro-preload.js", 65 | "src/monitor-rules.json", 66 | "src/localization/*", 67 | "src/Utils.js", 68 | "src/Monitors.js", 69 | "src/wmi-bridge-test.js", 70 | "src/vcp-codes.js", 71 | "src/assets/tray-icons/dark/*", 72 | "src/assets/tray-icons/light/*", 73 | "build/*", 74 | "!node_modules/@hensm/ddcci/build", 75 | "node_modules/@hensm/ddcci/build/Release/ddcci.node", 76 | "!src/modules/node-ddcci/build", 77 | "src/modules/node-ddcci/build/Release/ddcci.node", 78 | "!node_modules/node-addon-api/doc", 79 | "!node_modules/win32-displayconfig/node_modules*", 80 | "!node_modules/global-mouse-events/node_modules", 81 | "!node_modules/electron-acrylic-window/node_modules", 82 | "!node_modules/tt-windows-utils/node_modules", 83 | "!node_modules/windows-accent-colors/node_modules", 84 | "!node_modules/**/node-gyp/*", 85 | "!node_modules/**/*.ipdb", 86 | "!node_modules/**/*.pdb", 87 | "!node_modules/**/*.iobj", 88 | "!node_modules/**/*.obj", 89 | "!node_modules/**/*.lib", 90 | "!node_modules/**/*.h", 91 | "!node_modules/**/*.md", 92 | "!node_modules/**/*.c", 93 | "!node_modules/**/*.map", 94 | "!node_modules/**/*.log", 95 | "!node_modules/**/*.tlog", 96 | "!node_modules/**/*.vcxproj", 97 | "!node_modules/**/*.filters", 98 | "!node_modules/**/wmic_centos_x64", 99 | "!node_modules/**/wmic_ubuntu_x64", 100 | "!node_modules/sharp/vendor/*", 101 | "!node_modules/wmi-bridge/bin/*", 102 | "!node_modules/native-reg/prebuilds/*", 103 | "node_modules/native-reg/prebuilds/${platform}-${arch}/*", 104 | "!node_modules/native-reg/src/*", 105 | "!node_modules/acrylic/bin/**/*.node", 106 | "!node_modules/tt-windows-utils/bin/**/*.node", 107 | "!node_modules/win32-displayconfig/bin/**/*.node", 108 | "!node_modules/@paymoapp/active-window/module/*", 109 | "!node_modules/@paymoapp/active-window/src/*", 110 | "!node_modules/@paymoapp/active-window/build/Release/obj/*", 111 | "!node_modules/@paymoapp/active-window/*.json", 112 | "node_modules/@paymoapp/active-window/package.json", 113 | "!node_modules/iconv-lite", 114 | "!node_modules/cacache/*", 115 | "!node_modules/path-scurry/*", 116 | "!node_modules/tar/*", 117 | "!node_modules/@isaacs/*", 118 | "!node_modules/are-we-there-yet", 119 | "!node_modules/make-fetch-happen/*", 120 | "!node_modules/@npmcli/*", 121 | "!node_modules/node-gyp/*", 122 | "!node_modules/glob", 123 | "!node_modules/@pkgjs", 124 | "!node_modules/minizlib/*", 125 | "!node_modules/usb/libusb", 126 | "!node_modules/usb/prebuilds/*", 127 | "node_modules/usb/prebuilds/${platform}-${arch}/*" 128 | ], 129 | "extraResources": [ 130 | "node_modules\\wmi-client\\lib\\*", 131 | "node_modules\\wmi-client\\scripts\\*", 132 | "node_modules\\wmi-client\\index.js", 133 | "node_modules\\wmi-client\\package.json", 134 | "node_modules\\wmi-client\\.jscsrc" 135 | ], 136 | "asar": true, 137 | "asarUnpack": [ 138 | "node_modules\\wmi-client\\**\\*", 139 | "node_modules\\sharp\\**\\*", 140 | "**\\*.node", 141 | "src\\assets\\tray-icons\\dark\\*.ico", 142 | "src\\assets\\tray-icons\\light\\*.ico", 143 | "src\\assets\\logo.ico" 144 | ], 145 | "win": { 146 | "target": [ 147 | "nsis" 148 | ], 149 | "artifactName": "${productName} v${version}.${ext}", 150 | "icon": "src/assets/logo.ico", 151 | "publisherName": "Xander Frangos" 152 | }, 153 | "appx": { 154 | "publisherDisplayName": "Xander Frangos", 155 | "applicationId": "TwinkleTray", 156 | "displayName": "Twinkle Tray", 157 | "identityName": "38002AlexanderFrangos.TwinkleTray", 158 | "publisher": "CN=B8E9A58B-32A7-4C6C-A474-D4BE2A3CEAD8", 159 | "showNameOnTiles": false, 160 | "backgroundColor": "#6b479c", 161 | "addAutoLaunchExtension": true 162 | }, 163 | "nsis": { 164 | "deleteAppDataOnUninstall": true, 165 | "include": "resources/nsis/installer.nsh", 166 | "warningsAsErrors": false 167 | } 168 | }, 169 | "devDependencies": { 170 | "@babel/core": "^7.23.6", 171 | "@babel/preset-env": "^7.23.6", 172 | "@babel/runtime": "^7.23.6", 173 | "@babel/plugin-proposal-class-properties": "^7.18.6", 174 | "@babel/preset-react": "^7.23.3", 175 | "cross-env": "^7.0.3", 176 | "electron": "^34.5.5", 177 | "electron-builder": "22.14.13", 178 | "node-gyp": "^10.2.0", 179 | "bindings": "1.5.0", 180 | "node-addon-api": "^8.2.2", 181 | "parcel-bundler": "^1.12.5", 182 | "react": "18.3.1", 183 | "react-beautiful-dnd": "^13.1.1", 184 | "react-dom": "18.3.1", 185 | "sass": "^1.54.0" 186 | }, 187 | "overrides": { 188 | "node-gyp": "$node-gyp", 189 | "bindings@<1.5.0": "1.5.0", 190 | "node-addon-api@<8.2.2": "8.2.2", 191 | "node-active-window": { 192 | "node-gyp": "$node-gyp", 193 | "bindings": "$bindings", 194 | "node-addon-api": "$node-addon-api" 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /resources/appx/SmallTile.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/resources/appx/SmallTile.scale-100.png -------------------------------------------------------------------------------- /resources/appx/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/resources/appx/Square150x150Logo.png -------------------------------------------------------------------------------- /resources/appx/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/resources/appx/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /resources/appx/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/resources/appx/Square44x44Logo.png -------------------------------------------------------------------------------- /resources/appx/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/resources/appx/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /resources/appx/Square44x44Logo.targetsize-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/resources/appx/Square44x44Logo.targetsize-48.png -------------------------------------------------------------------------------- /resources/appx/Square44x44Logo.targetsize-48_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/resources/appx/Square44x44Logo.targetsize-48_altform-unplated.png -------------------------------------------------------------------------------- /resources/appx/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/resources/appx/StoreLogo.png -------------------------------------------------------------------------------- /resources/appx/StoreLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/resources/appx/StoreLogo.scale-200.png -------------------------------------------------------------------------------- /resources/appx/Wide310x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/resources/appx/Wide310x150Logo.png -------------------------------------------------------------------------------- /resources/appx/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/resources/appx/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /resources/nsis/installer.nsh: -------------------------------------------------------------------------------- 1 | !macro customUnInstall 2 | DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "electron.app.Twinkle Tray" 3 | !macroend -------------------------------------------------------------------------------- /src/Translate.js: -------------------------------------------------------------------------------- 1 | class Translate { 2 | constructor(localizationData = {}, fallbackData = {}) { 3 | this.localizationData = localizationData 4 | this.fallbackData = fallbackData 5 | 6 | // getString shorthand 7 | this.t = this.getString 8 | this.h = this.getHTML 9 | } 10 | 11 | setLocalizationData(data = {}, fallback = {}) { 12 | this.localizationData = data 13 | this.fallbackData = fallback 14 | } 15 | 16 | makeTranslation(string, args = []) { 17 | let outString = string 18 | for (let i = 1; i <= args.length; i++) { 19 | outString = outString.replace(`{{${i}}}`, args[i - 1]) 20 | } 21 | return outString 22 | } 23 | 24 | getString(key, ...args) { 25 | if (this.localizationData[key] !== undefined && this.localizationData[key] !== "") { 26 | return this.makeTranslation(this.localizationData[key], args) 27 | } else if (this.fallbackData[key] !== undefined & this.fallbackData[key] !== "") { 28 | return this.makeTranslation(this.fallbackData[key], args) 29 | } else { 30 | return "" 31 | } 32 | } 33 | getHTML(key, ...args) { 34 | return this.getString(key, args) 35 | } 36 | 37 | } 38 | 39 | module.exports = Translate -------------------------------------------------------------------------------- /src/TranslateReact.js: -------------------------------------------------------------------------------- 1 | import Translate from "./Translate" 2 | import React from "react"; 3 | 4 | class TranslateReact extends Translate { 5 | 6 | getHTML(key, ...args) { 7 | if (this.localizationData[key] !== undefined) { 8 | return () 11 | } else if (this.fallbackData[key] !== undefined) { 12 | return () 15 | } else { 16 | return key 17 | } 18 | } 19 | 20 | } 21 | 22 | module.exports = TranslateReact -------------------------------------------------------------------------------- /src/assets/intro-video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/intro-video.mp4 -------------------------------------------------------------------------------- /src/assets/logo-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/logo-512.png -------------------------------------------------------------------------------- /src/assets/logo-square.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/logo-square.ico -------------------------------------------------------------------------------- /src/assets/logo-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/logo-square.png -------------------------------------------------------------------------------- /src/assets/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/logo.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/noise.png -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/fluent.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/fluent.ico -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/fluent@1.25x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/fluent@1.25x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/fluent@1.5x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/fluent@1.5x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/fluent@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/fluent@1x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/fluent@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/fluent@2x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/fluent@4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/fluent@4x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/icon.ico -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/icon@1.25x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/icon@1.25x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/icon@1.5x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/icon@1.5x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/icon@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/icon@1x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/icon@2x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/icon@4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/icon@4x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/mdl2.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/mdl2.ico -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/mdl2@1.25x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/mdl2@1.25x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/mdl2@1.5x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/mdl2@1.5x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/mdl2@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/mdl2@1x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/mdl2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/mdl2@2x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/dark/mdl2@4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/dark/mdl2@4x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/light/fluent.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/light/fluent.ico -------------------------------------------------------------------------------- /src/assets/tray-icons/light/fluent@1.25x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/light/fluent@1.25x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/light/fluent@1.5x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/light/fluent@1.5x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/light/fluent@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/light/fluent@1x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/light/fluent@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/light/fluent@2x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/light/fluent@4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/light/fluent@4x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/light/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/light/icon.ico -------------------------------------------------------------------------------- /src/assets/tray-icons/light/icon@1.25x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/light/icon@1.25x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/light/icon@1.5x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/light/icon@1.5x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/light/icon@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/light/icon@1x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/light/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/light/icon@2x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/light/mdl2.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/light/mdl2.ico -------------------------------------------------------------------------------- /src/assets/tray-icons/light/mdl2@1.25x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/light/mdl2@1.25x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/light/mdl2@1.5x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/light/mdl2@1.5x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/light/mdl2@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/light/mdl2@1x.png -------------------------------------------------------------------------------- /src/assets/tray-icons/light/mdl2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xanderfrangos/twinkle-tray/386c7537dbb8752fb28b47097b200c9ad0733bf7/src/assets/tray-icons/light/mdl2@2x.png -------------------------------------------------------------------------------- /src/components/DDCCISliders.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react" 2 | import { useObject } from "../hooks/useObject" 3 | import Slider from "./Slider" 4 | 5 | export default function DDCCISliders(props) { 6 | const { monitor, name, monitorFeatures } = props 7 | 8 | const defaultValues = {} 9 | for(const vcp in monitor?.features) { 10 | defaultValues[vcp] = monitor?.features?.[vcp]?.[0] ?? 0 11 | } 12 | 13 | const [values, setValues] = useObject(defaultValues) 14 | 15 | let extraHTML = [] 16 | const featureSettings = window.settings?.monitorFeaturesSettings?.[monitor?.hwid[1]] 17 | 18 | if(monitor?.features) { 19 | let i = 0 20 | for(const vcp in monitor.features) { 21 | i++ 22 | 23 | if(vcp == "0x10" || vcp == "0x13" || vcp == "0xD6") { 24 | continue; // Skip if brightness or power state 25 | } 26 | 27 | const feature = monitor.features[vcp] 28 | if(feature && monitorFeatures?.[vcp] && !(featureSettings?.[vcp]?.linked)) { 29 | // Feature has a value, is enabled, and not linked 30 | if(vcp === "0x12") { 31 | // Contrast 32 | extraHTML.push( 33 |
34 |
35 | { setValues({ [vcp]: val }); setVCP(monitor.id, parseInt(vcp), val * (monitor.features[vcp][1] / 100)) }} scrollAmount={props.scrollAmount} /> 36 |
37 | ) 38 | } else if(vcp === "0x62") { 39 | // Volume 40 | extraHTML.push( 41 |
42 |
43 | { setValues({ [vcp]: val }); setVCP(monitor.id, parseInt(vcp), val * (monitor.features[vcp][1] / 100)) }} scrollAmount={props.scrollAmount} /> 44 |
45 | ) 46 | } else { 47 | // Custom 48 | const settings = featureSettings?.[vcp] ?? {} 49 | let icon 50 | if(settings?.iconType === "windows" && settings?.icon) { 51 | icon = () 52 | } else if(settings?.iconType === "text" && settings?.iconText) { 53 | icon = ({settings.iconText}) 54 | } else { 55 | // Default 56 | icon = () 57 | } 58 | extraHTML.push( 59 |
60 |
{ icon }
61 | { setValues({ [vcp]: val }); setVCP(monitor.id, parseInt(vcp), val * (monitor.features[vcp][1] / 100)) }} scrollAmount={props.scrollAmount} /> 62 |
63 | ) 64 | } 65 | } 66 | 67 | } 68 | } 69 | 70 | return ( 71 | <> 72 | {extraHTML} 73 | 74 | ) 75 | } 76 | 77 | function setVCP(monitor, code, value) { 78 | window.dispatchEvent(new CustomEvent("setVCP", { 79 | detail: { 80 | monitor, 81 | code, 82 | value 83 | } 84 | })) 85 | } -------------------------------------------------------------------------------- /src/components/IntroWindow.jsx: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import TranslateReact from "../TranslateReact" 3 | import AppLogo from "../assets/logo.png" 4 | import IntroVideo from "../assets/intro-video.mp4" 5 | 6 | let T = new TranslateReact({}, {}) 7 | 8 | export default class IntroWindow extends PureComponent { 9 | 10 | constructor(props) { 11 | super(props) 12 | } 13 | 14 | componentDidMount() { 15 | window.addEventListener("localizationUpdated", (e) => { T.setLocalizationData(e.detail.desired, e.detail.default); this.forceUpdate() }) 16 | window.ipc.send('request-localization') 17 | } 18 | 19 | render() { 20 | return ( 21 |
22 | 23 |
{T.t("INTRO_TITLE")}
24 |

{T.t("INTRO_INSTRUCTIONS")}

25 | 26 | {T.t("GENERIC_CLOSE")} 27 |
28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/MonitorInfo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react" 2 | import Slider from "./Slider" 3 | 4 | export default function MonitorInfo(props) { 5 | const { monitor, name } = props 6 | const [brightness, setBrightness] = useState(monitor?.features?.["0x10"] ? monitor?.features?.["0x10"][0] : 50) 7 | const [contrast, setContrast] = useState(monitor?.features?.["0x12"] ? monitor?.features?.["0x12"][0] : 50) 8 | const [volume, setVolume] = useState(monitor?.features?.["0x62"] ? monitor?.features?.["0x62"][0] : 50) 9 | const [powerState, setPowerState] = useState(monitor?.features?.["0xD6"] ? monitor?.features?.["0xD6"][0] : 50) 10 | const [sdr, setSDR] = useState(monitor.sdrLevel >= 0 ? monitor.sdrLevel : 50) 11 | const [manualVCP, setManualVCP] = useState("") 12 | const [manualValue, setManualValue] = useState("") 13 | 14 | let extraHTML = [] 15 | 16 | if (props.debug === true) { 17 | extraHTML.push( 18 |
19 |
Raw Brightness: {(monitor.type == "none" ? "Not supported" : monitor.brightnessRaw)} 20 |
Features: {(monitor.type == "ddcci" && monitor.features ? JSON.stringify(monitor.features) : "Unsupported")} 21 |
Order: {(monitor.order ? monitor.order : "0")} 22 |
Key: {monitor.key} 23 |
ID: {monitor.id} 24 |
Connection Type: {monitor.connector} 25 |

26 |
27 | ) 28 | } 29 | 30 | // Brightness 31 | if (monitor?.features?.["0x10"]) { 32 | extraHTML.push( 33 |
34 |
35 | { setBrightness(val); setVCP(monitor.id, 0x10, val * (monitor.features["0x10"][1] / 100)) }} scrolling={false} /> 36 |
37 | ) 38 | } 39 | 40 | // Contrast 41 | if (monitor?.features?.["0x12"]) { 42 | extraHTML.push( 43 |
44 |
45 | { setContrast(val); setVCP(monitor.id, 0x12, val * (monitor.features["0x12"][1] / 100)) }} scrolling={false} /> 46 |
47 | ) 48 | } 49 | 50 | // Volume 51 | if (monitor?.features?.["0x62"]) { 52 | extraHTML.push( 53 |
54 |
55 | { setVolume(val); setVCP(monitor.id, 0x62, val * (monitor.features["0x62"][1] / 100)) }} scrolling={false} /> 56 |
57 | ) 58 | } 59 | 60 | // Power State 61 | if (monitor?.features?.["0xD6"]) { 62 | extraHTML.push( 63 |
64 |
65 | { setPowerState(val); setVCP(monitor.id, 0xD6, val) }} scrolling={false} /> 66 |
67 | ) 68 | } 69 | 70 | // Manual VCP 71 | extraHTML.push( 72 |
73 | { setManualVCP(e.target.value) }} /> 74 | { setManualValue(e.target.value) }} /> 75 | setVCP(monitor.id, parseInt(manualVCP), parseInt(manualValue))}>Send VCP 76 |
77 | ) 78 | 79 | // SDR test 80 | extraHTML.push( 81 |
82 |
SDR
83 | { setSDR(val); setSDRBrightness(monitor.id, val) }} scrolling={false} /> 84 |
85 | ) 86 | 87 | return ( 88 |
89 |
90 |
{monitor.name}
91 |

Name: {name} 92 |
Internal name: {monitor.hwid[1]} 93 |
Communication Method: {getDebugMonitorType((monitor.type === "ddcci" && monitor.highLevelSupported?.brightness ? "ddcci-hl" : monitor.type))} 94 |
Current Brightness: {(monitor.type == "none" ? "Not supported" : monitor.brightness)} 95 |
Max Brightness: {(monitor.type !== "ddcci" ? "Not supported" : monitor.brightnessMax)} 96 |
Brightness Normalization: {(monitor.type == "none" ? "Not supported" : monitor.min + " - " + monitor.max)} 97 |

98 | {extraHTML} 99 |
100 | ) 101 | } 102 | 103 | function setVCP(monitor, code, value) { 104 | window.dispatchEvent(new CustomEvent("setVCP", { 105 | detail: { 106 | monitor, 107 | code, 108 | value 109 | } 110 | })) 111 | } 112 | 113 | function setSDRBrightness(monitor, value) { 114 | window.dispatchEvent(new CustomEvent("set-sdr-brightness", { 115 | detail: { 116 | monitor, 117 | value 118 | } 119 | })) 120 | } 121 | 122 | function getDebugMonitorType(type) { 123 | if (type == "none") { 124 | return (<>None ) 125 | } else if (type == "ddcci") { 126 | return (<>DDC/CI ) 127 | } else if (type == "ddcci-hl") { 128 | return (<>DDC/CI (HL) ) 129 | } else if (type == "wmi") { 130 | return (<>WMI ) 131 | } else if (type == "studio-display") { 132 | return (<>Studio Display ) 133 | } else { 134 | return (<>Unknown ({type}) ) 135 | } 136 | } -------------------------------------------------------------------------------- /src/components/SafeRender.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default class SafeRender extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { hasError: false, error: "" }; 7 | } 8 | 9 | static getDerivedStateFromError(error) { 10 | return { hasError: true, error: error }; 11 | } 12 | 13 | componentDidCatch(error, errorInfo) { 14 | console.log(error) 15 | this.setState({ 16 | hasError: true, 17 | error: error, 18 | errorInfo: errorInfo?.componentStack 19 | }) 20 | } 21 | 22 | render() { 23 | if (this.state.hasError) { 24 | return (
Error: {JSON.stringify(this.state.error)}
{this.state.errorInfo}
); 25 | } 26 | 27 | return <>{this.props?.children}; 28 | } 29 | } -------------------------------------------------------------------------------- /src/components/SettingsOption.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react" 2 | import SafeRender from "./SafeRender" 3 | 4 | export function SettingsOption(props) { 5 | const [expanded, setExpanded] = useState((props.startExpanded ?? false)) 6 | 7 | const title = (props.title ?
{props.title}
: null) 8 | const icon = (props.icon ?
: null) 9 | const description = (props.description ?
{props.description}
: null) 10 | const elem = (props.content ?
{props.content}
: null) 11 | const input = (props.input ?
{props.input}
: null) 12 | const className = `settings-option-elem ${props.className ?? ""}` 13 | 14 | return ( 15 | 16 |
17 |
18 | { icon } 19 |
20 | { title } 21 | { description } 22 | { elem } 23 |
24 | { input } 25 |
setExpanded(!expanded)}>
26 |
27 |
28 |
29 | { props.children } 30 |
31 |
32 |
33 |
34 | ) 35 | } 36 | 37 | export function SettingsChild(props) { 38 | const title = (props.title ?
{props.title}
: null) 39 | const icon = (props.icon ?
: null) 40 | const description = (props.description ?
{props.description}
: null) 41 | const elem = (props.content ?
{props.content}
: null) 42 | const children = (props.children ?
{props.children}
: null) 43 | const input = (props.input ?
{props.input}
: null) 44 | const className = `settings-child-elem ${props.className ?? ""}` 45 | 46 | return ( 47 |
48 |
49 | { icon } 50 |
51 | { title } 52 | { description } 53 | { elem } 54 | { children } 55 |
56 | { input } 57 |
58 |
59 | ) 60 | } -------------------------------------------------------------------------------- /src/components/Slider.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useEffect, Component } from "react" 3 | import PropTypes from 'prop-types'; 4 | 5 | export default class Slider extends Component { 6 | 7 | firingEvent = false 8 | handleChange = (event) => { 9 | if(event.target.value !== this.props.level) 10 | this.setState({ level: this.cap(event.target.value) }, this.fireChange) 11 | } 12 | 13 | handleWheel = (event) => { 14 | if (this.props.scrolling === false) return false; 15 | this.setState({ level: this.cap((this.state.level * 1) + Math.round(event.deltaY * -1 * 0.01 * (this.props.scrollAmount ?? 1))) }, this.fireChange) 16 | } 17 | 18 | fireChange = () => { 19 | if (this.firingEvent === false && this.props.onChange && typeof this.props.onChange == "function") { 20 | this.firingEvent = true 21 | this.props.onChange(this.cap(this.state.level) * 1, this) 22 | this.firingEvent = false 23 | } 24 | } 25 | 26 | getName = () => { 27 | if (this.props.name) { 28 | return ( 29 |
30 |
{(this.props.monitortype == "wmi" ? : )}
31 |
{this.props.name}
32 | {this.props.afterName} 33 |
34 | ) 35 | } 36 | } 37 | 38 | cap = (level) => { 39 | const min = (this.props.min || 0) * 1 40 | const max = (this.props.max || 100) * 1 41 | let capped = level * 1 42 | if (level < min) { 43 | capped = min 44 | } else if (level > max) { 45 | capped = max 46 | } 47 | return capped 48 | } 49 | 50 | progressStyle = () => { 51 | const min = (this.props.min || 0) * 1 52 | const max = (this.props.max || 100) * 1 53 | const level = this.cap((this.props.level || 0) * 1) 54 | return { width: (0 + (((level - min) * (100 / (max - min))))) + "%" } 55 | } 56 | 57 | constructor(props) { 58 | super(props); 59 | this.state = { 60 | level: this.cap((this.props.level === undefined ? 50 : this.props.level)), 61 | } 62 | //this.fireChange() 63 | } 64 | 65 | componentDidUpdate(oldProps) { 66 | if (oldProps.max != this.props.max || oldProps.min != this.props.min) { 67 | this.setState({ 68 | level: this.cap(this.props.level) 69 | }, this.fireChange()) 70 | } 71 | } 72 | 73 | render() { 74 | const min = (this.props.min || 0) * 1 75 | const max = (this.props.max || 100) * 1 76 | const level = this.cap(this.props.level) 77 | return ( 78 |
79 | {this.getName()} 80 |
81 |
82 | 83 |
84 |
85 | 86 |
87 |
88 | ); 89 | } 90 | 91 | }; -------------------------------------------------------------------------------- /src/components/Titlebar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default class Titlebar extends React.Component { 4 | 5 | constructor(props) { 6 | super(props) 7 | this.state = { 8 | active: false 9 | } 10 | } 11 | 12 | render() { 13 | return ( 14 |
15 |
16 |
{this.props.title || ""}
17 |
18 |
{ window.ipc.send("windowMinimize") }}> 19 |
20 |
21 |
{ window.ipc.send("windowToggleMaximize") }}> 22 |
23 |
24 |
{ window.ipc.send("windowClose")}}> 25 |
26 |
27 |
28 |
29 | ); 30 | } 31 | }; 32 | 33 | -------------------------------------------------------------------------------- /src/css/intro.scss: -------------------------------------------------------------------------------- 1 | body, 2 | html { 3 | background: transparent; 4 | } 5 | 6 | body { 7 | padding: 80px; 8 | box-sizing: border-box; 9 | } 10 | 11 | .page { 12 | background: var(--page-background); 13 | color: var(--tray-text); 14 | font-family: 'Segoe UI Variable Text', 'Segoe UI';; 15 | position: absolute; 16 | bottom: 10px; 17 | left: 10px; 18 | width: calc(100% - 20px); 19 | height: calc(100% - 58px); 20 | padding: 20px; 21 | box-sizing: border-box; 22 | border: 1px solid var(--window-border); 23 | user-select: none; 24 | overflow: visible; 25 | text-align: center; 26 | box-shadow: 0 3px 12px -8px black; 27 | line-height: 1; 28 | user-select: none; 29 | animation: introIn 1s; 30 | 31 | @keyframes introIn { 32 | 0% { 33 | opacity: 0; 34 | transform: scale(0.96); 35 | } 36 | 37 | 80% { 38 | opacity: 0; 39 | transform: scale(0.96); 40 | } 41 | 42 | 100% { 43 | opacity: 1; 44 | transform: scale(1); 45 | } 46 | } 47 | } 48 | 49 | $margin: 22px; 50 | 51 | img { 52 | width: 84px; 53 | height: 84px; 54 | margin: -62px auto 0; 55 | } 56 | 57 | .intro-title { 58 | font-size: 18px; 59 | font-weight: 700; 60 | margin: $margin auto 0; 61 | } 62 | 63 | p { 64 | max-width: 320px; 65 | margin: $margin auto 0; 66 | font-size: 14px; 67 | line-height: 20px; 68 | } 69 | 70 | video { 71 | margin: $margin auto 0; 72 | border: 1px solid rgba(122, 122, 122, 0.25); 73 | } 74 | 75 | .button { 76 | background: var(--window-border); 77 | border: 2px solid var(--window-border); 78 | line-height: 0; 79 | padding: 16px 0px; 80 | margin: 34px auto 0; 81 | display: block; 82 | font-size: 16px; 83 | width: 200px; 84 | cursor: pointer; 85 | 86 | &:hover { 87 | border-color: var(--input-border); 88 | } 89 | } 90 | 91 | 92 | 93 | // Windows 11 style 94 | body[data-is-win11="true"] { 95 | .page { 96 | background-color: var(--page-background); 97 | border: 1px solid var(--tray-border); 98 | border-radius: 8px; 99 | box-shadow: 0 8px 15px -5px rgb(0 0 0 / 75%); 100 | } 101 | .button { 102 | background: var(--button-background); 103 | color: var(--page-text); 104 | border-radius: 6px; 105 | border: 0; 106 | box-shadow: inset 0 0.25px 0 0.5px var(--button-border), 0 0.5px 1px 0 rgba(0, 0, 0, 0.203); 107 | &:hover { 108 | background: var(--button-highlight); 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /src/css/mica.scss: -------------------------------------------------------------------------------- 1 | #mica { 2 | position: absolute !important; 3 | top: 0; 4 | left: 0; 5 | width: 100vw; 6 | height: 100vh; 7 | overflow: hidden; 8 | background: var(--mica-base-color); 9 | 10 | .displays { 11 | width: 100vw; 12 | height: 100vh; 13 | position: relative; 14 | overflow: visible; 15 | mix-blend-mode: color; 16 | visibility: hidden; 17 | 18 | .blur { 19 | position: relative; 20 | width: 100%; 21 | height: 100%; 22 | opacity: 0.3; 23 | //filter: blur(100px); 24 | } 25 | 26 | img { 27 | display: block; 28 | } 29 | } 30 | .noise { 31 | position: absolute; 32 | top: 0; 33 | left: 0; 34 | background: url(../assets/noise.png) 0 0 repeat; 35 | width: 100vw; 36 | height: 100vh; 37 | opacity: 0.017; 38 | } 39 | } 40 | 41 | body[data-focused="false"] { 42 | #mica .blur { 43 | //opacity: 0; 44 | } 45 | } 46 | 47 | body[data-use-mica="false"], body[data-is-win11="false"], body[data-transparent="false"] { 48 | #mica { 49 | display: none; 50 | } 51 | } -------------------------------------------------------------------------------- /src/css/slider.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Inputs 3 | // 4 | 5 | .input--range { 6 | display: flex; 7 | flex-direction: row; 8 | justify-content: space-between; 9 | align-items: center; 10 | width: 100%; 11 | position: relative; 12 | 13 | .progress { 14 | background: var(--system-accent-medium); 15 | height: 2px; 16 | width: 100%; 17 | position: absolute; 18 | top: 50%; 19 | left: 0px; 20 | margin-top: -1px; 21 | pointer-events: none; 22 | } 23 | 24 | .rangeGroup { 25 | flex: 2; 26 | position: relative; 27 | } 28 | 29 | .range { 30 | display: block; 31 | appearance: none; 32 | width: 100%; 33 | background: transparent; 34 | border: none; 35 | padding: 0; 36 | margin: 0 15px 0 0; 37 | -webkit-appearance: none; 38 | 39 | &:hover { 40 | &::-webkit-slider-thumb { 41 | background: var(--tray-text); 42 | } 43 | } 44 | 45 | &::-webkit-progress-value { 46 | background-color: var(--system-accent-light); 47 | } 48 | 49 | &:focus { 50 | outline: none; 51 | } 52 | 53 | &::-webkit-slider-thumb { 54 | -webkit-appearance: none; 55 | height: 24px; 56 | width: 8px; 57 | box-sizing: content-box; 58 | border-radius: 20px; 59 | background: var(--system-accent-color); 60 | margin-top: -12px; 61 | z-index: 3; 62 | position: relative; 63 | } 64 | 65 | &::-webkit-slider-runnable-track { 66 | width: 100%; 67 | height: 2px; 68 | background: var(--slider-track-color); 69 | border-radius: 0px; 70 | transition: background-color 0.1s; 71 | border-left: 0; 72 | border-right: 0; 73 | box-sizing: border-box; 74 | 75 | } 76 | 77 | &::-webkit-slider-container { 78 | padding: 7px 0; 79 | } 80 | 81 | &:focus::-webkit-slider-runnable-track { 82 | //background: var(--slider-track-color-focus); 83 | } 84 | } 85 | 86 | .val { 87 | width: 52px; 88 | box-sizing: border-box; 89 | -webkit-appearance: none; 90 | background: none; 91 | outline: none; 92 | border: none; 93 | font-size: 22px; 94 | text-align: center; 95 | color: var(--tray-text); 96 | font-family: 'Segoe UI Variable Text', 'Segoe UI'; 97 | margin: 0; 98 | margin-right: -10px; 99 | margin-left: 5px; 100 | 101 | &::-webkit-inner-spin-button, 102 | &::-webkit-outer-spin-button { 103 | -webkit-appearance: none; 104 | margin: 0; 105 | } 106 | 107 | } 108 | 109 | &[data-height="short"] { 110 | padding-top: 4px; 111 | .val { 112 | text-align: left; 113 | padding: 0 0 0 4px; 114 | font-size: 16px; 115 | width: 32px; 116 | margin-left: 12px; 117 | line-height: 1; 118 | margin-bottom: -1px; 119 | } 120 | } 121 | } 122 | 123 | 124 | // 125 | // Monitors 126 | // 127 | 128 | .no-displays-message { 129 | margin-top: 13px; 130 | margin-bottom: 13px; 131 | } 132 | 133 | .monitor-item { 134 | 135 | &+.monitor-item { 136 | margin-top: 20px; 137 | } 138 | 139 | .name-row { 140 | display: flex; 141 | justify-content: flex-start; 142 | align-items: center; 143 | width: 100%; 144 | 145 | .title { 146 | font-size: 16px; 147 | flex: 1; 148 | } 149 | 150 | .icon { 151 | font-family: "Segoe MDL2 Assets"; 152 | font-size: 22px; 153 | margin-right: 10px; 154 | margin-bottom: -3px; 155 | } 156 | } 157 | 158 | .input--range { 159 | opacity: 1; 160 | transition: opacity 0.2s 0.05s; 161 | } 162 | } 163 | 164 | [data-refreshing="true"] .monitor-item .input--range { 165 | pointer-events: none; 166 | opacity: 0.35; 167 | } 168 | 169 | // Sun Valley tweaks 170 | #root[data-fluent-icons="true"] { 171 | 172 | .monitor-item .name-row .icon { 173 | font-family: "Segoe Fluent Icons", "Segoe MDL2 Assets"; 174 | } 175 | 176 | } 177 | 178 | // Windows 11 style 179 | body[data-is-win11="true"]:not(.ignoreWin11) { 180 | 181 | .input--range { 182 | 183 | .progress { 184 | height: 4px; 185 | border-radius: 50px; 186 | margin-top: 0px; 187 | background-color: var(--system-accent-dark1); 188 | @media screen and (prefers-color-scheme: dark) { 189 | background-color: var(--system-accent-light2); 190 | } 191 | } 192 | 193 | .rangeGroup { 194 | } 195 | 196 | .range { 197 | 198 | &:hover { 199 | &::-webkit-slider-thumb { 200 | 201 | } 202 | } 203 | 204 | &::-webkit-progress-value { 205 | 206 | } 207 | 208 | &:focus { 209 | 210 | } 211 | 212 | &::-webkit-slider-thumb { 213 | height: 10px; 214 | width: 10px; 215 | border-radius: 50px; 216 | background-color: var(--system-accent-dark1); 217 | margin-top: -3px; 218 | 219 | transform: scale(1); 220 | box-shadow: 0 0 0 4px var(--slider-thumb-border), 0 0.5px 3px 0 rgba(0, 0, 0, 0.603); 221 | 222 | height: 10px; 223 | width: 10px; 224 | 225 | transition: box-shadow 0.15s, transform 0.15s; 226 | will-change: box-shadow, transform; 227 | @media screen and (prefers-color-scheme: dark) { 228 | background-color: var(--system-accent-light2); 229 | } 230 | &:hover { 231 | box-shadow: 0 0 0 2px var(--slider-thumb-border), 0 0.5px 3px 0 rgba(0, 0, 0, 0.603); 232 | transform: scale(1.3); 233 | } 234 | } 235 | 236 | &::-webkit-slider-runnable-track { 237 | height: 4px; 238 | border-radius: 8px; 239 | margin-top: 3px; 240 | margin-top: 4px; 241 | } 242 | 243 | &::-webkit-slider-container { 244 | 245 | } 246 | 247 | &:focus::-webkit-slider-runnable-track { 248 | 249 | } 250 | } 251 | 252 | .val { 253 | 254 | &::-webkit-inner-spin-button, 255 | &::-webkit-outer-spin-button { 256 | 257 | } 258 | 259 | } 260 | } 261 | 262 | } -------------------------------------------------------------------------------- /src/css/vars.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Variables 3 | // 4 | 5 | :root { 6 | --system-accent-color: hsl(200, 80%, 83%); 7 | --system-accent-light: hsl(200, 80%, 83%); 8 | --system-accent-medium: hsl(200, 81%, 45%); 9 | --system-accent-dark: hsl(200, 80%, 20%); 10 | 11 | --slider-track-color: #727272; 12 | --slider-track-color-focus: #A5A5A5; 13 | 14 | --tray-background-base: #e4e4e4; 15 | --tray-background: var(--tray-background-base); 16 | --tray-background-transparent: rgba(219, 219, 219, 0.95); 17 | --tray-background-blur: rgba(219, 219, 219, 0); 18 | --tray-background-blur-start: rgba(219, 219, 219, 1); 19 | --tray-text: black; 20 | --tray-border: #aaaaaa; 21 | 22 | --page-background: #ffffff; 23 | --page-text: #000; 24 | --page-text-mid: #333; 25 | 26 | --sidebar-item-hover: #16161626; 27 | 28 | --window-border: #ddd; 29 | 30 | --titlebar-icon: #888; 31 | --titlebar-icon-hover: #000; 32 | 33 | --input-border: #555; 34 | --input-border-hover: #000; 35 | --input-active-background: #ddd; 36 | 37 | --button-background: #c0c0c0; 38 | --button-primary-background: var(--button-background); 39 | 40 | --slider-track-color: rgba(0, 0, 0, 0.42); 41 | --slider-track-color-focus: rgba(0, 0, 0, 0.65); 42 | 43 | 44 | body[data-theme="dark"] { 45 | --tray-background: var(--tray-background-base); 46 | --tray-background-base: #1f1f1f; 47 | --tray-background-transparent: rgba(41, 41, 41, 0.95); 48 | --tray-background-blur: rgba(41, 41, 41, 0); 49 | --tray-background-blur-start: rgba(41, 41, 41, 1); 50 | --tray-text: white; 51 | --tray-border: #434343; 52 | 53 | --page-background: #030303; 54 | --page-text: #fff; 55 | --page-text-mid: #bbb; 56 | 57 | --sidebar-item-hover: #ffffff26; 58 | 59 | --window-border: #222; 60 | 61 | --titlebar-icon: #888; 62 | --titlebar-icon-hover: #fff; 63 | 64 | --input-border: #6f6f6f; 65 | --input-border-hover: #fff; 66 | --input-active-background: #444; 67 | 68 | --button-background: #454545; 69 | --button-primary-background: var(--button-background); 70 | 71 | --slider-track-color: rgba(255, 255, 255, 0.42); 72 | --slider-track-color-focus: rgba(255, 255, 255, 0.65); 73 | } 74 | 75 | } 76 | 77 | 78 | 79 | body[data-transparent="true"][data-acrylic="false"] { 80 | .window-base { 81 | --tray-background: var(--tray-background-transparent) !important; 82 | } 83 | } 84 | 85 | body[data-transparent="true"][data-acrylic="true"][data-use-native-animation="false"] { 86 | .window-base { 87 | //--tray-background: var(--tray-background-blur-start) !important; 88 | background: transparent !important; 89 | background: var(--tray-background-blur-start) !important; 90 | } 91 | } 92 | 93 | body[data-transparent="true"][data-acrylic="true"][data-use-native-animation="false"][data-colored-taskbar="true"] { 94 | .window-base { 95 | //--tray-background: var(--tray-background-blur-start) !important; 96 | background: var(--system-accent-transparent) !important; 97 | } 98 | } 99 | 100 | body[data-transparent="true"][data-acrylic="true"][data-use-native-animation="false"][data-acrylic-show="true"], 101 | body[data-transparent="true"][data-acrylic="true"][data-use-native-animation="false"][data-acrylic-show="true"][data-colored-taskbar="true"] { 102 | .window-base { 103 | background: var(--tray-background-blur) !important; 104 | background: transparent !important; 105 | } 106 | } 107 | 108 | body[data-transparent="true"][data-acrylic="true"][data-use-native-animation="true"] { 109 | .window-base { 110 | background: transparent !important; 111 | } 112 | } 113 | 114 | html body[data-theme="dark"] { 115 | &[data-colored-taskbar="true"] { 116 | .window-base { 117 | --tray-background: var(--system-accent-dark) !important; 118 | } 119 | 120 | &[data-transparent="true"] { 121 | .window-base { 122 | --tray-background: var(--system-accent-transparent) !important; 123 | } 124 | } 125 | } 126 | } 127 | 128 | 129 | body[data-colored-taskbar="true"] { 130 | div#panel { 131 | --tray-background-base: #1f1f1f; 132 | --tray-background-transparent: var(--system-accent-transparent); 133 | --tray-background: var(--system-accent-dark1) !important; 134 | --tray-background-blur: rgba(41, 41, 41, 0); 135 | --tray-background-blur-start: rgba(41, 41, 41, 1); 136 | --tray-text: white; 137 | --tray-border: #434343; 138 | 139 | --page-background: #030303; 140 | --page-text: #fff; 141 | --page-text-mid: #bbb; 142 | 143 | --window-border: #222; 144 | 145 | --titlebar-icon: #888; 146 | --titlebar-icon-hover: #fff; 147 | 148 | --input-border: #6f6f6f; 149 | --input-border-hover: #fff; 150 | --input-active-background: #444; 151 | 152 | --slider-track-color: rgba(255, 255, 255, 0.42); 153 | --slider-track-color-focus: rgba(255, 255, 255, 0.65); 154 | 155 | #mica { 156 | background-color: transparent !important; 157 | .displays { 158 | .blur { 159 | opacity: 0.3 !important; 160 | } 161 | } 162 | } 163 | } 164 | } 165 | 166 | body[data-colored-taskbar="true"][data-transparent="false"][data-acrylic="false"], html body[data-is-win11="true"][data-colored-taskbar="true"] { 167 | div#panel { 168 | --tray-background: var(--system-accent-transparent) !important; 169 | --tray-background: var(--system-accent-dark2) !important; 170 | } 171 | } 172 | 173 | 174 | 175 | // Windows 11 style 176 | html body[data-is-win11="true"], html body[data-is-win11="true"] { 177 | 178 | --page-background: #f1f3f9 !important; 179 | --page-section-bg: #ffffff98; 180 | --tray-background: #f2f2f2 !important; 181 | --tray-background-transparent: #f2f2f2 !important; 182 | --tray-titlebar-bg: #eeeeee; 183 | --slider-track-color: #868686; 184 | --slider-thumb-border: #ffffff; 185 | --button-background: #ffffffc4; 186 | --button-highlight: #fffffffa; 187 | --button-border: #5c5c5c2b; 188 | --button-color-background: var(--system-accent-light2); 189 | --button-primary-background: var(--system-accent-dark1); 190 | --button-primary-background-hover: var(--system-accent-dark2); 191 | --button-color-border: var(--system-accent-light3); 192 | --input-border: rgba(0, 0, 0, 0.77); 193 | 194 | --mica-base-color: #f1f1f1; 195 | 196 | &[data-theme="dark"] { 197 | 198 | --page-background: #202020 !important; 199 | --page-section-bg: #ffffff0a; 200 | --tray-background: #242424 !important; 201 | --tray-background-transparent: #242424 !important; 202 | --tray-titlebar-bg: rgba(0,0,0,0.18); 203 | --slider-track-color: #9b9b9b; 204 | --slider-thumb-border: #414141; 205 | --button-background: #8a8a8a2b; 206 | --button-highlight: #b1b1b123; 207 | --button-border: #9797972b; 208 | --button-color-background: var(--system-accent-light2); 209 | --button-primary-background: var(--system-accent-light3); 210 | --button-primary-background-hover: var(--system-accent-light2); 211 | --button-color-border: var(--system-accent-light3); 212 | --input-border: rgba(255, 255, 255, 0.77); 213 | 214 | --mica-base-color: #2f2f2f; 215 | 216 | &[data-use-mica="false"] { 217 | --tray-titlebar-bg: rgba(0,0,0,0.24); 218 | } 219 | } 220 | } 221 | 222 | // Disable high contrast mode from Windows 223 | @media (forced-colors: active) { 224 | :root { 225 | forced-color-adjust: none; 226 | } 227 | } -------------------------------------------------------------------------------- /src/hooks/useObject.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | export function useObject(initialState = {}) { 4 | 5 | const [state, setState] = useState(Object.assign({}, initialState)) 6 | const applyState = (newState) => { 7 | setState(prevState => ({ 8 | ...prevState, 9 | ...newState 10 | })) 11 | } 12 | 13 | return [state, applyState] 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Twinkle Tray Panel 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/html/intro.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Twinkle Tray 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/html/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Twinkle Tray Settings 8 | 9 | 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 |
28 |
29 |
30 | 31 |
32 |
33 |
34 |
35 |
36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/intro-preload.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer: ipc } = require('electron'); 2 | 3 | window.closeIntro = () => { 4 | ipc.send('close-intro') 5 | } 6 | 7 | function detectSunValley() { 8 | try { 9 | // Detect new Fluent Icons (Windows build 21327+) 10 | if(document.fonts.check("12px Segoe Fluent Icons")) { 11 | window.document.body.dataset.fluentIcons = true 12 | window.document.body.dataset.isWin11 = true 13 | } else { 14 | window.document.body.dataset.fluentIcons = false 15 | } 16 | // Detect new system font (Windows build 21376+) 17 | if(document.fonts.check("12px Segoe UI Variable Text")) { 18 | window.document.body.dataset.segoeUIVariable = true 19 | } else { 20 | window.document.body.dataset.segoeUIVariable = false 21 | } 22 | } catch(e) { 23 | console.log("Couldn't test for Sun Valley", e) 24 | } 25 | } 26 | 27 | window.addEventListener('load', () => { 28 | ipc.send('request-localization') 29 | detectSunValley() 30 | setTimeout(() => { 31 | document.getElementById("video").play() 32 | }, 2400) 33 | }) 34 | 35 | // Localization recieved 36 | ipc.on('localization-updated', (event, localization) => { 37 | window.dispatchEvent(new CustomEvent('localizationUpdated', { 38 | detail: localization 39 | })) 40 | }) 41 | 42 | window.ipc = ipc -------------------------------------------------------------------------------- /src/intro.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createRoot } from 'react-dom/client'; 3 | import IntroWindow from "./components/IntroWindow"; 4 | 5 | createRoot(document.getElementById("root")).render() -------------------------------------------------------------------------------- /src/localization/fa.json: -------------------------------------------------------------------------------- 1 | { 2 | "LANGUAGE": "فارسی", 3 | "GENERIC_OK": "تأیید", 4 | "GENERIC_CANCEL": "لغو", 5 | "GENERIC_SAVE": "ذخیره", 6 | "GENERIC_CLEAR": "پاک کردن", 7 | "GENERIC_OFF": "خاموش", 8 | "GENERIC_ON": "روشن", 9 | "GENERIC_QUIT": "خروج", 10 | "GENERIC_CLOSE": "بستن", 11 | "GENERIC_SETTINGS": "تنظیمات", 12 | "GENERIC_INSTALL": "نصب", 13 | "GENERIC_DISMISS": "رد کردن", 14 | "GENERIC_DISPLAY_SINGLE": "نمایشگر", 15 | "GENERIC_ALL_DISPLAYS": "همه‌ی نمایشگرها", 16 | "GENERIC_REFRESH_DISPLAYS": "بازفراخوانی نمایشگر‌ها", 17 | "GENERIC_MINIMUM": "حداقل", 18 | "GENERIC_MAXIMUM": "حداکثر", 19 | "GENERIC_NO_DISPLAYS": "نمایشگری یافت نشد. لطفاً یک نمایگشر به رایانه‌ی خود متصل کنید.", 20 | "GENERIC_NO_COMPATIBLE_DISPLAYS": "نمایشگر سازگار با برنامه یافت نشد. لطفاً بررسی کنید که «DCC/CI» برای نمایشگر‌هایتان فعال باشد.", 21 | "GENERIC_NO_DISPLAYS_SHORT": "نمایشگر سازگار با برنامه یافت نشد.", 22 | "GENERIC_SECONDS": "ثانیه", 23 | "GENERIC_MINUTES": "دقیقه", 24 | "GENERIC_DDC_WARNING": "قابلیت‌هایی از «DDC/CI» مانند کنترل وضعیت خاموش و روشن بودن، می‌توانند نمایشگر شما را در وضعیت غیر پاسخگو قرار دهند. با مسئولیت خوتان استفاده کنید.", 25 | "GENERIC_SPEED_INSTANT": "بلافاصله", 26 | "GENERIC_SPEED_SLOW": "آهسته", 27 | "GENERIC_SPEED_VERY_SLOW": "بسیار آهسته", 28 | "GENERIC_SPEED_NORMAL": "عادی", 29 | "GENERIC_SPEED_FAST": "سریع", 30 | "GENERIC_SPEED_VERY_FAST": "بسیار سریع", 31 | "GENERIC_PAUSE_TOD": "", 32 | "GENERIC_PAUSE_IDLE": "متوقف سازی تشخیص بلااستفاده", 33 | "PANEL_TITLE": "تنظیم روشنایی", 34 | "PANEL_UPDATE_AVAILABLE": "نسخه جدیدی موجود است", 35 | "PANEL_BUTTON_LINK_LEVELS": "اتصال میزان های روشنایی", 36 | "PANEL_BUTTON_TURN_OFF_DISPLAYS": "خاموش کردن نمایشگرها", 37 | "PANEL_LABEL_BRIGHTNESS": "روشنایی", 38 | "PANEL_LABEL_CONTRAST": "کنتراست", 39 | "PANEL_LABEL_OFF_ON": "وضعیت پاور", 40 | "PANEL_LABEL_COLOR_TEMPERATURE": "دمای رنگ", 41 | "PANEL_LABEL_VOLUME": "صدا", 42 | "PANEL_LABEL_TURN_OFF": "خاموش کردن", 43 | "INTRO_TITLE": "سلام Twinkle Tray!", 44 | "INTRO_INSTRUCTIONS": "احتمالا آیکون Twinkle Tray مخفی است. بهتر است آن را به یک مکان جدید انتقال دهید که به راحتی قابل دسترس باشد. برای راهنمایی به زیر مراجعه کنید.", 45 | "SETTINGS_TITLE": "تنظیمات Twinkle Tray", 46 | "SETTINGS_SIDEBAR_GENERAL": "تنظیمات کلی", 47 | "SETTINGS_SIDEBAR_MONITORS": "تنظیمات نمایشگر", 48 | "SETTINGS_SIDEBAR_FEATURES": "ویژگی های DDC/CI", 49 | "SETTINGS_SIDEBAR_TIME": "تنظیم زمان", 50 | "SETTINGS_SIDEBAR_HOTKEYS": "کلیدها و میانبرها", 51 | "SETTINGS_SIDEBAR_UPDATES": "به‌روز رسانی", 52 | "SETTINGS_GENERAL_TITLE": "تنظیمات کلی", 53 | "SETTINGS_GENERAL_STARTUP": "راه اندازی در زمان روشن شدن", 54 | "SETTINGS_GENERAL_BRIGHTNESS_STARTUP_TITLE": "اعمال روشنایی در زمان روشن شدن", 55 | "SETTINGS_GENERAL_BRIGHTNESS_STARTUP_DESC": "", 56 | "SETTINGS_GENERAL_THEME_TITLE": "", 57 | "SETTINGS_GENERAL_THEME_SYSTEM": "", 58 | "SETTINGS_GENERAL_THEME_DARK": "", 59 | "SETTINGS_GENERAL_THEME_LIGHT": "", 60 | "SETTINGS_GENERAL_LANGUAGE_TITLE": "زبان", 61 | "SETTINGS_GENERAL_LANGUAGE_SYSTEM": "", 62 | "SETTINGS_GENERAL_RESET_TITLE": "", 63 | "SETTINGS_GENERAL_RESET_DESC": "", 64 | "SETTINGS_GENERAL_RESET_BUTTON": "", 65 | "SETTINGS_GENERAL_ACRYLIC_TITLE": "", 66 | "SETTINGS_GENERAL_ACRYLIC_DESC": "", 67 | "SETTINGS_GENERAL_MICA_TITLE": "", 68 | "SETTINGS_GENERAL_MICA_DESC": "", 69 | "SETTINGS_GENERAL_ANALYTICS_TITLE": "", 70 | "SETTINGS_GENERAL_ANALYTICS_DESC": "", 71 | "SETTINGS_GENERAL_ANALYTICS_LINK": "", 72 | "SETTINGS_GENERAL_SCROLL_TITLE": "", 73 | "SETTINGS_GENERAL_SCROLL_DESC": "", 74 | "SETTINGS_GENERAL_TRAY_ICON_TITLE": "", 75 | "SETTINGS_GENERAL_TROUBLESHOOTING": "", 76 | "SETTINGS_GENERAL_DIS_MONITOR_FEATURES_TITLE": "", 77 | "SETTINGS_GENERAL_DIS_MONITOR_FEATURES_DESC": "", 78 | "SETTINGS_GENERAL_DIS_OVERLAY_TITLE": "", 79 | "SETTINGS_GENERAL_DIS_OVERLAY_DESC": "", 80 | "SETTINGS_MONITORS_RATE_TITLE": "", 81 | "SETTINGS_MONITORS_RATE_DESC": "", 82 | "SETTINGS_MONITORS_RATE_0": "", 83 | "SETTINGS_MONITORS_RATE_1": "", 84 | "SETTINGS_MONITORS_RATE_2": "", 85 | "SETTINGS_MONITORS_RATE_3": "", 86 | "SETTINGS_MONITORS_RATE_4": "", 87 | "SETTINGS_MONITORS_RENAME_TITLE": "", 88 | "SETTINGS_MONITORS_RENAME_DESC": "", 89 | "SETTINGS_MONITORS_ENTER_NAME": "", 90 | "SETTINGS_MONITORS_REORDER_TITLE": "", 91 | "SETTINGS_MONITORS_REORDER_DESC": "", 92 | "SETTINGS_MONITORS_NORMALIZE_TITLE": "", 93 | "SETTINGS_MONITORS_NORMALIZE_DESC": "", 94 | "SETTINGS_MONITORS_DETAILS_NAME": "", 95 | "SETTINGS_MONITORS_DETAILS_INTERNAL_NAME": "", 96 | "SETTINGS_MONITORS_DETAILS_COMMUNICATION": "", 97 | "SETTINGS_MONITORS_DETAILS_BRIGHTNESS": "", 98 | "SETTINGS_MONITORS_DETAILS_MAX_BRIGHTNESS": "", 99 | "SETTINGS_MONITORS_DETAILS_BRIGHTNESS_NORMALIZATION": "", 100 | "SETTINGS_MONITORS_HIDE_INTERNAL_TITLE": "", 101 | "SETTINGS_MONITORS_HIDE_INTERNAL_DESC": "", 102 | "SETTINGS_MONITORS_HIDE_DISPLAYS_TITLE": "", 103 | "SETTINGS_MONITORS_HIDE_DISPLAYS_DESC": "", 104 | "SETTINGS_FEATURES_DESCRIPTION": "", 105 | "SETTINGS_FEATURES_UNSUPPORTED": "", 106 | "SETTINGS_FEATURES_CUR_BRIGHTNESS_TITLE": "", 107 | "SETTINGS_FEATURES_CUR_BRIGHTNESS_DESC": "", 108 | "SETTINGS_TIME_TITLE": "", 109 | "SETTINGS_TIME_DESC": "", 110 | "SETTINGS_TIME_ADD": "", 111 | "SETTINGS_TIME_REMOVE": "", 112 | "SETTINGS_TIME_INDIVIDUAL_TITLE": "", 113 | "SETTINGS_TIME_INDIVIDUAL_DESC": "", 114 | "SETTINGS_TIME_TRANSITON_TITLE": "", 115 | "SETTINGS_TIME_TRANSITON_DESC": "", 116 | "SETTINGS_TIME_STARTUP_TITLE": "", 117 | "SETTINGS_TIME_STARTUP_DESC": "", 118 | "SETTINGS_TIME_IDLE_TITLE": "", 119 | "SETTINGS_TIME_IDLE_DESC": "", 120 | "SETTINGS_TIME_ANIMATE_TITLE": "", 121 | "SETTINGS_TIME_ANIMATE_DESC": "", 122 | "SETTINGS_HOTKEYS_TITLE": "", 123 | "SETTINGS_HOTKEYS_DESC": "", 124 | "SETTINGS_HOTKEYS_ADD": "", 125 | "SETTINGS_HOTKEYS_REMOVE": "", 126 | "SETTINGS_HOTKEYS_INCREASE": "", 127 | "SETTINGS_HOTKEYS_DECREASE": "", 128 | "SETTINGS_HOTKEYS_PRESS_KEYS_HINT": "", 129 | "SETTINGS_HOTKEYS_LEVEL_TITLE": "", 130 | "SETTINGS_HOTKEYS_LEVEL_DESC": "", 131 | "SETTINGS_HOTKEYS_TOD_TITLE": "", 132 | "SETTINGS_HOTKEYS_TOD_DESC": "", 133 | "SETTINGS_HOTKEYS_TOD_NONE": "", 134 | "SETTINGS_HOTKEYS_TOD_SOFT": "", 135 | "SETTINGS_HOTKEYS_TOD_HARD": "", 136 | "SETTINGS_HOTKEYS_TOD_BOTH": "", 137 | "SETTINGS_HOTKEYS_TOD_NOTE": "", 138 | "SETTINGS_HOTKEYS_BREAK_TITLE": "", 139 | "SETTINGS_HOTKEYS_BREAK_DESC": "", 140 | "SETTINGS_UPDATES_TITLE": "به‌روز رسانی", 141 | "SETTINGS_UPDATES_VERSION": "", 142 | "SETTINGS_UPDATES_AUTOMATIC_TITLE": "", 143 | "SETTINGS_UPDATES_AUTOMATIC_DESC": "", 144 | "SETTINGS_UPDATES_MS_STORE": "", 145 | "SETTINGS_UPDATES_AVAILABLE": "", 146 | "SETTINGS_UPDATES_NONE_AVAILABLE": "", 147 | "SETTINGS_UPDATES_DOWNLOADING": "", 148 | "SETTINGS_UPDATES_DOWNLOAD": "", 149 | "SETTINGS_UPDATES_CHANNEL": "", 150 | "SETTINGS_UPDATES_BRANCH_STABLE": "", 151 | "SETTINGS_UPDATES_BRANCH_BETA": "آزمایشی", 152 | "SETTINGS_FEATURES_POWER_OFF": "خاموش کردن", 153 | "GENERIC_DEFAULT": "پیش فرض", 154 | "GENERIC_OPTIONAL": "اختیاری", 155 | "PANEL_LABEL_TURN_ON": "روشن کردن", 156 | "GENERIC_DELETE": "حذف" 157 | } 158 | -------------------------------------------------------------------------------- /src/localization/ta.json: -------------------------------------------------------------------------------- 1 | { 2 | "LANGUAGE": "", 3 | "GENERIC_OK": "", 4 | "GENERIC_CANCEL": "", 5 | "GENERIC_SAVE": "", 6 | "GENERIC_CLEAR": "", 7 | "GENERIC_DELETE": "", 8 | "GENERIC_OFF": "", 9 | "GENERIC_ON": "", 10 | "GENERIC_QUIT": "", 11 | "GENERIC_CLOSE": "", 12 | "GENERIC_SETTINGS": "", 13 | "GENERIC_INSTALL": "", 14 | "GENERIC_DISMISS": "", 15 | "GENERIC_DEFAULT": "", 16 | "GENERIC_OPTIONAL": "", 17 | "GENERIC_DISPLAY_SINGLE": "", 18 | "GENERIC_ALL_DISPLAYS": "", 19 | "GENERIC_REFRESH_DISPLAYS": "", 20 | "GENERIC_MINIMUM": "", 21 | "GENERIC_MAXIMUM": "", 22 | "GENERIC_NO_DISPLAYS": "", 23 | "GENERIC_NO_COMPATIBLE_DISPLAYS": "", 24 | "GENERIC_NO_DISPLAYS_SHORT": "", 25 | "GENERIC_SECONDS": "", 26 | "GENERIC_MINUTES": "", 27 | "GENERIC_DDC_WARNING": "", 28 | "GENERIC_SPEED_INSTANT": "", 29 | "GENERIC_SPEED_SLOW": "", 30 | "GENERIC_SPEED_VERY_SLOW": "", 31 | "GENERIC_SPEED_NORMAL": "", 32 | "GENERIC_SPEED_FAST": "", 33 | "GENERIC_SPEED_VERY_FAST": "", 34 | "GENERIC_PAUSE_TOD": "", 35 | "GENERIC_PAUSE_IDLE": "", 36 | "PANEL_TITLE": "", 37 | "PANEL_UPDATE_AVAILABLE": "", 38 | "PANEL_BUTTON_LINK_LEVELS": "", 39 | "PANEL_BUTTON_TURN_OFF_DISPLAYS": "", 40 | "PANEL_LABEL_BRIGHTNESS": "", 41 | "PANEL_LABEL_CONTRAST": "", 42 | "PANEL_LABEL_OFF_ON": "", 43 | "PANEL_LABEL_COLOR_TEMPERATURE": "", 44 | "PANEL_LABEL_VOLUME": "", 45 | "PANEL_LABEL_TURN_OFF": "", 46 | "PANEL_LABEL_TURN_ON": "", 47 | "INTRO_TITLE": "", 48 | "INTRO_INSTRUCTIONS": "", 49 | "SETTINGS_TITLE": "", 50 | "SETTINGS_SIDEBAR_GENERAL": "", 51 | "SETTINGS_SIDEBAR_MONITORS": "", 52 | "SETTINGS_SIDEBAR_FEATURES": "", 53 | "SETTINGS_SIDEBAR_TIME": "", 54 | "SETTINGS_SIDEBAR_HOTKEYS": "", 55 | "SETTINGS_SIDEBAR_UPDATES": "", 56 | "SETTINGS_GENERAL_TITLE": "", 57 | "SETTINGS_GENERAL_STARTUP": "", 58 | "SETTINGS_GENERAL_BRIGHTNESS_STARTUP_TITLE": "", 59 | "SETTINGS_GENERAL_BRIGHTNESS_STARTUP_DESC": "", 60 | "SETTINGS_GENERAL_THEME_TITLE": "", 61 | "SETTINGS_GENERAL_THEME_SYSTEM": "", 62 | "SETTINGS_GENERAL_THEME_DARK": "", 63 | "SETTINGS_GENERAL_THEME_LIGHT": "", 64 | "SETTINGS_GENERAL_LANGUAGE_TITLE": "", 65 | "SETTINGS_GENERAL_LANGUAGE_SYSTEM": "", 66 | "SETTINGS_GENERAL_RESET_TITLE": "", 67 | "SETTINGS_GENERAL_RESET_DESC": "", 68 | "SETTINGS_GENERAL_RESET_BUTTON": "", 69 | "SETTINGS_GENERAL_ACRYLIC_TITLE": "", 70 | "SETTINGS_GENERAL_ACRYLIC_DESC": "", 71 | "SETTINGS_GENERAL_MICA_TITLE": "", 72 | "SETTINGS_GENERAL_MICA_DESC": "", 73 | "SETTINGS_GENERAL_ANALYTICS_TITLE": "", 74 | "SETTINGS_GENERAL_ANALYTICS_DESC": "", 75 | "SETTINGS_GENERAL_ANALYTICS_LINK": "", 76 | "SETTINGS_GENERAL_SCROLL_TITLE": "", 77 | "SETTINGS_GENERAL_SCROLL_DESC": "", 78 | "SETTINGS_GENERAL_TRAY_ICON_TITLE": "", 79 | "SETTINGS_GENERAL_TROUBLESHOOTING": "", 80 | "SETTINGS_GENERAL_DIS_MONITOR_FEATURES_TITLE": "", 81 | "SETTINGS_GENERAL_DIS_MONITOR_FEATURES_DESC": "", 82 | "SETTINGS_GENERAL_OVERLAY_TITLE": "", 83 | "SETTINGS_GENERAL_OVERLAY_DESC": "", 84 | "SETTINGS_GENERAL_DIS_OVERLAY_TITLE": "", 85 | "SETTINGS_GENERAL_DIS_OVERLAY_DESC": "", 86 | "SETTINGS_GENERAL_ON_OVERLAY_TITLE": "", 87 | "SETTINGS_GENERAL_ON_OVERLAY_DESC": "", 88 | "SETTINGS_GENERAL_FORCE_OVERLAY_TITLE": "", 89 | "SETTINGS_GENERAL_FORCE_OVERLAY_DESC": "", 90 | "SETTINGS_GENERAL_LEGACY_DDC_TITLE": "", 91 | "SETTINGS_GENERAL_LEGACY_DDC_DESC": "", 92 | "SETTINGS_GENERAL_AUTOBRIGHT_TITLE": "", 93 | "SETTINGS_GENERAL_AUTOBRIGHT_DESC": "", 94 | "SETTINGS_GENERAL_SKIP_APPLY_TITLE": "", 95 | "SETTINGS_GENERAL_SKIP_APPLY_DESC": "", 96 | "SETTINGS_GENERAL_REPORT_TITLE": "", 97 | "SETTINGS_GENERAL_REPORT_DESC": "", 98 | "SETTINGS_MONITORS_RATE_TITLE": "", 99 | "SETTINGS_MONITORS_RATE_DESC": "", 100 | "SETTINGS_MONITORS_RATE_0": "", 101 | "SETTINGS_MONITORS_RATE_1": "", 102 | "SETTINGS_MONITORS_RATE_2": "", 103 | "SETTINGS_MONITORS_RATE_3": "", 104 | "SETTINGS_MONITORS_RATE_4": "", 105 | "SETTINGS_MONITORS_RENAME_TITLE": "", 106 | "SETTINGS_MONITORS_RENAME_DESC": "", 107 | "SETTINGS_MONITORS_ENTER_NAME": "", 108 | "SETTINGS_MONITORS_REORDER_TITLE": "", 109 | "SETTINGS_MONITORS_REORDER_DESC": "", 110 | "SETTINGS_MONITORS_NORMALIZE_TITLE": "", 111 | "SETTINGS_MONITORS_NORMALIZE_DESC": "", 112 | "SETTINGS_MONITORS_DETAILS_NAME": "", 113 | "SETTINGS_MONITORS_DETAILS_INTERNAL_NAME": "", 114 | "SETTINGS_MONITORS_DETAILS_COMMUNICATION": "", 115 | "SETTINGS_MONITORS_DETAILS_BRIGHTNESS": "", 116 | "SETTINGS_MONITORS_DETAILS_MAX_BRIGHTNESS": "", 117 | "SETTINGS_MONITORS_DETAILS_BRIGHTNESS_NORMALIZATION": "", 118 | "SETTINGS_MONITORS_HIDE_INTERNAL_TITLE": "", 119 | "SETTINGS_MONITORS_HIDE_INTERNAL_DESC": "", 120 | "SETTINGS_MONITORS_HIDE_DISPLAYS_TITLE": "", 121 | "SETTINGS_MONITORS_HIDE_DISPLAYS_DESC": "", 122 | "SETTINGS_FEATURES_DESCRIPTION": "", 123 | "SETTINGS_FEATURES_UNSUPPORTED": "", 124 | "SETTINGS_FEATURES_CUR_BRIGHTNESS_TITLE": "", 125 | "SETTINGS_FEATURES_CUR_BRIGHTNESS_DESC": "", 126 | "SETTINGS_FEATURES_POWER_TITLE": "", 127 | "SETTINGS_FEATURES_POWER_DESC": "", 128 | "SETTINGS_FEATURES_POWER_STANDBY": "", 129 | "SETTINGS_FEATURES_POWER_OFF": "", 130 | "SETTINGS_FEATURES_POWER_COMPAT": "", 131 | "SETTINGS_FEATURES_POWER_WARNING": "", 132 | "SETTINGS_FEATURES_ADD": "", 133 | "SETTINGS_FEATURES_ADD_DESC": "", 134 | "SETTINGS_FEATURES_ADD_VCP": "", 135 | "SETTINGS_FEATURES_ADD_PLACEHOLDER": "", 136 | "SETTINGS_FEATURES_ADD_EXISTS": "", 137 | "SETTINGS_FEATURES_VCP_LIST_TITLE": "", 138 | "SETTINGS_FEATURES_VCP_LIST_DESC": "", 139 | "SETTINGS_FEATURES_VCP_EXPECTED": "", 140 | "SETTINGS_TIME_TITLE": "", 141 | "SETTINGS_TIME_DESC": "", 142 | "SETTINGS_TIME_ADD": "", 143 | "SETTINGS_TIME_REMOVE": "", 144 | "SETTINGS_TIME_INDIVIDUAL_TITLE": "", 145 | "SETTINGS_TIME_INDIVIDUAL_DESC": "", 146 | "SETTINGS_TIME_TRANSITON_TITLE": "", 147 | "SETTINGS_TIME_TRANSITON_DESC": "", 148 | "SETTINGS_TIME_STARTUP_TITLE": "", 149 | "SETTINGS_TIME_STARTUP_DESC": "", 150 | "SETTINGS_TIME_IDLE_TITLE": "", 151 | "SETTINGS_TIME_IDLE_DESC": "", 152 | "SETTINGS_TIME_ANIMATE_TITLE": "", 153 | "SETTINGS_TIME_ANIMATE_DESC": "", 154 | "SETTINGS_TIME_SUN_TITLE": "", 155 | "SETTINGS_TIME_SUN_DESC": "", 156 | "SETTINGS_TIME_LAT": "", 157 | "SETTINGS_TIME_LONG": "", 158 | "SETTINGS_TIME_SUN_GET": "", 159 | "SETTINGS_TIME_IDLE_FS_TITLE": "", 160 | "SETTINGS_TIME_IDLE_FS_DESC": "", 161 | "SETTINGS_TIME_IDLE_MEDIA_TITLE": "", 162 | "SETTINGS_TIME_IDLE_MEDIA_DESC": "", 163 | "SETTINGS_HOTKEYS_TITLE": "", 164 | "SETTINGS_HOTKEYS_DESC": "", 165 | "SETTINGS_HOTKEYS_ADD": "", 166 | "SETTINGS_HOTKEYS_REMOVE": "", 167 | "SETTINGS_HOTKEYS_INCREASE": "", 168 | "SETTINGS_HOTKEYS_DECREASE": "", 169 | "SETTINGS_HOTKEYS_PRESS_KEYS_HINT": "", 170 | "SETTINGS_HOTKEYS_LEVEL_TITLE": "", 171 | "SETTINGS_HOTKEYS_LEVEL_DESC": "", 172 | "SETTINGS_HOTKEYS_SCROLL_AMOUNT": "", 173 | "SETTINGS_HOTKEYS_INVERT_SCROLL_TITLE": "", 174 | "SETTINGS_HOTKEYS_INVERT_SCROLL_DESC": "", 175 | "SETTINGS_HOTKEYS_TOD_TITLE": "", 176 | "SETTINGS_HOTKEYS_TOD_DESC": "", 177 | "SETTINGS_HOTKEYS_TOD_NONE": "", 178 | "SETTINGS_HOTKEYS_TOD_SOFT": "", 179 | "SETTINGS_HOTKEYS_TOD_HARD": "", 180 | "SETTINGS_HOTKEYS_TOD_BOTH": "", 181 | "SETTINGS_HOTKEYS_TOD_NOTE": "", 182 | "SETTINGS_HOTKEYS_BREAK_TITLE": "", 183 | "SETTINGS_HOTKEYS_BREAK_DESC": "", 184 | "SETTINGS_HOTKEY_OFF_WARN": "", 185 | "SETTINGS_HOTKEY_TARGET": "", 186 | "SETTINGS_HOTKEY_VALUE": "", 187 | "SETTINGS_HOTKEY_VALUE_PLACEHOLDER": "", 188 | "SETTINGS_HOTKEY_VALUES": "", 189 | "SETTINGS_HOTKEY_ADD_VALUE": "", 190 | "SETTINGS_HOTKEY_ACTION": "", 191 | "SETTINGS_HOTKEY_ACTION_SET": "", 192 | "SETTINGS_HOTKEY_ACTION_OFFSET": "", 193 | "SETTINGS_HOTKEY_ACTION_CYCLE": "", 194 | "SETTINGS_PROFILES_TITLE": "", 195 | "SETTINGS_PROFILES_DESC": "", 196 | "SETTINGS_PROFILES_ADD": "", 197 | "SETTINGS_PROFILES_NAME": "", 198 | "SETTINGS_PROFILES_BRIGHTNESS_TOGGLE": "", 199 | "SETTINGS_PROFILES_SHOW_MENU": "", 200 | "SETTINGS_PROFILES_TRIGGER_TITLE": "", 201 | "SETTINGS_PROFILES_APP_PATH": "", 202 | "SETTINGS_PROFILES_APP_DESC": "", 203 | "SETTINGS_PROFILES_OVERLAY_TITLE": "", 204 | "SETTINGS_PROFILES_OVERLAY_DESC": "", 205 | "SETTINGS_UPDATES_TITLE": "", 206 | "SETTINGS_UPDATES_VERSION": "", 207 | "SETTINGS_UPDATES_AUTOMATIC_TITLE": "", 208 | "SETTINGS_UPDATES_AUTOMATIC_DESC": "", 209 | "SETTINGS_UPDATES_MS_STORE": "", 210 | "SETTINGS_UPDATES_AVAILABLE": "", 211 | "SETTINGS_UPDATES_NONE_AVAILABLE": "", 212 | "SETTINGS_UPDATES_DOWNLOADING": "", 213 | "SETTINGS_UPDATES_DOWNLOAD": "", 214 | "SETTINGS_UPDATES_CHANNEL": "", 215 | "SETTINGS_UPDATES_BRANCH_STABLE": "", 216 | "SETTINGS_UPDATES_BRANCH_BETA": "" 217 | } 218 | -------------------------------------------------------------------------------- /src/modules/acrylic/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log* 3 | build 4 | .vscode/ipch 5 | .history -------------------------------------------------------------------------------- /src/modules/acrylic/acrylic.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | enum AccentState { 5 | ACCENT_DISABLED = 0, 6 | ACCENT_ENABLE_GRADIENT = 1, 7 | ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, 8 | ACCENT_ENABLE_BLURBEHIND = 3, 9 | ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, 10 | ACCENT_ENABLE_HOSTBACKDROP = 5, // RS5 1809 11 | ACCENT_INVALID_STATE = 6 12 | }; 13 | 14 | enum WindowCompositionAttribute { 15 | WCA_ACCENT_POLICY = 19 16 | }; 17 | 18 | struct AccentPolicy { 19 | AccentState accentState; 20 | int accentFlags; 21 | int gradientColor; 22 | int animationId; 23 | }; 24 | 25 | struct WindowCompositionAttributeData { 26 | WindowCompositionAttribute attribute; 27 | PVOID pData; 28 | ULONG dataSize; 29 | }; 30 | 31 | typedef BOOL(WINAPI 32 | *pSetWindowCompositionAttribute)(HWND, WindowCompositionAttributeData*); 33 | 34 | const HINSTANCE hModule = LoadLibrary(TEXT("user32.dll")); 35 | 36 | void setAcrylic(const Napi::CallbackInfo &info) { 37 | Napi::Env env = info.Env(); 38 | try { 39 | if (info.Length() == 0) { 40 | Napi::TypeError::New(env, "WINDOW_NOT_GIVEN").ThrowAsJavaScriptException(); 41 | return; 42 | } 43 | if (info.Length() != 6) { 44 | Napi::TypeError::New(env, "PARAMETER_ERROR").ThrowAsJavaScriptException(); 45 | return; 46 | } 47 | if (!info[0].IsNumber()) { 48 | Napi::TypeError::New(env, "UNKNOWN").ThrowAsJavaScriptException(); 49 | return; 50 | } 51 | HWND hWnd = (HWND) info[0].As().Int64Value(); 52 | int isRS4OrGreater = info[1].As().Int32Value(); 53 | 54 | int redValue = info[2].As().Int32Value(); 55 | int greenValue = info[3].As().Int32Value(); 56 | int blueValue = info[4].As().Int32Value(); 57 | int alphaValue = info[5].As().Int32Value(); 58 | 59 | if (hModule) { 60 | const pSetWindowCompositionAttribute SetWindowCompositionAttribute = (pSetWindowCompositionAttribute) GetProcAddress( 61 | hModule, "SetWindowCompositionAttribute"); 62 | if (SetWindowCompositionAttribute) { 63 | int gradientColor = (alphaValue<<24) + (blueValue<<16) + (greenValue<<8) + (redValue); 64 | AccentState blurType = isRS4OrGreater == 1 ? ACCENT_ENABLE_ACRYLICBLURBEHIND : ACCENT_ENABLE_BLURBEHIND; 65 | AccentPolicy policy = {blurType, 2, gradientColor, 0}; 66 | WindowCompositionAttributeData data = {WCA_ACCENT_POLICY, &policy, sizeof(AccentPolicy)}; 67 | SetWindowCompositionAttribute(hWnd, &data); 68 | } else { 69 | Napi::Error::New(env, "FAIL_LOAD_DLL").ThrowAsJavaScriptException(); 70 | return; 71 | } 72 | FreeLibrary(hModule); 73 | } else { 74 | Napi::Error::New(env, "FAIL_LOAD_DLL").ThrowAsJavaScriptException(); 75 | return; 76 | } 77 | } catch (const char *ex) { 78 | Napi::Error::New(env, "UNKNOWN").ThrowAsJavaScriptException(); 79 | } 80 | } 81 | 82 | void disableAcrylic(const Napi::CallbackInfo &info) { 83 | Napi::Env env = info.Env(); 84 | try { 85 | if (info.Length() != 1) { 86 | Napi::TypeError::New(env, "WINDOW_NOT_GIVEN").ThrowAsJavaScriptException(); 87 | return; 88 | } 89 | if (!info[0].IsNumber()) { 90 | Napi::TypeError::New(env, "UNKNOWN").ThrowAsJavaScriptException(); 91 | return; 92 | } 93 | HWND hWnd = (HWND) info[0].As().Int64Value(); 94 | if (hModule) { 95 | const pSetWindowCompositionAttribute SetWindowCompositionAttribute = (pSetWindowCompositionAttribute) GetProcAddress( 96 | hModule, "SetWindowCompositionAttribute"); 97 | if (SetWindowCompositionAttribute) { 98 | AccentPolicy policy = {ACCENT_DISABLED, 0, 0, 0}; 99 | WindowCompositionAttributeData data = {WCA_ACCENT_POLICY, &policy, sizeof(AccentPolicy)}; 100 | SetWindowCompositionAttribute(hWnd, &data); 101 | } else { 102 | Napi::Error::New(env, "FAIL_LOAD_DLL").ThrowAsJavaScriptException(); 103 | return; 104 | } 105 | FreeLibrary(hModule); 106 | } else { 107 | Napi::Error::New(env, "FAIL_LOAD_DLL").ThrowAsJavaScriptException(); 108 | return; 109 | } 110 | } catch (const char *ex) { 111 | Napi::Error::New(env, "UNKNOWN").ThrowAsJavaScriptException(); 112 | } 113 | } 114 | 115 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 116 | exports.Set(Napi::String::New(env, "setAcrylic"), 117 | Napi::Function::New(env, setAcrylic)); 118 | exports.Set(Napi::String::New(env, "disableAcrylic"), 119 | Napi::Function::New(env, disableAcrylic)); 120 | return exports; 121 | } 122 | 123 | NODE_API_MODULE(acrylic, Init) 124 | -------------------------------------------------------------------------------- /src/modules/acrylic/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "acrylic", 5 | "cflags!": [ "-fno-exceptions" ], 6 | "cflags_cc!": [ "-fno-exceptions" ], 7 | "conditions": [ 8 | ["OS=='win'", { 9 | "sources": [ "acrylic.cc" ] 10 | }], 11 | ], 12 | "include_dirs": [ 13 | " { 26 | return Object.assign({ title: info.title, application: info.application, path: info.path, pid: info.pid, icon: info.icon }, (process.platform == 'win32' 27 | ? { 28 | windows: { 29 | isUWPApp: info['windows.isUWPApp'] || false, 30 | uwpPackage: info['windows.uwpPackage'] || '' 31 | } 32 | } 33 | : {})); 34 | }; 35 | const ActiveWindow = { 36 | getActiveWindow: () => { 37 | if (!addon) { 38 | throw new Error('Failed to load native addon'); 39 | } 40 | const info = addon.getActiveWindow(); 41 | return encodeWindowInfo(info); 42 | }, 43 | subscribe: (callback) => { 44 | if (!addon) { 45 | throw new Error('Failed to load native addon'); 46 | } 47 | const watchId = addon.subscribe(nativeWindowInfo => { 48 | callback(!nativeWindowInfo ? null : encodeWindowInfo(nativeWindowInfo)); 49 | }); 50 | return watchId; 51 | }, 52 | unsubscribe: (watchId) => { 53 | if (!addon) { 54 | throw new Error('Failed to load native addon'); 55 | } 56 | if (watchId < 0) { 57 | throw new Error('Watch ID must be a positive number'); 58 | } 59 | addon.unsubscribe(watchId); 60 | }, 61 | initialize: ({ osxRunLoop } = {}) => { 62 | if (!addon) { 63 | throw new Error('Failed to load native addon'); 64 | } 65 | if (addon.initialize) { 66 | addon.initialize(); 67 | } 68 | // set up runloop on MacOS 69 | if (process.platform == 'darwin' && osxRunLoop) { 70 | const interval = setInterval(() => { 71 | if (addon && addon.runLoop) { 72 | addon.runLoop(); 73 | } 74 | else { 75 | clearInterval(interval); 76 | } 77 | }, 100); 78 | } 79 | }, 80 | requestPermissions: () => { 81 | if (!addon) { 82 | throw new Error('Failed to load native addon'); 83 | } 84 | if (addon.requestPermissions) { 85 | return addon.requestPermissions(); 86 | } 87 | return true; 88 | } 89 | }; 90 | __exportStar(require("./types"), exports); 91 | exports.default = ActiveWindow; 92 | -------------------------------------------------------------------------------- /src/modules/node-active-window/dist/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface NativeWindowInfo { 2 | title: string; 3 | application: string; 4 | path: string; 5 | pid: number; 6 | icon: string; 7 | 'windows.isUWPApp'?: boolean; 8 | 'windows.uwpPackage'?: string; 9 | } 10 | export interface WindowInfo { 11 | title: string; 12 | application: string; 13 | path: string; 14 | pid: number; 15 | icon: string; 16 | windows?: { 17 | isUWPApp: boolean; 18 | uwpPackage: string; 19 | }; 20 | } 21 | export interface Module { 22 | getActiveWindow(): T; 23 | subscribe(callback: (windowInfo: T | null) => void): number; 24 | unsubscribe(watchId: number): void; 25 | initialize?(): void; 26 | requestPermissions?(): boolean; 27 | runLoop?(): void; 28 | } 29 | export interface InitializeOptions { 30 | osxRunLoop?: boolean; 31 | } 32 | export interface IActiveWindow extends Omit, 'runLoop'> { 33 | initialize(opts?: InitializeOptions): void; 34 | requestPermissions(): boolean; 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/node-active-window/dist/types.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /src/modules/node-active-window/module/windows/demo/Makefile: -------------------------------------------------------------------------------- 1 | LIBDIR=..\src 2 | BUILDDIR=build 3 | 4 | CLFLAGS=/std:c++17 5 | LIBS=User32.lib Shell32.lib Version.lib Shlwapi.lib Gdiplus.lib Gdi32.lib Windowsapp.lib 6 | 7 | all: create_dir demo 8 | 9 | demo: $(BUILDDIR)\demo.exe 10 | 11 | $(BUILDDIR)\demo.exe: $(BUILDDIR)\ActiveWindow.obj $(BUILDDIR)\IconCache.obj $(BUILDDIR)\base64.obj $(BUILDDIR)\GdiPlusUtils.obj $(BUILDDIR)\main.obj 12 | @echo Linking demo.exe 13 | link /out:$(BUILDDIR)\demo.exe $(BUILDDIR)\*.obj $(LIBS) 14 | 15 | $(BUILDDIR)\main.obj: main.cpp 16 | @echo Building main.cpp for demo 17 | cl /c /EHsc $(CLFLAGS) /Fo$(BUILDDIR)\ main.cpp 18 | 19 | $(BUILDDIR)\ActiveWindow.obj: $(LIBDIR)\ActiveWindow.cpp $(LIBDIR)\ActiveWindow.h 20 | @echo Building $(LIBDIR)\ActiveWindow.cpp 21 | cl /c /EHsc $(CLFLAGS) /Fo$(BUILDDIR)\ $(LIBDIR)\ActiveWindow.cpp 22 | 23 | $(BUILDDIR)\IconCache.obj: $(LIBDIR)\IconCache.cpp $(LIBDIR)\IconCache.h 24 | @echo Building $(LIBDIR)\IconCache.cpp 25 | cl /c /EHsc $(CLFLAGS) /Fo$(BUILDDIR)\ $(LIBDIR)\IconCache.cpp 26 | 27 | $(BUILDDIR)\GdiPlusUtils.obj: $(LIBDIR)\GdiPlusUtils.cpp $(LIBDIR)\GdiPlusUtils.h 28 | @echo Building $(LIBDIR)\GdiPlusUtils.cpp 29 | cl /c /EHsc $(CLFLAGS) /Fo$(BUILDDIR)\ $(LIBDIR)\GdiPlusUtils.cpp 30 | 31 | $(BUILDDIR)\base64.obj: $(LIBDIR)\base64\base64.cpp $(LIBDIR)\base64\base64.h 32 | @echo Building $(LIBDIR)\base64\base64.cpp 33 | cl /c /EHsc $(CLFLAGS) /Fo$(BUILDDIR)\ $(LIBDIR)\base64\base64.cpp 34 | 35 | create_dir: 36 | @if not exist $(BUILDDIR) mkdir $(BUILDDIR) 37 | 38 | clean: 39 | @if exist $(BUILDDIR) rmdir /s /q $(BUILDDIR) 40 | 41 | run: 42 | $(BUILDDIR)\demo.exe $(MODE) 43 | -------------------------------------------------------------------------------- /src/modules/node-active-window/module/windows/demo/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../src/ActiveWindow.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void printWindowInfo(PaymoActiveWindow::WindowInfo* inf) { 8 | std::wcout<title<application<path<pid<<"\""<isUWPApp ? "true" : "false")<uwpPackage<icon<getActiveWindow(); 21 | 22 | if (inf == NULL) { 23 | std::cout<<"Error: Could not get window info"<watchActiveWindow([](PaymoActiveWindow::WindowInfo* inf) { 79 | std::cout<<"[Notif] Active window has changed!\n"; 80 | 81 | if (inf == NULL) { 82 | std::cout<<"Empty"<unwatchActiveWindow(watchId); 98 | std::cout<<"Watch removed"<getActiveWindow(); 106 | delete inf; 107 | } 108 | double end = getCpuTime(); 109 | 110 | std::cout<<"Elapsed CPU seconds: "<<(end - start)< 2 | #include "module.h" 3 | 4 | Napi::Object InitAll(Napi::Env env, Napi::Object exports) { 5 | return module::Init(env, exports); 6 | } 7 | 8 | NODE_API_MODULE(NODE_GYP_MODULE_NAME, InitAll); 9 | -------------------------------------------------------------------------------- /src/modules/node-active-window/module/windows/napi/module.cpp: -------------------------------------------------------------------------------- 1 | #include "module.h" 2 | 3 | Napi::Object module::Init(Napi::Env env, Napi::Object exports) { 4 | env.SetInstanceData(new PaymoActiveWindow::ActiveWindow(0)); 5 | 6 | exports.Set("getActiveWindow", Napi::Function::New(env, module::getActiveWindow)); 7 | exports.Set("subscribe", Napi::Function::New(env, module::subscribe)); 8 | exports.Set("unsubscribe", Napi::Function::New(env, module::unsubscribe)); 9 | 10 | return exports; 11 | } 12 | 13 | Napi::Object module::getActiveWindow(const Napi::CallbackInfo& info) { 14 | PaymoActiveWindow::ActiveWindow* activeWindow = info.Env().GetInstanceData(); 15 | if (activeWindow == NULL) { 16 | Napi::Error::New(info.Env(), "ActiveWindow module not initialized").ThrowAsJavaScriptException(); 17 | return Napi::Object::Object(); 18 | } 19 | 20 | PaymoActiveWindow::WindowInfo* windowInfo = activeWindow->getActiveWindow(); 21 | if (windowInfo == NULL) { 22 | Napi::Error::New(info.Env(), "Failed to get active window").ThrowAsJavaScriptException(); 23 | return Napi::Object::Object(); 24 | } 25 | 26 | Napi::Object result = module::encodeWindowInfo(info.Env(), windowInfo); 27 | 28 | delete windowInfo; 29 | 30 | return result; 31 | } 32 | 33 | Napi::Number module::subscribe(const Napi::CallbackInfo& info) { 34 | PaymoActiveWindow::ActiveWindow* activeWindow = info.Env().GetInstanceData(); 35 | if (activeWindow == NULL) { 36 | Napi::Error::New(info.Env(), "ActiveWindow module not initialized").ThrowAsJavaScriptException(); 37 | return Napi::Number::New(info.Env(), -1); 38 | } 39 | 40 | if (info.Length() != 1) { 41 | Napi::TypeError::New(info.Env(), "Expected 1 argument").ThrowAsJavaScriptException(); 42 | return Napi::Number::New(info.Env(), -1); 43 | } 44 | 45 | if (!info[0].IsFunction()) { 46 | Napi::TypeError::New(info.Env(), "Expected first argument to be function").ThrowAsJavaScriptException(); 47 | return Napi::Number::New(info.Env(), -1); 48 | } 49 | 50 | Napi::ThreadSafeFunction tsfn = Napi::ThreadSafeFunction::New(info.Env(), info[0].As(), "Active Window Callback", 0, 2); 51 | auto mainThreadCallback = [](Napi::Env env, Napi::Function jsCallback, PaymoActiveWindow::WindowInfo* windowInfo) { 52 | if (windowInfo == NULL) { 53 | jsCallback.Call({ env.Null() }); 54 | } 55 | else { 56 | jsCallback.Call({ module::encodeWindowInfo(env, windowInfo) }); 57 | delete windowInfo; 58 | } 59 | }; 60 | 61 | PaymoActiveWindow::watch_t watchId = activeWindow->watchActiveWindow([tsfn, mainThreadCallback](PaymoActiveWindow::WindowInfo* windowInfo) { 62 | if (windowInfo == NULL) { 63 | tsfn.BlockingCall((PaymoActiveWindow::WindowInfo*)NULL, mainThreadCallback); 64 | return; 65 | } 66 | 67 | // clone window info 68 | PaymoActiveWindow::WindowInfo* arg = new PaymoActiveWindow::WindowInfo(); 69 | *arg = *windowInfo; 70 | 71 | tsfn.BlockingCall(arg, mainThreadCallback); 72 | }); 73 | 74 | return Napi::Number::New(info.Env(), watchId); 75 | } 76 | 77 | void module::unsubscribe(const Napi::CallbackInfo& info) { 78 | PaymoActiveWindow::ActiveWindow* activeWindow = info.Env().GetInstanceData(); 79 | if (activeWindow == NULL) { 80 | Napi::Error::New(info.Env(), "ActiveWindow module not initialized").ThrowAsJavaScriptException(); 81 | return; 82 | } 83 | 84 | if (info.Length() != 1) { 85 | Napi::TypeError::New(info.Env(), "Expected 1 argument").ThrowAsJavaScriptException(); 86 | return; 87 | } 88 | 89 | if (!info[0].IsNumber()) { 90 | Napi::TypeError::New(info.Env(), "Expected first argument to be number").ThrowAsJavaScriptException(); 91 | return; 92 | } 93 | 94 | PaymoActiveWindow::watch_t watchId = info[0].As().Uint32Value(); 95 | 96 | activeWindow->unwatchActiveWindow(watchId); 97 | } 98 | 99 | Napi::Object module::encodeWindowInfo(Napi::Env env, PaymoActiveWindow::WindowInfo* windowInfo) { 100 | Napi::Object result = Napi::Object::New(env); 101 | result.Set("title", Napi::String::New(env, std::u16string(windowInfo->title.begin(), windowInfo->title.end()))); 102 | result.Set("application", Napi::String::New(env, std::u16string(windowInfo->application.begin(), windowInfo->application.end()))); 103 | result.Set("path", Napi::String::New(env, std::u16string(windowInfo->path.begin(), windowInfo->path.end()))); 104 | result.Set("pid", Napi::Number::New(env, windowInfo->pid)); 105 | result.Set("windows.isUWPApp", Napi::Boolean::New(env, windowInfo->isUWPApp)); 106 | result.Set("windows.uwpPackage", Napi::String::New(env, std::u16string(windowInfo->uwpPackage.begin(), windowInfo->uwpPackage.end()))); 107 | result.Set("icon", Napi::String::New(env, windowInfo->icon)); 108 | 109 | return result; 110 | } 111 | -------------------------------------------------------------------------------- /src/modules/node-active-window/module/windows/napi/module.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../src/ActiveWindow.h" 4 | 5 | #ifndef _PAYMO_MODULE_H 6 | #define _PAYMO_MODULE_H 7 | 8 | namespace module { 9 | Napi::Object Init(Napi::Env env, Napi::Object exports); 10 | 11 | Napi::Object getActiveWindow(const Napi::CallbackInfo& info); 12 | Napi::Number subscribe(const Napi::CallbackInfo& info); 13 | void unsubscribe(const Napi::CallbackInfo& info); 14 | 15 | // helpers 16 | Napi::Object encodeWindowInfo(Napi::Env env, PaymoActiveWindow::WindowInfo* windowInfo); 17 | } 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/modules/node-active-window/module/windows/src/ActiveWindow.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #ifndef _PAYMO_ACTIVEWINDOW_H 18 | #define _PAYMO_ACTIVEWINDOW_H 19 | 20 | namespace PaymoActiveWindow { 21 | struct WindowInfo { 22 | std::wstring title = L""; 23 | std::wstring application = L""; 24 | std::wstring path = L""; 25 | unsigned int pid = 0; 26 | bool isUWPApp = false; 27 | std::wstring uwpPackage = L""; 28 | std::string icon = ""; 29 | }; 30 | 31 | typedef unsigned int watch_t; 32 | typedef std::function watch_callback; 33 | 34 | class ActiveWindow { 35 | public: 36 | ActiveWindow(unsigned int iconCacheSize = 0); 37 | ~ActiveWindow(); 38 | WindowInfo* getActiveWindow(); 39 | watch_t watchActiveWindow(watch_callback cb); 40 | void unwatchActiveWindow(watch_t watch); 41 | private: 42 | ULONG_PTR gdiPlusToken; 43 | CLSID gdiPlusEncoder; 44 | 45 | watch_t nextWatchId = 1; 46 | 47 | std::thread* watchThread = NULL; 48 | std::mutex mutex; 49 | std::atomic threadShouldExit; 50 | std::unordered_map watches; 51 | 52 | static std::mutex smutex; 53 | static std::unordered_map winEventProcCbCtx; 54 | 55 | std::wstring getWindowTitle(HWND hWindow); 56 | std::wstring getProcessPath(HANDLE hProc); 57 | std::wstring getProcessName(std::wstring path); 58 | std::string getWindowIcon(std::wstring path); 59 | std::string getUWPIcon(HANDLE hProc); 60 | std::wstring getUWPPackage(HANDLE hProc); 61 | std::wstring basename(std::wstring path); 62 | bool isUWPApp(std::wstring path); 63 | HICON getHighResolutionIcon(std::wstring path); 64 | IStream* getPngFromIcon(HICON hIcon); 65 | std::wstring getUWPPackagePath(HANDLE hProc); 66 | IAppxManifestProperties* getUWPPackageProperties(std::wstring pkgPath); 67 | std::wstring getUWPLargestIconPath(std::wstring iconPath); 68 | std::string encodeImageStream(IStream* pngStream); 69 | void runWatchThread(); 70 | static BOOL CALLBACK EnumChildWindowsCb(HWND hWindow, LPARAM param); 71 | static VOID CALLBACK WinEventProcCb(HWINEVENTHOOK hHook, DWORD event, HWND hWindow, LONG idObject, LONG idChild, DWORD eventThread, DWORD eventTime); 72 | }; 73 | 74 | struct EnumChildWindowsCbParam { 75 | ActiveWindow* aw; 76 | std::wstring path = L""; 77 | HANDLE hProc; 78 | bool ok = false; 79 | 80 | EnumChildWindowsCbParam(ActiveWindow* aw) { 81 | this->aw = aw; 82 | } 83 | }; 84 | } 85 | 86 | #endif 87 | -------------------------------------------------------------------------------- /src/modules/node-active-window/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@paymoapp/active-window", 3 | "version": "1.2.3", 4 | "description": "NodeJS library using native modules to get the active window and some metadata (including the application icon) on Windows, MacOS and Linux.", 5 | "main": "./dist/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "postinstall": "npm run build", 9 | "build": "npm run build:gyp", 10 | "build:ts": "tsc --project tsconfig.build.json", 11 | "build:gyp": "node-gyp rebuild", 12 | "clean": "node-gyp clean && rimraf dist", 13 | "typecheck": "tsc --noEmit", 14 | "release": "standard-version", 15 | "release:pre": "standard-version --prerelease", 16 | "generate:readme-toc": "markdown-toc -i --bullets=\"-\" --maxdepth=4 README.md" 17 | }, 18 | "binary": { 19 | "napi_versions": [ 20 | 6 21 | ] 22 | }, 23 | "files": [ 24 | "binding.gyp", 25 | "dist/", 26 | "module/" 27 | ], 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/paymoapp/node-active-window" 31 | }, 32 | "keywords": [ 33 | "node", 34 | "native", 35 | "active", 36 | "window" 37 | ], 38 | "author": "Paymo SRL", 39 | "license": "GPL-3.0", 40 | "devDependencies": { 41 | "@types/node": "^17.0.35", 42 | "@types/ws": "^8.5.3", 43 | "@typescript-eslint/eslint-plugin": "^5.26.0", 44 | "@typescript-eslint/parser": "^5.26.0", 45 | "eslint": "^8.16.0", 46 | "eslint-config-prettier": "^8.5.0", 47 | "eslint-plugin-import": "^2.26.0", 48 | "eslint-plugin-prettier": "^4.0.0", 49 | "eslint-watch": "^8.0.0", 50 | "markdown-toc": "^1.2.0", 51 | "prettier": "^2.6.2", 52 | "rimraf": "^3.0.2", 53 | "standard-version": "^9.5.0", 54 | "ts-node": "^10.8.1", 55 | "typescript": "^4.6.4", 56 | "ws": "^8.7.0" 57 | }, 58 | "dependencies": { 59 | "node-addon-api": "^5.0.0" 60 | }, 61 | "standard-version": { 62 | "scripts": { 63 | "prerelease": "git fetch --all --tags" 64 | }, 65 | "types": [ 66 | { 67 | "type": "feat", 68 | "section": "Features" 69 | }, 70 | { 71 | "type": "fix", 72 | "section": "Bug Fixes" 73 | }, 74 | { 75 | "type": "imp", 76 | "section": "Improvements" 77 | }, 78 | { 79 | "type": "ci", 80 | "section": "Build/CI" 81 | }, 82 | { 83 | "type": "chore", 84 | "hidden": true 85 | }, 86 | { 87 | "type": "docs", 88 | "section": "Documentation" 89 | }, 90 | { 91 | "type": "refactor", 92 | "section": "Refactor" 93 | }, 94 | { 95 | "type": "test", 96 | "section": "Testing" 97 | } 98 | ] 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/modules/node-active-window/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Module, 3 | NativeWindowInfo, 4 | WindowInfo, 5 | IActiveWindow, 6 | InitializeOptions 7 | } from './types'; 8 | 9 | const SUPPORTED_PLATFORMS = ['win32', 'linux', 'darwin']; 10 | 11 | let addon: Module | undefined; 12 | 13 | if (SUPPORTED_PLATFORMS.includes(process.platform)) { 14 | addon = require('../build/Release/PaymoActiveWindow.node'); // eslint-disable-line import/no-dynamic-require 15 | } else { 16 | throw new Error( 17 | `Unsupported platform. The supported platforms are: ${SUPPORTED_PLATFORMS.join( 18 | ',' 19 | )}` 20 | ); 21 | } 22 | 23 | const encodeWindowInfo = (info: NativeWindowInfo): WindowInfo => { 24 | return { 25 | title: info.title, 26 | application: info.application, 27 | path: info.path, 28 | pid: info.pid, 29 | icon: info.icon, 30 | ...(process.platform == 'win32' 31 | ? { 32 | windows: { 33 | isUWPApp: info['windows.isUWPApp'] || false, 34 | uwpPackage: info['windows.uwpPackage'] || '' 35 | } 36 | } 37 | : {}) 38 | }; 39 | }; 40 | 41 | const ActiveWindow: IActiveWindow = { 42 | getActiveWindow: (): WindowInfo => { 43 | if (!addon) { 44 | throw new Error('Failed to load native addon'); 45 | } 46 | 47 | const info = addon.getActiveWindow(); 48 | 49 | return encodeWindowInfo(info); 50 | }, 51 | subscribe: (callback: (windowInfo: WindowInfo | null) => void): number => { 52 | if (!addon) { 53 | throw new Error('Failed to load native addon'); 54 | } 55 | 56 | const watchId = addon.subscribe(nativeWindowInfo => { 57 | callback( 58 | !nativeWindowInfo ? null : encodeWindowInfo(nativeWindowInfo) 59 | ); 60 | }); 61 | 62 | return watchId; 63 | }, 64 | unsubscribe: (watchId: number): void => { 65 | if (!addon) { 66 | throw new Error('Failed to load native addon'); 67 | } 68 | 69 | if (watchId < 0) { 70 | throw new Error('Watch ID must be a positive number'); 71 | } 72 | 73 | addon.unsubscribe(watchId); 74 | }, 75 | initialize: ({ osxRunLoop }: InitializeOptions = {}): void => { 76 | if (!addon) { 77 | throw new Error('Failed to load native addon'); 78 | } 79 | 80 | if (addon.initialize) { 81 | addon.initialize(); 82 | } 83 | 84 | // set up runloop on MacOS 85 | if (process.platform == 'darwin' && osxRunLoop) { 86 | const interval = setInterval(() => { 87 | if (addon && addon.runLoop) { 88 | addon.runLoop(); 89 | } else { 90 | clearInterval(interval); 91 | } 92 | }, 100); 93 | } 94 | }, 95 | requestPermissions: (): boolean => { 96 | if (!addon) { 97 | throw new Error('Failed to load native addon'); 98 | } 99 | 100 | if (addon.requestPermissions) { 101 | return addon.requestPermissions(); 102 | } 103 | 104 | return true; 105 | } 106 | }; 107 | 108 | export * from './types'; 109 | export default ActiveWindow; 110 | -------------------------------------------------------------------------------- /src/modules/node-active-window/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface NativeWindowInfo { 2 | title: string; 3 | application: string; 4 | path: string; 5 | pid: number; 6 | icon: string; 7 | 'windows.isUWPApp'?: boolean; 8 | 'windows.uwpPackage'?: string; 9 | } 10 | 11 | export interface WindowInfo { 12 | title: string; 13 | application: string; 14 | path: string; 15 | pid: number; 16 | icon: string; 17 | windows?: { 18 | isUWPApp: boolean; 19 | uwpPackage: string; 20 | }; 21 | } 22 | 23 | export interface Module { 24 | getActiveWindow(): T; 25 | subscribe(callback: (windowInfo: T | null) => void): number; 26 | unsubscribe(watchId: number): void; 27 | initialize?(): void; 28 | requestPermissions?(): boolean; 29 | runLoop?(): void; 30 | } 31 | 32 | export interface InitializeOptions { 33 | osxRunLoop?: boolean; 34 | } 35 | 36 | export interface IActiveWindow extends Omit, 'runLoop'> { 37 | initialize(opts?: InitializeOptions): void; 38 | requestPermissions(): boolean; 39 | } 40 | -------------------------------------------------------------------------------- /src/modules/node-active-window/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/node-active-window/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "lib": ["esnext", "DOM"], 6 | "allowJs": true, 7 | "rootDirs": ["src"], 8 | "outDir": "dist", 9 | "resolveJsonModule": true, 10 | "strict": true, 11 | "skipLibCheck": true, 12 | "allowSyntheticDefaultImports": true, 13 | "esModuleInterop": true, 14 | "moduleResolution": "node", 15 | "isolatedModules": true, 16 | "declaration": true, 17 | "typeRoots": ["./typings", "./node_modules/@types"] 18 | }, 19 | "include": ["src", "demo"] 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/node-ddcci/.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Mozilla 2 | 3 | AlignOperands: true 4 | AlignTrailingComments: true 5 | BreakBeforeBinaryOperators: NonAssignment 6 | BreakBeforeBraces: Linux 7 | ColumnLimit: 80 8 | ContinuationIndentWidth: 2 9 | IndentWidth: 4 10 | MaxEmptyLinesToKeep: 2 11 | BreakStringLiterals: false -------------------------------------------------------------------------------- /src/modules/node-ddcci/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | -------------------------------------------------------------------------------- /src/modules/node-ddcci/README.md: -------------------------------------------------------------------------------- 1 | # node-ddcci 2 | 3 | ## Installation 4 | 5 | node-ddcci is currently only supported on Windows. 6 | 7 | ````bash 8 | npm install @hensm/ddcci 9 | ```` 10 | 11 | ## Usage 12 | 13 | ````js 14 | const ddcci = require("@hensm/ddcci"); 15 | 16 | for (const monitor of ddcci.getMonitorList()) { 17 | console.log(`${monitor} current brightness: ${ddcci.getBrightness(monitor)}`); 18 | ddcci.setBrightness(monitor, 25); 19 | } 20 | ```` 21 | 22 | ## Docs 23 | 24 | * ### `getMonitorList()` 25 | Gets a list of the current connected monitors. 26 | * #### Return value 27 | An array of `String` containing the monitor IDs. 28 | 29 | * ### `getBrightness(monitorId)` 30 | Queries a monitor's brightness level. 31 | * #### Parameters 32 | * **`monitorId`** 33 | `String`. ID of monitor for which to query the brightness. 34 | * #### Return value 35 | An `integer`, typically between 0-100, representing the current brightness. 36 | 37 | * ### `getMaxBrightness(monitorId)` 38 | Queries a monitor's maximum brightness level. 39 | * #### Parameters 40 | * **`monitorId`** 41 | `String`. ID of monitor for which to query the brightness. 42 | * #### Return value 43 | An `integer`, typically between 0-100, representing the maximum brightness. 44 | 45 | * ### `setBrightness(monitorId, level)` 46 | Sets a monitor's brightness level. 47 | * #### Parameters 48 | * **`monitorId`** 49 | `String`. ID of monitor for which to set the brightness. 50 | * **`level`** 51 | `integer`. Between 0-100 representing the new brightness level. 52 | 53 | * ### `getContrast(monitorId)` 54 | Queries a monitor's contrast level. 55 | * #### Parameters 56 | * **`monitorId`** 57 | `String`. ID of monitor for which to query the contrast. 58 | * #### Return value 59 | An `integer`, typically between 0-100, representing the current contrast. 60 | 61 | * ### `getMaxContrast(monitorId)` 62 | Queries a monitor's maximum contrast level. 63 | * #### Parameters 64 | * **`monitorId`** 65 | `String`. ID of monitor for which to query the contrast. 66 | * #### Return value 67 | An `integer`, typically between 0-100, representing the maximum contrast. 68 | 69 | * ### `setContrast(monitorId, level)` 70 | Sets a monitor's contrast level. 71 | * #### Parameters 72 | * **`monitorId`** 73 | `String`. ID of monitor for which to set the contrast. 74 | * **`level`** 75 | `integer`. Between 0-100 representing the new contrast level. 76 | 77 | * ### `_getVCP(monitorId, vcpCode)` 78 | Queries a monitor for a VCP code value. 79 | * #### Parameters 80 | * **`monitorId`** 81 | `String`. ID of monitor for which to query the VCP feature. 82 | * **`vcpCode`** 83 | `integer`. VCP code to query 84 | * #### Return value 85 | An `array` of two `integer` values in the format of `[currentValue, maxValue]`. 86 | 87 | * ### `_setVCP(monitorId, vcpCode, value)` 88 | Sets the value of a VCP code for a monitor. 89 | * #### Parameters 90 | * **`monitorId`** 91 | `String`. ID of monitor for which to set the VCP feature. 92 | * **`vcpCode`** 93 | `integer`. VCP code to set. 94 | * **`value`** 95 | `integer`. Value of the VCP code. 96 | 97 | * ### `_refresh()` 98 | Refreshes the monitor list. 99 | 100 | 101 | -------------------------------------------------------------------------------- /src/modules/node-ddcci/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [{ 3 | "target_name": "ddcci" 4 | , "sources": [ "./ddcci.cc" ] 5 | , "cflags_cc": [ "-std=c++17" ] 6 | , "include_dirs": [ " ddcci.refresh(method, usePreviousResults, checkHighLevel) 21 | , getMonitorList: (method = "accurate", usePreviousResults = true, checkHighLevel = true) => { 22 | ddcci.refresh(method, usePreviousResults, checkHighLevel); 23 | return ddcci.getMonitorList(); 24 | } 25 | , getAllMonitors: (method = "accurate", usePreviousResults = true, checkHighLevel = true) => { 26 | ddcci.refresh(method, usePreviousResults, checkHighLevel); 27 | const monitors = ddcci.getAllMonitors(); 28 | for(const monitor of monitors) { 29 | if(monitor.result && monitor.result != "ok" && monitor.result != "invalid") { 30 | monitor.capabilities = parseCapabilitiesString(monitor.result); 31 | monitor.capabilitiesRaw = monitor.result; 32 | } 33 | delete monitor.result; 34 | } 35 | return monitors; 36 | } 37 | 38 | , getVCP: ddcci.getVCP 39 | , setVCP: ddcci.setVCP 40 | 41 | , getBrightness (monitorId) { 42 | return ddcci.getVCP(monitorId, vcp.LUMINANCE)[0]; 43 | } 44 | 45 | , getMaxBrightness (monitorId) { 46 | return ddcci.getVCP(monitorId, vcp.LUMINANCE)[1]; 47 | } 48 | 49 | , getContrast (monitorId) { 50 | return ddcci.getVCP(monitorId, vcp.CONTRAST)[0]; 51 | } 52 | 53 | , getMaxContrast (monitorId) { 54 | return ddcci.getVCP(monitorId, vcp.CONTRAST)[1]; 55 | } 56 | 57 | , setBrightness (monitorId, level) { 58 | if (level < 0) { 59 | throw RangeError("Brightness level not within valid range"); 60 | } 61 | 62 | ddcci.setVCP(monitorId, vcp.LUMINANCE, level); 63 | } 64 | 65 | , setContrast (monitorId, level) { 66 | if (level < 0) { 67 | throw RangeError("Contrast level not within valid range"); 68 | } 69 | 70 | ddcci.setVCP(monitorId, vcp.CONTRAST, level); 71 | } 72 | 73 | // Returns an array where keys are valid VCP codes and the keys are an array of accepted values. 74 | // If the array of accepted values is empty, the VCP code either accepts a range of values or no values. Use getVCP to determine the range, if any. 75 | , getCapabilities (monitorId) { 76 | let report = ddcci.getCapabilitiesString(monitorId); 77 | return parseCapabilitiesString(report); 78 | } 79 | , getCapabilitiesRaw (monitorId) { 80 | return ddcci.getCapabilitiesString(monitorId); 81 | } 82 | }; 83 | 84 | function parseCapabilitiesString(report = "") { 85 | const start = report.indexOf('vcp('); // Find where VCP list starts 86 | 87 | // Only run if VCP list found 88 | if(start > 0) { 89 | let layers = 1; 90 | let output = ""; 91 | let position = start + 4; 92 | 93 | // Iterate through report string after the start of the list until we hit the end. 94 | // We'll check for nested parenthesis to correctly find the end of the list. 95 | while(layers > 0 && position + 1 < report.length) { 96 | const char = report[position]; 97 | if(char === "(") { 98 | layers++; 99 | } else if(char === ")") { 100 | layers--; 101 | if(layers <= 0) break; 102 | } 103 | output += char; 104 | position++; 105 | } 106 | 107 | // Strip out unnecessary characters 108 | output = output.replaceAll('\0', '').replaceAll(' ', '').trim(); 109 | 110 | // Iterate through the above string, alternating between parsing VCP codes and accepted values as needed 111 | const codeList = {}; 112 | 113 | let pos = 0 114 | while(pos < output.length){ 115 | const cur = output[pos] 116 | pos++ 117 | 118 | // Check for VCP codes 119 | if(cur !== "(") { 120 | let vcpCode = `${cur}${output[pos]}` 121 | const vcpValues = [] 122 | pos++ 123 | 124 | // Check for defined values 125 | if(output[pos] == "(") { 126 | pos++ 127 | let depth = 0 128 | while((output[pos] != ")" && depth == 0) && pos < output.length) { 129 | 130 | // Check for subdata 131 | // I don't know what to do with this, so we'll just read through and ignore it. 132 | if(output[pos] == "(") { 133 | pos++ 134 | depth++ 135 | while(depth > 0 && pos < output.length) { 136 | if(output[pos] == "(") { 137 | depth++ 138 | } else if(output[pos] == ")") { 139 | depth-- 140 | } 141 | pos++ 142 | } 143 | } else { 144 | // Push defined value to list 145 | vcpValues.push(parseInt(`0x${output[pos]}${output[pos+1]}`)) 146 | pos += 2 147 | } 148 | 149 | } 150 | pos++ 151 | } 152 | // Add VCP code with defined values (if found) 153 | codeList[`0x${vcpCode.toUpperCase()}`] = vcpValues 154 | } 155 | } 156 | 157 | return codeList 158 | } 159 | return false; 160 | } -------------------------------------------------------------------------------- /src/modules/node-ddcci/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hensm/ddcci", 3 | "version": "0.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@hensm/ddcci", 9 | "version": "0.1.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "bindings": "^1.5.0", 13 | "node-addon-api": "^2.0.0" 14 | } 15 | }, 16 | "node_modules/bindings": { 17 | "version": "1.5.0", 18 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", 19 | "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", 20 | "dependencies": { 21 | "file-uri-to-path": "1.0.0" 22 | } 23 | }, 24 | "node_modules/file-uri-to-path": { 25 | "version": "1.0.0", 26 | "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 27 | "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" 28 | }, 29 | "node_modules/node-addon-api": { 30 | "version": "2.0.0", 31 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz", 32 | "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA==" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/node-ddcci/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hensm/ddcci", 3 | "version": "0.1.0", 4 | "description": "NodeJS DDC/CI library", 5 | "main": "index.js", 6 | "author": "Matt Hensman <@matt.tf>", 7 | "license": "MIT", 8 | "bugs": { 9 | "url": "https://github.com/hensm/node-ddcci/issues" 10 | }, 11 | "repository": "github:hensm/node-ddcci", 12 | "dependencies": { 13 | "bindings": "^1.5.0", 14 | "node-addon-api": "^2.0.0" 15 | }, 16 | "types": "index.d.ts" 17 | } 18 | -------------------------------------------------------------------------------- /src/modules/node-ddcci/vcp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Full list of MCCS v2.2a VCP codes. 3 | */ 4 | module.exports = { 5 | // 8.1 - Preset Functions 6 | CODE_PAGE: 0x00 7 | , RESTORE_FACTORY_COLOR_DEFAULTS: 0x08 8 | , RESTORE_FACTORY_DEFAULTS: 0x04 9 | , RESTORE_FACTORY_GEOMETRY_DEFAULTS: 0x06 10 | , RESTORE_FACTORY_LUMINANCE_CONTRAST_DEFAULTS: 0x05 11 | , RESTORE_FACTORY_TV_DEFAULTS: 0x0a 12 | , SAVE_RESTORE_SETTINGS: 0xb0 13 | 14 | // 8.2 - Image Adjustment 15 | , SIX_AXIS_HUE_CONTROL_BLUE: 0x9f 16 | , SIX_AXIS_HUE_CONTROL_CYAN: 0x9e 17 | , SIX_AXIS_HUE_CONTROL_GREEN: 0x9d 18 | , SIX_AXIS_HUE_CONTROL_MAGENTA: 0xa0 19 | , SIX_AXIS_HUE_CONTROL_RED: 0x9b 20 | , SIX_AXIS_HUE_CONTROL_YELLOW: 0x9c 21 | , SIX_AXIS_SATURATION_CONTROL_BLUE: 0x5d 22 | , SIX_AXIS_SATURATION_CONTROL_CYAN: 0x5c 23 | , SIX_AXIS_SATURATION_CONTROL_GREEN: 0x5b 24 | , SIX_AXIS_SATURATION_CONTROL_MAGENTA: 0x5e 25 | , SIX_AXIS_SATURATION_CONTROL_RED: 0x59 26 | , SIX_AXIS_SATURATION_CONTROL_YELLOW: 0x5a 27 | , ADJUST_ZOOM: 0x7c 28 | , AUTO_COLOR_SETUP: 0x1f 29 | , AUTO_SETUP: 0x1e 30 | , AUTO_SETUP_ON_OFF: 0xa2 31 | , BACKLIGHT_CONTROL: 0x13 32 | , BACKLIGHT_LEVEL_BLUE: 0x71 33 | , BACKLIGHT_LEVEL_GREEN: 0x6f 34 | , BACKLIGHT_LEVEL_RED: 0x6d 35 | , BACKLIGHT_LEVEL_WHITE: 0x6b 36 | , BLOCK_LUT_OPERATION: 0x75 37 | , CLOCK: 0x0e 38 | , CLOCK_PHASE: 0x3e 39 | , COLOR_SATURATION: 0x8a 40 | , COLOR_TEMPERATURE_INCREMENT: 0x0b 41 | , COLOR_TEMPERATURE_REQUEST: 0x0c 42 | , CONTRAST: 0x12 43 | , DISPLAY_APPLICATION: 0xdc 44 | , FLESH_TONE_ENHANCEMENT: 0x11 45 | , FOCUS: 0x1c 46 | , GAMMA: 0x72 47 | , GRAY_SCALE_EXPANSION: 0x2e 48 | , HORIZONTAL_MOIRE: 0x56 49 | , HUE: 0x90 50 | , LUMINANCE: 0x10 51 | , LUT_SIZE: 0x73 52 | , SCREEN_ORIENTATION: 0xaa 53 | , SELECT_COLOR_PRESET: 0x14 54 | , SHARPNESS: 0x87 55 | , SINGLE_POINT_LUT_OPERATION: 0x74 56 | , STEREO_VIDEO_MODE: 0xd4 57 | , TV_BLACK_LEVEL_LUMINANCE: 0x92 58 | , TV_CONTRAST: 0x8e 59 | , TV_SHARPNESS: 0x8c 60 | , USER_COLOR_VISION_COMPENSATION: 0x17 61 | , VELOCITY_SCAN_MODULATION: 0x88 62 | , VERTICAL_MOIRE: 0x58 63 | , VIDEO_BLACK_LEVEL_BLUE: 0x70 64 | , VIDEO_BLACK_LEVEL_GREEN: 0x6e 65 | , VIDEO_BLACK_LEVEL_RED: 0x6c 66 | , VIDEO_GAIN_BLUE: 0x1a 67 | , VIDEO_GAIN_GREEN: 0x18 68 | , VIDEO_GAIN_RED: 0x16 69 | , WINDOW_BACKGROUND: 0x9a 70 | , WINDOW_CONTROL_ON_OFF: 0xa4 71 | , WINDOW_SELECT: 0xa5 72 | , WINDOW_SIZE: 0xa6 73 | , WINDOW_TRANSPARENCY: 0xa7 74 | 75 | // 8.3 - Display Control 76 | , DISPLAY_CONTROLLER_ID: 0xc8 77 | , DISPLAY_FIRMWARE_LEVEL: 0xc9 78 | , DISPLAY_USAGE_TIME: 0xc6 79 | , HORIZONTAL_FREQUENCY: 0xac 80 | , IMAGE_MODE: 0xdb 81 | , OSD_BUTTON_LEVEL_CONTROL: 0xca 82 | , OSD_LANGUAGE: 0xcc 83 | , POWER_MODE: 0xd6 84 | , SOURCE_COLOR_CODING: 0xb5 85 | , SOURCE_TIMING_MODE: 0xb4 86 | , VERSION: 0xdf 87 | , VERTICAL_FREQUENCY: 0xae 88 | 89 | // 8.4 - Geometry 90 | , BOTTOM_CORNER_FLARE: 0x4a 91 | , BOTTOM_CORNER_HOOK: 0x4c 92 | , DISPLAY_SCALING: 0x86 93 | , HORIZONTAL_CONVERGENCE_M_G: 0x29 94 | , HORIZONTAL_CONVERGENCE_R_B: 0x28 95 | , HORIZONTAL_KEYSTONE: 0x42 96 | , HORIZONTAL_LINEARITY: 0x2a 97 | , HORIZONTAL_LINEARITY_BALANCE: 0x2c 98 | , HORIZONTAL_MIRROR: 0x82 99 | , HORIZONTAL_PARALLELOGRAM: 0x40 100 | , HORIZONTAL_PINCUSHION: 0x24 101 | , HORIZONTAL_PINCUSHION_BALANCE: 0x26 102 | , HORIZONTAL_POSITION: 0x20 103 | , HORIZONTAL_SIZE: 0x22 104 | , ROTATION: 0x44 105 | , SCAN_MODE: 0xda 106 | , TOP_CORNER_FLARE: 0x46 107 | , TOP_CORNER_HOOK: 0x48 108 | , VERTICAL_CONVERGENCE_M_G: 0x39 109 | , VERTICAL_CONVERGENCE_R_B: 0x38 110 | , VERTICAL_KEYSTONE: 0x43 111 | , VERTICAL_LINEARITY: 0x3a 112 | , VERTICAL_LINEARITY_BALANCE: 0x3c 113 | , VERTICAL_MIRROR: 0x84 114 | , VERTICAL_PARALLELOGRAM: 0x41 115 | , VERTICAL_PINCUSHION: 0x34 116 | , VERTICAL_PINCUSHION_BALANCE: 0x36 117 | , VERTICAL_POSITION: 0x30 118 | , VERTICAL_SIZE: 0x32 119 | , WINDOW_POSITION_BR_X: 0x97 120 | , WINDOW_POSITION_BR_Y: 0x98 121 | , WINDOW_POSITION_TL_X: 0x95 122 | , WINDOW_POSITION_TL_Y: 0x96 123 | 124 | // 8.5 - Miscellaneous 125 | , ACTIVE_CONTROL: 0x52 126 | , AMBIENT_LIGHT_SENSOR: 0x66 127 | , APPLICATION_ENABLE_KEY: 0xc6 128 | , ASSET_TAG: 0xd2 129 | , AUXILIARY_DISPLAY_DATA: 0xcf 130 | , AUXILIARY_DISPLAY_SIZE: 0xce 131 | , AUXILIARY_POWER_OUTPUT: 0xd7 132 | , DEGAUSS: 0x01 133 | , DISPLAY_DESCRIPTOR_LENGTH: 0xc2 134 | , DISPLAY_IDENTIFICATION_DATA_OPERATION: 0x87 135 | , DISPLAY_TECHNOLOGY_TYPE: 0xb6 136 | , ENABLE_DISPLAY_OF_DISPLAY_DESCRIPTOR: 0xc4 137 | , FLAT_PANEL_SUB_PIXEL_LAYOUT: 0xb2 138 | , INPUT_SOURCE: 0x60 139 | , NEW_CONTROL_VALUE: 0x02 140 | , OUTPUT_SELECT: 0xd0 141 | , PERFORMANCE_PRESERVATION: 0x54 142 | , REMOTE_PROCEDURE_CALL: 0x76 143 | , SCRATCH_PAD: 0xde 144 | , SOFT_CONTROLS: 0x03 145 | , STATUS_INDICATORS: 0xcd 146 | , TRANSMIT_DISPLAY_DESCRIPTOR: 0xc3 147 | , TV_CHANNEL_UP_DOWN: 0x8b 148 | 149 | // 8.6 - Audio Function 150 | , AUDIO_BALANCE_L_R: 0x93 151 | , AUDIO_BASS: 0x91 152 | , AUDIO_JACK_CONNECTION_STATUS: 0x65 153 | , AUDIO_MICROPHONE_VOLUME: 0x64 154 | , AUDIO_MUTE: 0x8d 155 | , AUDIO_PROCESSOR_MODE: 0x94 156 | , AUDIO_SPEAKER_SELECT: 0x63 157 | , AUDIO_SPEAKER_VOLUME: 0x62 158 | , AUDIO_TREBLE: 0x8f 159 | 160 | // 8.7 - DPVL Support 161 | , BODY_CRC_ERROR_COUNT: 0xbc 162 | , CLIENT_ID: 0xbd 163 | , HEADER_ERROR_COUNT: 0xbb 164 | , LINK_CONTROL: 0xbe 165 | , MONITOR_STATUS: 0xb7 166 | , MONITOR_X_ORIGIN: 0xb9 167 | , MONITOR_Y_ORIGIN: 0xba 168 | , PACKET_COUNT: 0xb8 169 | }; 170 | -------------------------------------------------------------------------------- /src/modules/tt-windows-utils/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log* 3 | build 4 | .vscode/ipch 5 | .history -------------------------------------------------------------------------------- /src/modules/tt-windows-utils/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "windows_window_utils", 5 | "cflags!": [ "-fno-exceptions" ], 6 | "cflags_cc!": [ "-fno-exceptions" ], 7 | "sources": [ "windows_window_utils.cc" ], 8 | "include_dirs": [ 9 | " 2 | #pragma comment(lib, "windowsapp") 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace winrt; 8 | 9 | typedef winrt::Windows::ApplicationModel::StartupTask StartupTask; 10 | 11 | StartupTask getStartupTask() { 12 | 13 | try { 14 | winrt::Windows::Foundation::IAsyncOperation< 15 | winrt::Windows::Foundation::Collections::IVectorView> 16 | package_async = StartupTask::GetForCurrentPackageAsync(); 17 | if (package_async.wait_for(std::chrono::seconds{1}) != 18 | winrt::Windows::Foundation::AsyncStatus::Completed) { 19 | return NULL; 20 | } 21 | 22 | winrt::Windows::Foundation::Collections::IVectorView tasks = 23 | package_async.get(); 24 | for (auto &&task : tasks) { 25 | if (tasks != NULL) { 26 | return task; 27 | } 28 | } 29 | } catch (int e) { 30 | return NULL; 31 | } 32 | 33 | return NULL; 34 | } 35 | 36 | Napi::Boolean Enable(const Napi::CallbackInfo &info) { 37 | try { 38 | StartupTask task = getStartupTask(); 39 | if (task != NULL) { 40 | task.RequestEnableAsync(); 41 | } 42 | 43 | return Napi::Boolean::New(info.Env(), true); 44 | } catch (int e) { 45 | return Napi::Boolean::New(info.Env(), false); 46 | } 47 | } 48 | 49 | Napi::Boolean Disable(const Napi::CallbackInfo &info) { 50 | try { 51 | Windows::ApplicationModel::StartupTask task = getStartupTask(); 52 | if (task != NULL) { 53 | task.Disable(); 54 | } 55 | return Napi::Boolean::New(info.Env(), true); 56 | } catch (int e) { 57 | return Napi::Boolean::New(info.Env(), false); 58 | } 59 | } 60 | 61 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 62 | 63 | exports.Set(Napi::String::New(env, "enable"), 64 | Napi::Function::New(env, Enable)); 65 | 66 | exports.Set(Napi::String::New(env, "disable"), 67 | Napi::Function::New(env, Disable)); 68 | return exports; 69 | } 70 | 71 | NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init); -------------------------------------------------------------------------------- /src/modules/tt-windows-utils/windows_media_status.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #pragma comment(lib, "windowsapp") 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace winrt; 10 | using namespace Windows::Media::Control; 11 | 12 | typedef GlobalSystemMediaTransportControlsSessionManager GSMTCSM; 13 | typedef GlobalSystemMediaTransportControlsSessionPlaybackStatus 14 | SessionPlaybackStatus; 15 | typedef GlobalSystemMediaTransportControlsSessionMediaProperties 16 | MediaProperties; 17 | typedef Windows::Media::MediaPlaybackType MediaPlaybackType; 18 | 19 | Napi::String getPlaybackStatus(const Napi::CallbackInfo &info) { 20 | std::string statusStr = "closed"; 21 | 22 | auto session_async = GSMTCSM::RequestAsync(); 23 | if (session_async.wait_for(std::chrono::seconds{ 1 }) != winrt::Windows::Foundation::AsyncStatus::Completed) { 24 | return Napi::String::New(info.Env(), statusStr); 25 | } 26 | 27 | GSMTCSM manager = session_async.get(); 28 | GlobalSystemMediaTransportControlsSession session = 29 | manager.GetCurrentSession(); 30 | 31 | if (session == NULL) 32 | return Napi::String::New(info.Env(), statusStr); 33 | 34 | SessionPlaybackStatus status = session.GetPlaybackInfo().PlaybackStatus(); 35 | 36 | switch (status) { 37 | case SessionPlaybackStatus::Opened: 38 | statusStr = "opened"; 39 | break; 40 | case SessionPlaybackStatus::Changing: 41 | statusStr = "changing"; 42 | break; 43 | case SessionPlaybackStatus::Stopped: 44 | statusStr = "stopped"; 45 | break; 46 | case SessionPlaybackStatus::Playing: 47 | statusStr = "playing"; 48 | break; 49 | case SessionPlaybackStatus::Paused: 50 | statusStr = "paused"; 51 | break; 52 | } 53 | 54 | return Napi::String::New(info.Env(), statusStr); 55 | } 56 | 57 | Napi::Object getPlaybackInfo(const Napi::CallbackInfo &info) { 58 | Napi::Object obj = Napi::Object::New(info.Env()); 59 | auto session_async = GSMTCSM::RequestAsync(); 60 | if (session_async.wait_for(std::chrono::seconds{ 1 }) != winrt::Windows::Foundation::AsyncStatus::Completed) { 61 | return obj; 62 | } 63 | GSMTCSM manager = session_async.get(); 64 | GlobalSystemMediaTransportControlsSession session = 65 | manager.GetCurrentSession(); 66 | 67 | if (session == NULL) 68 | return obj; 69 | 70 | MediaProperties playback = session.TryGetMediaPropertiesAsync().get(); 71 | 72 | if (playback == NULL) 73 | return obj; 74 | 75 | obj.Set(Napi::String::New(info.Env(), "title"), 76 | Napi::String::New(info.Env(), 77 | winrt::to_string(playback.Title().c_str()))); 78 | 79 | obj.Set(Napi::String::New(info.Env(), "subtitle"), 80 | Napi::String::New(info.Env(), 81 | winrt::to_string(playback.Subtitle().c_str()))); 82 | 83 | obj.Set(Napi::String::New(info.Env(), "artist"), 84 | Napi::String::New(info.Env(), 85 | winrt::to_string(playback.Artist().c_str()))); 86 | 87 | obj.Set(Napi::String::New(info.Env(), "album"), 88 | Napi::String::New(info.Env(), 89 | winrt::to_string(playback.AlbumTitle().c_str()))); 90 | 91 | obj.Set(Napi::String::New(info.Env(), "albumartist"), 92 | Napi::String::New(info.Env(), 93 | winrt::to_string(playback.AlbumArtist().c_str()))); 94 | 95 | obj.Set(Napi::String::New(info.Env(), "source"), 96 | Napi::String::New( 97 | info.Env(), 98 | winrt::to_string(session.SourceAppUserModelId().c_str()))); 99 | 100 | obj.Set(Napi::String::New(info.Env(), "tracks"), 101 | Napi::Number::New(info.Env(), playback.AlbumTrackCount())); 102 | 103 | obj.Set(Napi::String::New(info.Env(), "tracknumber"), 104 | Napi::Number::New(info.Env(), playback.TrackNumber())); 105 | 106 | std::string typeStr = "unknown"; 107 | MediaPlaybackType type = playback.PlaybackType().Value(); 108 | 109 | switch (type) { 110 | case MediaPlaybackType::Music: 111 | typeStr = "music"; 112 | break; 113 | case MediaPlaybackType::Video: 114 | typeStr = "video"; 115 | break; 116 | case MediaPlaybackType::Image: 117 | typeStr = "image"; 118 | break; 119 | } 120 | 121 | obj.Set(Napi::String::New(info.Env(), "type"), 122 | Napi::String::New(info.Env(), typeStr)); 123 | 124 | return obj; 125 | } 126 | 127 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 128 | 129 | exports.Set(Napi::String::New(env, "getPlaybackStatus"), 130 | Napi::Function::New(env, getPlaybackStatus)); 131 | 132 | exports.Set(Napi::String::New(env, "getPlaybackInfo"), 133 | Napi::Function::New(env, getPlaybackInfo)); 134 | return exports; 135 | } 136 | 137 | NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init); -------------------------------------------------------------------------------- /src/modules/tt-windows-utils/windows_power_events.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | std::string GUIDToString(const GUID guid) { 5 | wchar_t source[40]; 6 | int length = StringFromGUID2(guid, source, 40); 7 | if (length == 0) return ""; 8 | char dest[40]; 9 | wcstombs(dest, source, 40); 10 | return std::string(dest); 11 | } 12 | 13 | Napi::Object GetPowerSetting(const Napi::CallbackInfo& info) { 14 | Napi::BigInt lParamInt = info[0].As(); 15 | 16 | Napi::Env env = info.Env(); 17 | Napi::Object obj = Napi::Object::New(env); 18 | 19 | bool lossless; 20 | int64_t lParamValue = lParamInt.Int64Value(&lossless); 21 | int64_t* lParamPtr = (int64_t*)lParamValue; 22 | LPARAM lParam = (LPARAM)lParamPtr; 23 | 24 | POWERBROADCAST_SETTING* setting = reinterpret_cast(lParam); 25 | 26 | std::string guid = GUIDToString(setting->PowerSetting); 27 | obj.Set(Napi::String::New(env, "guid"), Napi::String::New(env, guid)); 28 | 29 | std::string name = ""; 30 | if(IsEqualGUID(setting->PowerSetting, GUID_CONSOLE_DISPLAY_STATE)) { 31 | name = "GUID_CONSOLE_DISPLAY_STATE"; 32 | } else if(IsEqualGUID(setting->PowerSetting, GUID_MONITOR_POWER_ON)) { 33 | name = "GUID_MONITOR_POWER_ON"; 34 | } else if(IsEqualGUID(setting->PowerSetting, GUID_SESSION_DISPLAY_STATUS)) { 35 | name = "GUID_SESSION_DISPLAY_STATUS"; 36 | } else if(IsEqualGUID(setting->PowerSetting, GUID_SYSTEM_AWAYMODE)) { 37 | name = "GUID_SYSTEM_AWAYMODE"; 38 | } else if(IsEqualGUID(setting->PowerSetting, GUID_LIDSWITCH_STATE_CHANGE)) { 39 | name = "GUID_LIDSWITCH_STATE_CHANGE"; 40 | } else if(IsEqualGUID(setting->PowerSetting, GUID_SESSION_USER_PRESENCE)) { 41 | name = "GUID_SESSION_USER_PRESENCE"; 42 | } else if(IsEqualGUID(setting->PowerSetting, GUID_STANDBY_TIMEOUT)) { 43 | name = "GUID_STANDBY_TIMEOUT"; 44 | } else if(IsEqualGUID(setting->PowerSetting, GUID_VIDEO_ADAPTIVE_DISPLAY_BRIGHTNESS)) { 45 | name = "GUID_VIDEO_ADAPTIVE_DISPLAY_BRIGHTNESS"; 46 | } else if(IsEqualGUID(setting->PowerSetting, GUID_VIDEO_ADAPTIVE_PERCENT_INCREASE)) { 47 | name = "GUID_VIDEO_ADAPTIVE_PERCENT_INCREASE"; 48 | } else if(IsEqualGUID(setting->PowerSetting, GUID_VIDEO_ADAPTIVE_POWERDOWN)) { 49 | name = "GUID_VIDEO_ADAPTIVE_POWERDOWN"; 50 | } else if(IsEqualGUID(setting->PowerSetting, GUID_VIDEO_DIM_TIMEOUT)) { 51 | name = "GUID_VIDEO_DIM_TIMEOUT"; 52 | } else if(IsEqualGUID(setting->PowerSetting, GUID_SLEEP_IDLE_THRESHOLD)) { 53 | name = "GUID_SLEEP_IDLE_THRESHOLD"; 54 | } else if(IsEqualGUID(setting->PowerSetting, GUID_VIDEO_CURRENT_MONITOR_BRIGHTNESS)) { 55 | name = "GUID_VIDEO_CURRENT_MONITOR_BRIGHTNESS"; 56 | } else if(IsEqualGUID(setting->PowerSetting, GUID_VIDEO_POWERDOWN_TIMEOUT)) { 57 | name = "GUID_VIDEO_POWERDOWN_TIMEOUT"; 58 | } 59 | obj.Set(Napi::String::New(env, "name"), Napi::String::New(env, name)); 60 | 61 | DWORD data = *reinterpret_cast(setting->Data); 62 | obj.Set(Napi::String::New(env, "data"), Napi::Number::New(env, data)); 63 | 64 | return obj; 65 | } 66 | 67 | Napi::Boolean RegisterPowerSettingNotifications(const Napi::CallbackInfo& info) { 68 | Napi::Number hwnd = info[0].As(); 69 | RegisterPowerSettingNotification((HWND) hwnd.Int32Value(), &GUID_CONSOLE_DISPLAY_STATE, 0); 70 | RegisterPowerSettingNotification((HWND) hwnd.Int32Value(), &GUID_MONITOR_POWER_ON, 0); 71 | RegisterPowerSettingNotification((HWND) hwnd.Int32Value(), &GUID_SESSION_DISPLAY_STATUS, 0); 72 | RegisterPowerSettingNotification((HWND) hwnd.Int32Value(), &GUID_SYSTEM_AWAYMODE, 0); 73 | RegisterPowerSettingNotification((HWND) hwnd.Int32Value(), &GUID_LIDSWITCH_STATE_CHANGE, 0); 74 | RegisterPowerSettingNotification((HWND) hwnd.Int32Value(), &GUID_SESSION_USER_PRESENCE, 0); 75 | RegisterPowerSettingNotification((HWND) hwnd.Int32Value(), &GUID_STANDBY_TIMEOUT, 0); 76 | RegisterPowerSettingNotification((HWND) hwnd.Int32Value(), &GUID_VIDEO_ADAPTIVE_DISPLAY_BRIGHTNESS, 0); 77 | RegisterPowerSettingNotification((HWND) hwnd.Int32Value(), &GUID_VIDEO_ADAPTIVE_PERCENT_INCREASE, 0); 78 | RegisterPowerSettingNotification((HWND) hwnd.Int32Value(), &GUID_VIDEO_ADAPTIVE_POWERDOWN, 0); 79 | RegisterPowerSettingNotification((HWND) hwnd.Int32Value(), &GUID_VIDEO_DIM_TIMEOUT, 0); 80 | RegisterPowerSettingNotification((HWND) hwnd.Int32Value(), &GUID_SLEEP_IDLE_THRESHOLD, 0); 81 | RegisterPowerSettingNotification((HWND) hwnd.Int32Value(), &GUID_VIDEO_CURRENT_MONITOR_BRIGHTNESS, 0); 82 | RegisterPowerSettingNotification((HWND) hwnd.Int32Value(), &GUID_VIDEO_POWERDOWN_TIMEOUT, 0); 83 | return Napi::Boolean::New(info.Env(), true); 84 | } 85 | 86 | 87 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 88 | exports.Set(Napi::String::New(env, "registerPowerSettingNotifications"), Napi::Function::New(env, RegisterPowerSettingNotifications)); 89 | exports.Set(Napi::String::New(env, "getPowerSetting"), Napi::Function::New(env, GetPowerSetting)); 90 | return exports; 91 | } 92 | 93 | NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) -------------------------------------------------------------------------------- /src/modules/tt-windows-utils/windows_window_utils.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | Napi::Boolean SetWindowPosition(const Napi::CallbackInfo& info) { 5 | Napi::Number hwnd = info[0].As(); 6 | Napi::Number hwndAfter = info[1].As(); 7 | Napi::Number X = info[2].As(); 8 | Napi::Number Y = info[3].As(); 9 | Napi::Number width = info[4].As(); 10 | Napi::Number height = info[5].As(); 11 | Napi::Number flags = info[6].As(); 12 | 13 | boolean result = SetWindowPos((HWND) hwnd.Int32Value(), (HWND) hwndAfter.Int32Value(), X, Y, width, height, flags); 14 | 15 | return Napi::Boolean::New(info.Env(), result); 16 | } 17 | 18 | Napi::Object RectToObj(Napi::Env env, RECT rect) { 19 | Napi::Object pos = Napi::Object::New(env); 20 | pos.Set(Napi::String::New(env, "top"), Napi::Number::New(env, rect.top)); 21 | pos.Set(Napi::String::New(env, "right"), Napi::Number::New(env, rect.right)); 22 | pos.Set(Napi::String::New(env, "bottom"), Napi::Number::New(env, rect.bottom)); 23 | pos.Set(Napi::String::New(env, "left"), Napi::Number::New(env, rect.left)); 24 | pos.Set(Napi::String::New(env, "width"), Napi::Number::New(env, rect.right - rect.left)); 25 | pos.Set(Napi::String::New(env, "height"), Napi::Number::New(env, rect.bottom - rect.top)); 26 | return pos; 27 | } 28 | 29 | Napi::Object GetWindowPosition(const Napi::CallbackInfo& info) { 30 | Napi::Number hwnd = info[0].As(); 31 | RECT rect; 32 | GetWindowRect((HWND) hwnd.Int32Value(), &rect); 33 | return RectToObj(info.Env(), rect); 34 | } 35 | 36 | Napi::Object GetClientPosition(const Napi::CallbackInfo& info) { 37 | Napi::Number hwnd = info[0].As(); 38 | RECT rect; 39 | GetClientRect((HWND) hwnd.Int32Value(), &rect); 40 | return RectToObj(info.Env(), rect); 41 | } 42 | 43 | Napi::Boolean GetWindowFullscreen(const Napi::CallbackInfo& info) 44 | { 45 | HWND hwnd = (HWND) info[0].As().Int32Value(); 46 | 47 | MONITORINFO monitorInfo = { 0 }; 48 | monitorInfo.cbSize = sizeof(MONITORINFO); 49 | GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY), &monitorInfo); 50 | 51 | RECT rect; 52 | GetWindowRect(hwnd, &rect); 53 | 54 | bool fullscreen = rect.left == monitorInfo.rcMonitor.left 55 | && rect.right == monitorInfo.rcMonitor.right 56 | && rect.top == monitorInfo.rcMonitor.top 57 | && rect.bottom == monitorInfo.rcMonitor.bottom; 58 | 59 | return Napi::Boolean::New(info.Env(), fullscreen); 60 | } 61 | 62 | Napi::Number GetForegroundWin(const Napi::CallbackInfo& info) { 63 | HWND result = GetForegroundWindow(); 64 | 65 | return Napi::Number::New(info.Env(), (long) result); 66 | } 67 | 68 | Napi::Boolean SetForegroundWin(const Napi::CallbackInfo& info) { 69 | Napi::Number hwnd = info[0].As(); 70 | 71 | boolean result = SetForegroundWindow((HWND) hwnd.Int32Value()); 72 | 73 | return Napi::Boolean::New(info.Env(), result); 74 | } 75 | 76 | Napi::Number GetWinLong(const Napi::CallbackInfo& info) { 77 | Napi::Number hwnd = info[0].As(); 78 | Napi::Number index = info[1].As(); 79 | 80 | LONG_PTR result = GetWindowLongPtr((HWND) hwnd.Int32Value(), (int) index.Int32Value()); 81 | 82 | return Napi::Number::New(info.Env(), result); 83 | } 84 | 85 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 86 | exports.Set(Napi::String::New(env, "setWindowPos"), Napi::Function::New(env, SetWindowPosition)); 87 | exports.Set(Napi::String::New(env, "getWindowPos"), Napi::Function::New(env, GetWindowPosition)); 88 | exports.Set(Napi::String::New(env, "getClientPos"), Napi::Function::New(env, GetClientPosition)); 89 | exports.Set(Napi::String::New(env, "getForegroundWindow"), Napi::Function::New(env, GetForegroundWin)); 90 | exports.Set(Napi::String::New(env, "setForegroundWindow"), Napi::Function::New(env, SetForegroundWin)); 91 | exports.Set(Napi::String::New(env, "getWindowLong"), Napi::Function::New(env, GetWinLong)); 92 | exports.Set(Napi::String::New(env, "getWindowFullscreen"), Napi::Function::New(env, GetWindowFullscreen)); 93 | return exports; 94 | } 95 | 96 | NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) -------------------------------------------------------------------------------- /src/modules/win32-displayconfig/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | 4 | # production 5 | build/ 6 | dist/ 7 | 8 | # misc 9 | .cache 10 | .DS_Store 11 | .env.local 12 | .env.development.local 13 | .env.test.local 14 | .env.production.local 15 | 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | .idea 20 | 21 | .log 22 | logs/ 23 | *.pfx 24 | .env 25 | cache/ -------------------------------------------------------------------------------- /src/modules/win32-displayconfig/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Daniel Sweet (@djsweet) 2 | -------------------------------------------------------------------------------- /src/modules/win32-displayconfig/COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright 2020 individuals listed in the CONTRIBUTORS file of this distribution. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTIBILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/modules/win32-displayconfig/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "openssl_fips" : "0" 4 | }, 5 | "targets": [ 6 | { 7 | "target_name": "win32_displayconfig", 8 | "cflags!": ["-fno-exceptions"], 9 | "cflags_cc!": ["-fno-exceptions"], 10 | "conditions": [ 11 | ["OS=='win'", { 12 | "sources": ["win32-displayconfig.cc"] 13 | }], 14 | ], 15 | "include_dirs": [ 16 | "; 126 | 127 | interface ConfigId { 128 | adapterId: AdapterId; 129 | id: number; 130 | } 131 | 132 | export interface ExtractedDisplayConfig { 133 | displayName: string; 134 | devicePath: string; 135 | sourceConfigId: ConfigId; 136 | targetConfigId: ConfigId; 137 | inUse: boolean; 138 | outputTechnology: string; 139 | rotation: number; 140 | scaling: string; 141 | sourceMode: SourceMode; 142 | targetVideoSignalInfo?: TargetVideoSignalInfo; 143 | pathBuffer: Buffer; 144 | sourceModeBuffer: Buffer; 145 | targetModeBuffer?: Buffer; 146 | } 147 | 148 | export function extractDisplayConfig(): Promise; 149 | 150 | export interface ToggleEnabledDisplayArgs { 151 | enablePaths: string[]; 152 | disablePaths: string[]; 153 | persistent: boolean; 154 | } 155 | 156 | export function toggleEnabledDisplays( 157 | args: ToggleEnabledDisplayArgs 158 | ): Promise; 159 | 160 | export interface DisplayResotrationConfigurationEntry { 161 | devicePath: string; 162 | pathBuffer: string; 163 | sourceModeBuffer: string; 164 | targetModeBuffer: string; 165 | } 166 | 167 | export function displayConfigForRestoration(): Promise< 168 | DisplayResotrationConfigurationEntry[] 169 | >; 170 | 171 | export interface RestoreDisplayConfigArgs { 172 | config: DisplayResotrationConfigurationEntry[]; 173 | persistent: boolean; 174 | } 175 | 176 | export function restoreDisplayConfig( 177 | args: RestoreDisplayConfigArgs 178 | ): Promise; 179 | 180 | export type DisplayChangeListener = { 181 | (err: Error): void; 182 | (err: null, conf: ExtractedDisplayConfig): void; 183 | }; 184 | 185 | export function addDisplayChangeListener( 186 | listener: DisplayChangeListener 187 | ): DisplayChangeListener; 188 | export function removeDisplayChangeListener( 189 | listener: DisplayChangeListener 190 | ): void; 191 | 192 | export class VerticalRefreshRateContext { 193 | findVerticalRefreshRateForDisplayPoint( 194 | x: number, 195 | y: number 196 | ): Promise; 197 | close(): void; 198 | } 199 | -------------------------------------------------------------------------------- /src/modules/win32-displayconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "win32-displayconfig", 3 | "version": "0.1.0", 4 | "description": "Inspect and reconfigure displays in Windows", 5 | "main": "index.js", 6 | "scripts": { 7 | "rebuild": "node-gyp rebuild", 8 | "install": "npm run rebuild" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/djsweet/win32-displayconfig.git" 13 | }, 14 | "keywords": [ 15 | "windows" 16 | ], 17 | "author": "djsweet", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/djsweet/win32-displayconfig/issues" 21 | }, 22 | "homepage": "https://github.com/djsweet/win32-displayconfig#readme", 23 | "dependencies": { 24 | "bindings": "^1.5.0", 25 | "node-addon-api": "^8.2.2", 26 | "node-gyp": "^10.2.0" 27 | }, 28 | "devDependencies": { 29 | "@types/node": "^14.0.27", 30 | "typescript": "^3.9.7" 31 | }, 32 | "engines": { 33 | "node": ">=10.16 <11 || >= 11.8" 34 | }, 35 | "files": [ 36 | "CONTRIBUTORS", 37 | "COPYRIGHT", 38 | "index.js", 39 | "index.d.ts", 40 | "binding.gyp", 41 | "win32-displayconfig.cc" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /src/modules/win32-displayconfig/scripts/dumpextract.js: -------------------------------------------------------------------------------- 1 | /* 2 | * scripts/dumpextract.js: part of the "win32-displayconfig" Node package. 3 | * See the COPYRIGHT file at the top-level directory of this distribution. 4 | */ 5 | "use strict"; 6 | const w32mon = require("../index"); 7 | const util = require("util"); 8 | 9 | w32mon.extractDisplayConfig().then((output) => { 10 | console.log(util.inspect(output, { depth: 10 })); 11 | }); 12 | -------------------------------------------------------------------------------- /src/modules/win32-displayconfig/scripts/dumpquery.js: -------------------------------------------------------------------------------- 1 | /* 2 | * scripts/dumpquery.js: part of the "win32-displayconfig" Node package. 3 | * See the COPYRIGHT file at the top-level directory of this distribution. 4 | */ 5 | "use strict"; 6 | const w32mon = require("../index"); 7 | const util = require("util"); 8 | 9 | w32mon.queryDisplayConfig().then((config) => { 10 | const pathArray = config.pathArray.map((pa) => pa.value); 11 | const modeArray = config.modeArray.map((ma) => ma.value); 12 | console.log( 13 | util.inspect( 14 | { pathArray, modeArray, nameArray: config.nameArray }, 15 | { depth: 10 } 16 | ) 17 | ); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/win32-displayconfig/scripts/watchmouse.js: -------------------------------------------------------------------------------- 1 | /* 2 | * scripts/watchmouse.js: part of the "win32-displayconfig" Node package. 3 | * See the COPYRIGHT file at the top-level directory of this distribution. 4 | */ 5 | "use strict"; 6 | const robotjs = require("robotjs"); 7 | const { VerticalRefreshRateContext } = require("../index"); 8 | 9 | const ctx = new VerticalRefreshRateContext(); 10 | let didShutdown = false; 11 | let outstanding = 0; 12 | 13 | setTimeout(() => { 14 | ctx.close(); 15 | didShutdown = true; 16 | console.log(`Shutting down with ${outstanding} outstanding`); 17 | }, 60 * 1000); 18 | 19 | const pollInterval = setInterval(async () => { 20 | outstanding++; 21 | if (didShutdown) { 22 | clearInterval(pollInterval); 23 | outstanding--; 24 | return; 25 | } 26 | const pos = robotjs.getMousePos(); 27 | const refreshRate = await ctx.findVerticalRefreshRateForDisplayPoint( 28 | pos.x, 29 | pos.y 30 | ); 31 | console.log( 32 | `Refresh rate for position ${pos.x}, ${ 33 | pos.y 34 | }: ${refreshRate} with ${outstanding--} outstanding` 35 | ); 36 | }, 250); 37 | -------------------------------------------------------------------------------- /src/modules/windows-hdr/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log* 3 | build 4 | .vscode/ipch 5 | .history -------------------------------------------------------------------------------- /src/modules/windows-hdr/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "windows-hdr", 5 | "cflags!": [ "-fno-exceptions" ], 6 | "cflags_cc!": [ "-fno-exceptions" ], 7 | "conditions": [ 8 | ["OS=='win'", { 9 | "sources": [ "windows-hdr.cc" ] 10 | }], 11 | ], 12 | "include_dirs": [ 13 | " 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | enum DISPLAYCONFIG_DEVICE_INFO_TYPE_INTERNAL { 10 | DISPLAYCONFIG_DEVICE_INFO_SET_SDR_WHITE_LEVEL = 0xFFFFFFEE, 11 | }; 12 | 13 | typedef struct _DISPLAYCONFIG_SET_SDR_WHITE_LEVEL { 14 | DISPLAYCONFIG_DEVICE_INFO_HEADER header; 15 | unsigned int SDRWhiteLevel; 16 | unsigned char finalValue; 17 | } _DISPLAYCONFIG_SET_SDR_WHITE_LEVEL; 18 | 19 | LONG pathSetSdrWhite(DISPLAYCONFIG_PATH_INFO path, int nits) { 20 | _DISPLAYCONFIG_SET_SDR_WHITE_LEVEL sdrWhiteParams = {}; 21 | sdrWhiteParams.header.type = (DISPLAYCONFIG_DEVICE_INFO_TYPE) DISPLAYCONFIG_DEVICE_INFO_SET_SDR_WHITE_LEVEL; 22 | sdrWhiteParams.header.size = sizeof(sdrWhiteParams); 23 | sdrWhiteParams.header.adapterId = path.targetInfo.adapterId; 24 | sdrWhiteParams.header.id = path.targetInfo.id; 25 | 26 | sdrWhiteParams.SDRWhiteLevel = nits * 1000 / 80; 27 | sdrWhiteParams.finalValue = 1; 28 | 29 | return DisplayConfigSetDeviceInfo(&sdrWhiteParams.header); 30 | } 31 | 32 | struct Display { 33 | std::string name; 34 | std::string path; 35 | int nits; 36 | boolean hdrSupported; 37 | boolean hdrEnabled; 38 | DISPLAYCONFIG_PATH_INFO target; 39 | int bits; 40 | }; 41 | 42 | std::string wcharToString(const wchar_t* wstr, boolean hasNullTerminator) { 43 | int size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr) - (hasNullTerminator ? 1 : 0); 44 | std::string str(size_needed, 0); 45 | WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &str[0], size_needed, nullptr, nullptr); 46 | return str; 47 | } 48 | 49 | std::map getDisplays() { 50 | std::map newDisplays; 51 | DISPLAYCONFIG_PATH_INFO *paths = 0; 52 | DISPLAYCONFIG_MODE_INFO *modes = 0; 53 | UINT32 pathCount, modeCount; 54 | { 55 | UINT32 flags = QDC_ONLY_ACTIVE_PATHS; 56 | LONG result = ERROR_SUCCESS; 57 | 58 | do { 59 | if (paths) { 60 | free(paths); 61 | } 62 | if (modes) { 63 | free(modes); 64 | } 65 | 66 | result = GetDisplayConfigBufferSizes(flags, &pathCount, &modeCount); 67 | 68 | if (result != ERROR_SUCCESS) { 69 | fprintf(stderr, "Error on GetDisplayConfigBufferSizes\n"); 70 | return newDisplays; 71 | } 72 | 73 | paths = (DISPLAYCONFIG_PATH_INFO *) malloc(pathCount * sizeof(paths[0])); 74 | modes = (DISPLAYCONFIG_MODE_INFO *) malloc(modeCount * sizeof(modes[0])); 75 | 76 | result = QueryDisplayConfig(flags, &pathCount, paths, &modeCount, modes, 0); 77 | if (result != ERROR_SUCCESS && result != ERROR_INSUFFICIENT_BUFFER) { 78 | fprintf(stderr, "Error on QueryDisplayConfig\n"); 79 | return newDisplays; 80 | } 81 | } while (result == ERROR_INSUFFICIENT_BUFFER); 82 | } 83 | 84 | for (int i = 0; i < pathCount; i++) { 85 | DISPLAYCONFIG_PATH_INFO path = paths[i]; 86 | 87 | DISPLAYCONFIG_TARGET_DEVICE_NAME targetName = {}; 88 | targetName.header.adapterId = path.targetInfo.adapterId; 89 | targetName.header.id = path.targetInfo.id; 90 | targetName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; 91 | targetName.header.size = sizeof(targetName); 92 | LONG result = DisplayConfigGetDeviceInfo(&targetName.header); 93 | 94 | if (result != ERROR_SUCCESS) { 95 | fprintf(stderr, "Error on DisplayConfigGetDeviceInfo for target name\n"); 96 | return newDisplays; 97 | } 98 | 99 | DISPLAYCONFIG_SDR_WHITE_LEVEL displayInfo = {}; 100 | displayInfo.header.type = (DISPLAYCONFIG_DEVICE_INFO_TYPE) DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL; 101 | displayInfo.header.size = sizeof(displayInfo); 102 | displayInfo.header.adapterId = path.targetInfo.adapterId; 103 | displayInfo.header.id = path.targetInfo.id; 104 | 105 | result = DisplayConfigGetDeviceInfo(&displayInfo.header); 106 | 107 | if (result != ERROR_SUCCESS) { 108 | fprintf(stderr, "Error on DisplayConfigGetDeviceInfo for SDR white level\n"); 109 | return newDisplays; 110 | } 111 | 112 | DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO hdrInfo = {}; 113 | hdrInfo.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO; 114 | hdrInfo.header.size = sizeof(DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO); 115 | hdrInfo.header.adapterId = path.targetInfo.adapterId; 116 | hdrInfo.header.id = path.targetInfo.id; 117 | 118 | result = DisplayConfigGetDeviceInfo(&hdrInfo.header); 119 | 120 | int nits = (int) displayInfo.SDRWhiteLevel * 80 / 1000; 121 | std::string monitorDevicePath = wcharToString(targetName.monitorDevicePath, false); 122 | 123 | Display newDisplay; 124 | newDisplay.name = wcharToString(targetName.monitorFriendlyDeviceName, true); 125 | newDisplay.path = monitorDevicePath.substr(0, monitorDevicePath.find("#{")); 126 | newDisplay.nits = nits; 127 | newDisplay.hdrSupported = hdrInfo.advancedColorSupported; 128 | newDisplay.hdrEnabled = hdrInfo.advancedColorEnabled; 129 | newDisplay.bits = hdrInfo.bitsPerColorChannel; 130 | newDisplay.target = path; 131 | 132 | newDisplays.insert({ newDisplay.name, newDisplay }); 133 | } 134 | 135 | return newDisplays; 136 | } 137 | 138 | boolean setSDRBrightness(DISPLAYCONFIG_PATH_INFO target, int desiredNits) { 139 | int nits = desiredNits; 140 | 141 | if (nits < 80) { 142 | nits = 80; 143 | } 144 | 145 | if (nits > 480) { 146 | nits = 480; 147 | } 148 | 149 | if (nits % 4 != 0) { 150 | nits += 4 - (nits % 4); 151 | } 152 | 153 | LONG result = pathSetSdrWhite(target, nits); 154 | 155 | if (result != ERROR_SUCCESS) { 156 | fprintf(stderr, "Error on DisplayConfigSetDeviceInfo for SDR white level\n"); 157 | return false; 158 | } 159 | 160 | return true; 161 | } 162 | 163 | 164 | Napi::Array nodeGetDisplays(const Napi::CallbackInfo& info) { 165 | std::map displays = getDisplays(); 166 | 167 | Napi::Env env = info.Env(); 168 | Napi::Array out = Napi::Array::New(env); 169 | 170 | int i = 0; 171 | for (auto& display : displays) { 172 | Napi::Object displayObj = Napi::Object::New(env); 173 | displayObj.Set(Napi::String::New(env, "name"), Napi::String::New(env, display.second.name)); 174 | displayObj.Set(Napi::String::New(env, "path"), Napi::String::New(env, display.second.path)); 175 | displayObj.Set(Napi::String::New(env, "nits"), Napi::Number::New(env, display.second.nits)); 176 | displayObj.Set(Napi::String::New(env, "hdrSupported"), Napi::Boolean::New(env, display.second.hdrSupported)); 177 | displayObj.Set(Napi::String::New(env, "hdrEnabled"), Napi::Boolean::New(env, display.second.hdrEnabled)); 178 | displayObj.Set(Napi::String::New(env, "bits"), Napi::Number::New(env, display.second.bits)); 179 | out.Set(i++, displayObj); 180 | } 181 | 182 | return out; 183 | } 184 | 185 | Napi::Boolean nodeSetSDRBrightness(const Napi::CallbackInfo& info) { 186 | if(info.Length() != 2) { 187 | fprintf(stderr, "Invalid number of parameters.\n"); 188 | return Napi::Boolean::New(info.Env(), false); 189 | } 190 | Napi::String path = info[0].As(); 191 | Napi::Number nits = info[1].As(); 192 | 193 | std::map displays = getDisplays(); 194 | 195 | boolean result = false; 196 | for (auto& display : displays) { 197 | if(display.second.path == (std::string)path) { 198 | result = setSDRBrightness(display.second.target, nits); 199 | break; 200 | } 201 | } 202 | 203 | return Napi::Boolean::New(info.Env(), result); 204 | } 205 | 206 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 207 | exports.Set(Napi::String::New(env, "getDisplays"), Napi::Function::New(env, nodeGetDisplays)); 208 | exports.Set(Napi::String::New(env, "setSDRBrightness"), Napi::Function::New(env, nodeSetSDRBrightness)); 209 | return exports; 210 | } 211 | 212 | NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) -------------------------------------------------------------------------------- /src/modules/wmi-bridge/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "wmi_bridge", 5 | "cflags!": [ "-fno-exceptions" ], 6 | "cflags_cc!": [ "-fno-exceptions" ], 7 | "conditions": [ 8 | ["OS=='win'", { 9 | "sources": [ "wmi-bridge.cc" ] 10 | }], 11 | ], 12 | "msvs_settings": { 13 | "VCCLCompilerTool": { 'AdditionalOptions': ['/permissive'], }, 14 | }, 15 | "include_dirs": [ 16 | " { 4 | console.log("==== TESTING WMIBRIDGE ====") 5 | const monitors = WMIBridgeTest.getMonitors(); 6 | console.log(`getMonitors: ${Object.keys(monitors)}`) 7 | 8 | const brightness = WMIBridgeTest.getBrightness(); 9 | console.log(`getBrightness:`, brightness) 10 | 11 | const ok = WMIBridgeTest.setBrightness(100); 12 | console.log(`setBrightness: ${ok}`) 13 | console.log('===========================') 14 | console.log(" ") 15 | console.log(" ") 16 | }, 500) -------------------------------------------------------------------------------- /src/modules/wmi-bridge/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const addon = require("bindings")("wmi_bridge"); 3 | require("os").setPriority(0, require("os").constants.priority.PRIORITY_BELOW_NORMAL) 4 | 5 | class WMIBridge { 6 | constructor() {} 7 | setBrightness = async (level = 50) => { 8 | let ok = false 9 | try { 10 | ok = addon.setBrightness(level) 11 | } catch(e) { console.log(e) } 12 | return ok 13 | } 14 | getBrightness = async () => { 15 | let brightness = { failed: true } 16 | try { 17 | brightness = addon.getBrightness() 18 | } catch (e) { console.log(e) } 19 | return brightness 20 | } 21 | getMonitors = async () => { 22 | try { 23 | return addon.getMonitors() 24 | } catch(e) { 25 | console.log(e) 26 | return { failed: true } 27 | } 28 | } 29 | } 30 | 31 | module.exports = new WMIBridge(); -------------------------------------------------------------------------------- /src/modules/wmi-bridge/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wmi-bridge", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node example.js", 8 | "rebuild": "node-gyp rebuild", 9 | "install": "npm run rebuild" 10 | }, 11 | "author": "xanderfrangos", 12 | "dependencies": { 13 | "bindings": "1.5.0", 14 | "node-addon-api": "3.1.0" 15 | }, 16 | "files": [ 17 | "index.js", 18 | "binding.gyp", 19 | "wmi-bridge.cc", 20 | "example.js" 21 | ], 22 | "devDependencies": { 23 | "@types/node": "^14.14.32", 24 | "typescript": "^4.2.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/monitor-rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "ddcBrightnessCodes": { 3 | "FUS087C": 107, 4 | "FUS06AB": 19 5 | }, 6 | "skipReapply": [ 7 | "DEL41D9" 8 | ] 9 | } -------------------------------------------------------------------------------- /src/panel.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createRoot } from 'react-dom/client'; 3 | import BrightnessPanel from "./components/BrightnessPanel"; 4 | 5 | if (window.settings == undefined) window.settings = {} 6 | 7 | createRoot(document.getElementById("root")).render() 8 | 9 | window.updateMica = () => { 10 | const pos = [window.winPosition[0], window.winPosition[1]] 11 | if(window.settings.isWin11) { 12 | pos[0] += 12 13 | pos[1] += 12 14 | } 15 | const micaDisplays = document.querySelector("#mica .displays") 16 | if(micaDisplays) micaDisplays.style.transform = `translate(${pos[0] * -1}px, ${pos[1] * -1}px)`; 17 | 18 | } 19 | 20 | // Demo mode 21 | window.addEventListener("enableDemoMode", () => { 22 | window.allMonitors = [{ 23 | "brightness": 63, 24 | "device": "\\\\?\\DISPLAY#ACR0408#5&2e7612e0&0&UID4357#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}", 25 | "id": "\\\\.\\DISPLAY1", 26 | "localID": 0, 27 | "max": 100, 28 | "min": 0, 29 | "name": "XB270HU", 30 | "num": 0, 31 | "type": "ddcci" 32 | }, { 33 | "brightness": 46, 34 | "device": "\\\\?\\DISPLAY#DELA0BC#5&2e7612e0&0&UID4356#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}", 35 | "id": "\\\\.\\DISPLAY2", 36 | "localID": 1, 37 | "max": 100, 38 | "min": 0, 39 | "name": "DELL U2415", 40 | "num": 1, 41 | "type": "ddcci" 42 | }] 43 | window.dispatchEvent(new Event("monitorsUpdated")) 44 | window.document.getElementById("root").dataset["visible"] = true 45 | window.document.body.style.setProperty("--system-accent-color", window.accent || "#744DA9") 46 | }) 47 | 48 | window.document.getElementById("root").addEventListener('transitionend', function () { 49 | window.panelAnimationDone() 50 | }); 51 | 52 | window.document.addEventListener('keydown', (e) => { 53 | if (e.key === "Escape") { 54 | window.ipc.send('blur-panel') 55 | } 56 | }) 57 | 58 | allMonitors = {} 59 | window.ipc.send('get-mica-wallpaper') 60 | window.ipc.send('get-refreshing') 61 | window.ipc.send('request-localization') 62 | setTimeout(() => { 63 | window.ipc.send('request-localization') 64 | }, 200) -------------------------------------------------------------------------------- /src/parcel.js: -------------------------------------------------------------------------------- 1 | const ParcelAPI = require("./parcelAPI") 2 | 3 | let mode 4 | let logLevel 5 | try { 6 | let modeFlagPos = (process.argv.indexOf("--mode")) 7 | mode = (process.argv[modeFlagPos + 1]) 8 | } catch(e) { 9 | console.log("Couldn't read mode flag.", e) 10 | } 11 | try { 12 | let logFlagPos = (process.argv.indexOf("--logLevel")) 13 | logLevel = (process.argv[logFlagPos + 1]) 14 | } catch(e) { 15 | console.log("Couldn't read log flag.", e) 16 | } 17 | ParcelAPI(mode, logLevel) -------------------------------------------------------------------------------- /src/parcelAPI.js: -------------------------------------------------------------------------------- 1 | const Bundler = require('parcel-bundler') 2 | const Path = require('path') 3 | const fs = require("fs") 4 | 5 | const entryFiles = Path.join(__dirname, './html/*.html') 6 | 7 | const optionsDev = { 8 | outDir: './cache', 9 | watch: true 10 | } 11 | 12 | const optionsProd = { 13 | outDir: './build', 14 | publicUrl: './', 15 | watch: true, 16 | cache: false, 17 | sourceMaps: false, 18 | minify: true, 19 | scopeHoist: true 20 | } 21 | 22 | function clearDirectory(relativePath) { 23 | const dir = Path.join(__dirname, relativePath) 24 | if (fs.existsSync(dir)){ 25 | fs.rmSync(dir, { recursive: true }) 26 | } 27 | if (!fs.existsSync(dir)){ 28 | fs.mkdirSync(dir) 29 | } 30 | } 31 | 32 | async function runParcel(mode = "dev", logLevel = null) { 33 | const parcelMode = mode?.toLocaleLowerCase?.() 34 | if(parcelMode == "dev") { 35 | clearDirectory('../cache') 36 | const bundler = new Bundler(entryFiles, Object.assign(optionsDev, { watch: true, logLevel: (logLevel ?? 3) })) 37 | return await bundler.serve(3000) 38 | } 39 | if(parcelMode == "live") { 40 | clearDirectory('../build') 41 | const bundler = new Bundler(entryFiles, Object.assign(optionsProd, { watch: true, logLevel: (logLevel ?? 1) })) 42 | return await bundler.bundle() 43 | } 44 | if(parcelMode == "build") { 45 | clearDirectory('../build') 46 | const bundler = new Bundler(entryFiles, Object.assign(optionsProd, { watch: false, logLevel: (logLevel ?? 3) })) 47 | return await bundler.bundle() 48 | } 49 | } 50 | 51 | module.exports = runParcel -------------------------------------------------------------------------------- /src/settings.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createRoot } from 'react-dom/client'; 3 | import SettingsWindow from "./Components/SettingsWindow"; 4 | 5 | const micaDisplays = document.querySelector("#mica .displays") 6 | 7 | function updateMicaPosition(pos = [0, 0]) { 8 | micaDisplays.style.transform = `translate(${pos[0] * -1}px, ${pos[1] * -1}px)` 9 | } 10 | window.ipc.on("settingsWindowMove", (e, position) => { 11 | updateMicaPosition(position) 12 | }) 13 | 14 | window.addEventListener("blur", () => { 15 | document.body.dataset.focused = "false" 16 | }) 17 | 18 | window.addEventListener("focus", () => { 19 | document.body.dataset.focused = "true" 20 | }) 21 | 22 | createRoot(document.getElementById("settings")).render() 23 | 24 | setTimeout(() => { 25 | window.requestSettings() 26 | }, 33) -------------------------------------------------------------------------------- /src/vcp-codes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Full list of MCCS v2.2a VCP codes. 3 | */ 4 | module.exports = { 5 | // 8.1 - Preset Functions 6 | CODE_PAGE: 0x00 7 | , RESTORE_FACTORY_COLOR_DEFAULTS: 0x08 8 | , RESTORE_FACTORY_DEFAULTS: 0x04 9 | , RESTORE_FACTORY_GEOMETRY_DEFAULTS: 0x06 10 | , RESTORE_FACTORY_LUMINANCE_CONTRAST_DEFAULTS: 0x05 11 | , RESTORE_FACTORY_TV_DEFAULTS: 0x0a 12 | , SAVE_RESTORE_SETTINGS: 0xb0 13 | 14 | // 8.2 - Image Adjustment 15 | , SIX_AXIS_HUE_CONTROL_BLUE: 0x9f 16 | , SIX_AXIS_HUE_CONTROL_CYAN: 0x9e 17 | , SIX_AXIS_HUE_CONTROL_GREEN: 0x9d 18 | , SIX_AXIS_HUE_CONTROL_MAGENTA: 0xa0 19 | , SIX_AXIS_HUE_CONTROL_RED: 0x9b 20 | , SIX_AXIS_HUE_CONTROL_YELLOW: 0x9c 21 | , SIX_AXIS_SATURATION_CONTROL_BLUE: 0x5d 22 | , SIX_AXIS_SATURATION_CONTROL_CYAN: 0x5c 23 | , SIX_AXIS_SATURATION_CONTROL_GREEN: 0x5b 24 | , SIX_AXIS_SATURATION_CONTROL_MAGENTA: 0x5e 25 | , SIX_AXIS_SATURATION_CONTROL_RED: 0x59 26 | , SIX_AXIS_SATURATION_CONTROL_YELLOW: 0x5a 27 | , ADJUST_ZOOM: 0x7c 28 | , AUTO_COLOR_SETUP: 0x1f 29 | , AUTO_SETUP: 0x1e 30 | , AUTO_SETUP_ON_OFF: 0xa2 31 | , BACKLIGHT_CONTROL: 0x13 32 | , BACKLIGHT_LEVEL_BLUE: 0x71 33 | , BACKLIGHT_LEVEL_GREEN: 0x6f 34 | , BACKLIGHT_LEVEL_RED: 0x6d 35 | , BACKLIGHT_LEVEL_WHITE: 0x6b 36 | , BLOCK_LUT_OPERATION: 0x75 37 | , CLOCK: 0x0e 38 | , CLOCK_PHASE: 0x3e 39 | , COLOR_SATURATION: 0x8a 40 | , COLOR_TEMPERATURE_INCREMENT: 0x0b 41 | , COLOR_TEMPERATURE_REQUEST: 0x0c 42 | , CONTRAST: 0x12 43 | , DISPLAY_APPLICATION: 0xdc 44 | , FLESH_TONE_ENHANCEMENT: 0x11 45 | , FOCUS: 0x1c 46 | , GAMMA: 0x72 47 | , GRAY_SCALE_EXPANSION: 0x2e 48 | , HORIZONTAL_MOIRE: 0x56 49 | , HUE: 0x90 50 | , LUMINANCE: 0x10 51 | , LUT_SIZE: 0x73 52 | , SCREEN_ORIENTATION: 0xaa 53 | , SELECT_COLOR_PRESET: 0x14 54 | , SHARPNESS: 0x87 55 | , SINGLE_POINT_LUT_OPERATION: 0x74 56 | , STEREO_VIDEO_MODE: 0xd4 57 | , TV_BLACK_LEVEL_LUMINANCE: 0x92 58 | , TV_CONTRAST: 0x8e 59 | , TV_SHARPNESS: 0x8c 60 | , USER_COLOR_VISION_COMPENSATION: 0x17 61 | , VELOCITY_SCAN_MODULATION: 0x88 62 | , VERTICAL_MOIRE: 0x58 63 | , VIDEO_BLACK_LEVEL_BLUE: 0x70 64 | , VIDEO_BLACK_LEVEL_GREEN: 0x6e 65 | , VIDEO_BLACK_LEVEL_RED: 0x6c 66 | , VIDEO_GAIN_BLUE: 0x1a 67 | , VIDEO_GAIN_GREEN: 0x18 68 | , VIDEO_GAIN_RED: 0x16 69 | , WINDOW_BACKGROUND: 0x9a 70 | , WINDOW_CONTROL_ON_OFF: 0xa4 71 | , WINDOW_SELECT: 0xa5 72 | , WINDOW_SIZE: 0xa6 73 | , WINDOW_TRANSPARENCY: 0xa7 74 | 75 | // 8.3 - Display Control 76 | , DISPLAY_CONTROLLER_ID: 0xc8 77 | , DISPLAY_FIRMWARE_LEVEL: 0xc9 78 | , DISPLAY_USAGE_TIME: 0xc6 79 | , HORIZONTAL_FREQUENCY: 0xac 80 | , IMAGE_MODE: 0xdb 81 | , OSD_BUTTON_LEVEL_CONTROL: 0xca 82 | , OSD_LANGUAGE: 0xcc 83 | , POWER_MODE: 0xd6 84 | , SOURCE_COLOR_CODING: 0xb5 85 | , SOURCE_TIMING_MODE: 0xb4 86 | , VERSION: 0xdf 87 | , VERTICAL_FREQUENCY: 0xae 88 | 89 | // 8.4 - Geometry 90 | , BOTTOM_CORNER_FLARE: 0x4a 91 | , BOTTOM_CORNER_HOOK: 0x4c 92 | , DISPLAY_SCALING: 0x86 93 | , HORIZONTAL_CONVERGENCE_M_G: 0x29 94 | , HORIZONTAL_CONVERGENCE_R_B: 0x28 95 | , HORIZONTAL_KEYSTONE: 0x42 96 | , HORIZONTAL_LINEARITY: 0x2a 97 | , HORIZONTAL_LINEARITY_BALANCE: 0x2c 98 | , HORIZONTAL_MIRROR: 0x82 99 | , HORIZONTAL_PARALLELOGRAM: 0x40 100 | , HORIZONTAL_PINCUSHION: 0x24 101 | , HORIZONTAL_PINCUSHION_BALANCE: 0x26 102 | , HORIZONTAL_POSITION: 0x20 103 | , HORIZONTAL_SIZE: 0x22 104 | , ROTATION: 0x44 105 | , SCAN_MODE: 0xda 106 | , TOP_CORNER_FLARE: 0x46 107 | , TOP_CORNER_HOOK: 0x48 108 | , VERTICAL_CONVERGENCE_M_G: 0x39 109 | , VERTICAL_CONVERGENCE_R_B: 0x38 110 | , VERTICAL_KEYSTONE: 0x43 111 | , VERTICAL_LINEARITY: 0x3a 112 | , VERTICAL_LINEARITY_BALANCE: 0x3c 113 | , VERTICAL_MIRROR: 0x84 114 | , VERTICAL_PARALLELOGRAM: 0x41 115 | , VERTICAL_PINCUSHION: 0x34 116 | , VERTICAL_PINCUSHION_BALANCE: 0x36 117 | , VERTICAL_POSITION: 0x30 118 | , VERTICAL_SIZE: 0x32 119 | , WINDOW_POSITION_BR_X: 0x97 120 | , WINDOW_POSITION_BR_Y: 0x98 121 | , WINDOW_POSITION_TL_X: 0x95 122 | , WINDOW_POSITION_TL_Y: 0x96 123 | 124 | // 8.5 - Miscellaneous 125 | , ACTIVE_CONTROL: 0x52 126 | , AMBIENT_LIGHT_SENSOR: 0x66 127 | , APPLICATION_ENABLE_KEY: 0xc6 128 | , ASSET_TAG: 0xd2 129 | , AUXILIARY_DISPLAY_DATA: 0xcf 130 | , AUXILIARY_DISPLAY_SIZE: 0xce 131 | , AUXILIARY_POWER_OUTPUT: 0xd7 132 | , DEGAUSS: 0x01 133 | , DISPLAY_DESCRIPTOR_LENGTH: 0xc2 134 | , DISPLAY_IDENTIFICATION_DATA_OPERATION: 0x87 135 | , DISPLAY_TECHNOLOGY_TYPE: 0xb6 136 | , ENABLE_DISPLAY_OF_DISPLAY_DESCRIPTOR: 0xc4 137 | , FLAT_PANEL_SUB_PIXEL_LAYOUT: 0xb2 138 | , INPUT_SOURCE: 0x60 139 | , NEW_CONTROL_VALUE: 0x02 140 | , OUTPUT_SELECT: 0xd0 141 | , PERFORMANCE_PRESERVATION: 0x54 142 | , REMOTE_PROCEDURE_CALL: 0x76 143 | , SCRATCH_PAD: 0xde 144 | , SOFT_CONTROLS: 0x03 145 | , STATUS_INDICATORS: 0xcd 146 | , TRANSMIT_DISPLAY_DESCRIPTOR: 0xc3 147 | , TV_CHANNEL_UP_DOWN: 0x8b 148 | 149 | // 8.6 - Audio Function 150 | , AUDIO_BALANCE_L_R: 0x93 151 | , AUDIO_BASS: 0x91 152 | , AUDIO_JACK_CONNECTION_STATUS: 0x65 153 | , AUDIO_MICROPHONE_VOLUME: 0x64 154 | , AUDIO_MUTE: 0x8d 155 | , AUDIO_PROCESSOR_MODE: 0x94 156 | , AUDIO_SPEAKER_SELECT: 0x63 157 | , AUDIO_SPEAKER_VOLUME: 0x62 158 | , AUDIO_TREBLE: 0x8f 159 | 160 | // 8.7 - DPVL Support 161 | , BODY_CRC_ERROR_COUNT: 0xbc 162 | , CLIENT_ID: 0xbd 163 | , HEADER_ERROR_COUNT: 0xbb 164 | , LINK_CONTROL: 0xbe 165 | , MONITOR_STATUS: 0xb7 166 | , MONITOR_X_ORIGIN: 0xb9 167 | , MONITOR_Y_ORIGIN: 0xba 168 | , PACKET_COUNT: 0xb8 169 | }; 170 | -------------------------------------------------------------------------------- /src/wmi-bridge-test.js: -------------------------------------------------------------------------------- 1 | console.log("\x1b[45mRunning wmi-bridge test...\x1b[0m") 2 | const wmibridge = require("wmi-bridge"); 3 | require("os").setPriority(0, require("os").constants.priority.PRIORITY_BELOW_NORMAL) 4 | 5 | function readInstanceName(insName) { 6 | return (insName ? insName.replace(/&/g, '&').split("\\") : undefined) 7 | } 8 | 9 | // For testing timeouts 10 | function wait4s() { 11 | return new Promise(resolve => { 12 | setTimeout(() => { 13 | resolve(true); 14 | }, 4000); 15 | }); 16 | } 17 | 18 | getMonitorsWMI = () => { 19 | return new Promise(async (resolve, reject) => { 20 | const foundMonitors = {} 21 | try { 22 | const wmiMonitors = await wmibridge.getMonitors(); 23 | 24 | if (wmiMonitors.failed) { 25 | // Something went wrong 26 | console.log("\x1b[41m" + "wmi-bridge-test: Recieved FAILED response from getMonitors()" + "\x1b[0m") 27 | reject(foundMonitors) 28 | } else { 29 | // Sort through results 30 | for (let monitorHWID in wmiMonitors) { 31 | const monitor = wmiMonitors[monitorHWID] 32 | 33 | if (!monitor.InstanceName) continue; 34 | 35 | let hwid = readInstanceName(monitor.InstanceName) 36 | hwid[2] = hwid[2].split("_")[0] 37 | 38 | const wmiInfo = { 39 | id: `\\\\?\\${hwid[0]}#${hwid[1]}#${hwid[2]}`, 40 | key: hwid[2], 41 | hwid: hwid, 42 | serial: monitor.SerialNumberID 43 | } 44 | 45 | if (monitor.UserFriendlyName !== null && monitor.UserFriendlyName !== "") { 46 | wmiInfo.name = monitor.UserFriendlyName 47 | } 48 | 49 | foundMonitors[hwid[2]] = wmiInfo 50 | } 51 | } 52 | } catch (e) { 53 | console.log(`wmi-bridge-test: Failed to get all monitors.`) 54 | console.log(e) 55 | } 56 | resolve(foundMonitors) 57 | }) 58 | } 59 | 60 | 61 | 62 | process.send({ 63 | type: 'ready' 64 | }) 65 | 66 | //wait4s().then(() => { }) 67 | getMonitorsWMI().then(() => { 68 | process.send({ type: 'ok' }) 69 | }).catch(() => { 70 | process.send({ type: 'failed' }) 71 | }) --------------------------------------------------------------------------------