├── .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 |
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 |
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 |
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 |
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 |
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