├── .github └── workflows │ └── notify.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── flake.lock ├── flake.nix ├── hyprpm.toml ├── res └── preview.png └── src ├── DecorationsWrapper.h ├── Helpers.cpp ├── Helpers.h ├── TexturesDark.h ├── WindowInverter.cpp ├── WindowInverter.h └── main.cpp /.github/workflows/notify.yml: -------------------------------------------------------------------------------- 1 | name: Hyprland Breaking Change Notifier 2 | 3 | on: 4 | schedule: 5 | - cron: "0 3 * * *" 6 | workflow_dispatch: {} 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Install Nix 16 | uses: cachix/install-nix-action@v26 17 | 18 | - name: Nix Cache 19 | uses: DeterminateSystems/magic-nix-cache-action@main 20 | 21 | - name: Update 22 | run: nix flake update 23 | 24 | - name: Build 25 | run: nix build --extra-trusted-substituters "https://hyprland.cachix.org" -L 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | hyprland/ 2 | out/ 3 | .vscode/ 4 | .envrc 5 | .direnv -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 micha4w 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. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # use with make target VERSION=tags/0.31.0 2 | VERSION = HEAD 3 | 4 | 5 | all: 6 | mkdir -p out 7 | $(CXX) -shared -fPIC --no-gnu-unique src/*.cpp -Isrc/ -o out/hyprchroma.so -g `pkg-config --cflags pixman-1 libdrm hyprland hyprlang` -std=c++2b -DWLR_USE_UNSTABLE 8 | 9 | build-version: 10 | mkdir -p "out/$(VERSION)" 11 | $(CXX) -shared -fPIC --no-gnu-unique src/*.cpp -Isrc/ -I"hyprland/$(VERSION)/include/hyprland/protocols" -I"hyprland/$(VERSION)/include/hyprland/wlroots" -I"hyprland/$(VERSION)/include/" -o "out/$(VERSION)/hyprchroma.so" -g `pkg-config --cflags pixman-1 libdrm` -std=c++2b -DWLR_USE_UNSTABLE 12 | 13 | clean: 14 | rm -rf out 15 | rm -rf hyprland 16 | 17 | load: unload 18 | hyprctl plugin load $(shell pwd)/out/$(VERSION)/hyprchroma.so 19 | 20 | unload: 21 | hyprctl plugin unload $(shell pwd)/out/$(VERSION)/hyprchroma.so 22 | 23 | setup-dev: 24 | ifeq ("$(wildcard hyprland/$(VERSION))","") 25 | mkdir -p "hyprland/$(VERSION)" 26 | git clone https://github.com/hyprwm/Hyprland "hyprland/$(VERSION)" 27 | cd "hyprland/$(VERSION)" && git checkout "$(VERSION)" && git submodule update --init 28 | endif 29 | cd "hyprland/$(VERSION)" && make debug && make installheaders PREFIX="." 30 | 31 | setup-headers: 32 | ifeq ("$(wildcard hyprland/$(VERSION))","") 33 | mkdir -p "hyprland/$(VERSION)" 34 | git clone https://github.com/hyprwm/Hyprland "hyprland/$(VERSION)" 35 | cd "hyprland/$(VERSION)" && git checkout "$(VERSION)" && git submodule update --init 36 | endif 37 | cd "hyprland/$(VERSION)" && make all && make installheaders PREFIX="`pwd`" 38 | 39 | dev: 40 | hyprland/$(VERSION)/build/Hyprland 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hyprchroma 2 | 3 | ![2024-10-18-000536_hyprshot](https://github.com/user-attachments/assets/d47d78e7-5ddd-4637-83d4-6a8a7be2e0ce) 4 | 5 | Hyprchroma is a Hyprland plugin that applies a chromakey effect for global window background transparency without affecting readability 6 | 7 | ## Configuration 8 | ```conf 9 | # hyprland.conf 10 | windowrulev2 = plugin:chromakey,fullscreen:0 11 | chromakey_background = 7,8,17 12 | ``` 13 | 14 | Also adds 2 Dispatches `togglewindowchromakey WINDOW` and `togglechromakey` (for the active window) 15 | 16 | ## Installation 17 | 18 | ### Hyprland >= v0.36.0 19 | We now support Nix, wooo! 20 | 21 | ### Hyprpm 22 | 23 | outputs = { 24 | home-manager, 25 | hypr-darkwindow, 26 | ... 27 | }: { 28 | ... = { 29 | home-manager.users.micha4w = { 30 | wayland.windowManager.hyprland.plugins = [ 31 | hypr-darkwindow.packages.${pkgs.system}.Hypr-DarkWindow 32 | ]; 33 | }; 34 | }; 35 | } 36 | ``` 37 | 38 | > [!NOTE] 39 | > In this example `inputs.hypr-darkwindow.url` sets the tag, Make sure that tag matches your Hyprland version. 40 | 41 | 42 | ### Hyprland >= v0.34.0 43 | Install using `hyprpm` 44 | ```sh 45 | hyprpm add https://github.com/alexhulbert/Hyprchroma 46 | hyprpm enable hyprchroma 47 | hyprpm reload 48 | ``` 49 | 50 | ### Nix 51 | 52 | For nix instructions, refer to the parent repository. 53 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "aquamarine": { 4 | "inputs": { 5 | "hyprutils": [ 6 | "hyprland", 7 | "hyprutils" 8 | ], 9 | "hyprwayland-scanner": [ 10 | "hyprland", 11 | "hyprwayland-scanner" 12 | ], 13 | "nixpkgs": [ 14 | "hyprland", 15 | "nixpkgs" 16 | ], 17 | "systems": [ 18 | "hyprland", 19 | "systems" 20 | ] 21 | }, 22 | "locked": { 23 | "lastModified": 1734400729, 24 | "narHash": "sha256-Bf+oya0BuleVXYGIWsb0eWnrK6s0aiesOsI7Mpj1pMU=", 25 | "owner": "hyprwm", 26 | "repo": "aquamarine", 27 | "rev": "a132fa41be7ebe797ad758e84d9df068151a723b", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "hyprwm", 32 | "repo": "aquamarine", 33 | "type": "github" 34 | } 35 | }, 36 | "flake-compat": { 37 | "flake": false, 38 | "locked": { 39 | "lastModified": 1696426674, 40 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 41 | "owner": "edolstra", 42 | "repo": "flake-compat", 43 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "owner": "edolstra", 48 | "repo": "flake-compat", 49 | "type": "github" 50 | } 51 | }, 52 | "gitignore": { 53 | "inputs": { 54 | "nixpkgs": [ 55 | "hyprland", 56 | "pre-commit-hooks", 57 | "nixpkgs" 58 | ] 59 | }, 60 | "locked": { 61 | "lastModified": 1709087332, 62 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 63 | "owner": "hercules-ci", 64 | "repo": "gitignore.nix", 65 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 66 | "type": "github" 67 | }, 68 | "original": { 69 | "owner": "hercules-ci", 70 | "repo": "gitignore.nix", 71 | "type": "github" 72 | } 73 | }, 74 | "hyprcursor": { 75 | "inputs": { 76 | "hyprlang": [ 77 | "hyprland", 78 | "hyprlang" 79 | ], 80 | "nixpkgs": [ 81 | "hyprland", 82 | "nixpkgs" 83 | ], 84 | "systems": [ 85 | "hyprland", 86 | "systems" 87 | ] 88 | }, 89 | "locked": { 90 | "lastModified": 1734364709, 91 | "narHash": "sha256-+2bZJL2u5hva7rSp65OfKJBK+k03T6GB/NCvpoS1OOo=", 92 | "owner": "hyprwm", 93 | "repo": "hyprcursor", 94 | "rev": "f388aacd22be4a6e4d634fbaf6f75eb0713d239a", 95 | "type": "github" 96 | }, 97 | "original": { 98 | "owner": "hyprwm", 99 | "repo": "hyprcursor", 100 | "type": "github" 101 | } 102 | }, 103 | "hyprgraphics": { 104 | "inputs": { 105 | "hyprutils": [ 106 | "hyprland", 107 | "hyprutils" 108 | ], 109 | "nixpkgs": [ 110 | "hyprland", 111 | "nixpkgs" 112 | ], 113 | "systems": [ 114 | "hyprland", 115 | "systems" 116 | ] 117 | }, 118 | "locked": { 119 | "lastModified": 1733684019, 120 | "narHash": "sha256-2kYREgmSmbLsmDpLEq96hxVAU3qz8aCvVhF65yCFZHY=", 121 | "owner": "hyprwm", 122 | "repo": "hyprgraphics", 123 | "rev": "fb2c0268645a77403af3b8a4ce8fa7ba5917f15d", 124 | "type": "github" 125 | }, 126 | "original": { 127 | "owner": "hyprwm", 128 | "repo": "hyprgraphics", 129 | "type": "github" 130 | } 131 | }, 132 | "hyprland": { 133 | "inputs": { 134 | "aquamarine": "aquamarine", 135 | "hyprcursor": "hyprcursor", 136 | "hyprgraphics": "hyprgraphics", 137 | "hyprland-protocols": "hyprland-protocols", 138 | "hyprland-qtutils": "hyprland-qtutils", 139 | "hyprlang": "hyprlang", 140 | "hyprutils": "hyprutils", 141 | "hyprwayland-scanner": "hyprwayland-scanner", 142 | "nixpkgs": "nixpkgs", 143 | "pre-commit-hooks": "pre-commit-hooks", 144 | "systems": "systems", 145 | "xdph": "xdph" 146 | }, 147 | "locked": { 148 | "lastModified": 1734423633, 149 | "narHash": "sha256-C4qDR/8vVqciYvq5tAwBViSQVnb7LFhYjXO89pz39bk=", 150 | "ref": "refs/heads/main", 151 | "rev": "c7d97199103fd1ca43c2a9810c085df5500169ac", 152 | "revCount": 5558, 153 | "submodules": true, 154 | "type": "git", 155 | "url": "https://github.com/hyprwm/Hyprland" 156 | }, 157 | "original": { 158 | "submodules": true, 159 | "type": "git", 160 | "url": "https://github.com/hyprwm/Hyprland" 161 | } 162 | }, 163 | "hyprland-protocols": { 164 | "inputs": { 165 | "nixpkgs": [ 166 | "hyprland", 167 | "nixpkgs" 168 | ], 169 | "systems": [ 170 | "hyprland", 171 | "systems" 172 | ] 173 | }, 174 | "locked": { 175 | "lastModified": 1728345020, 176 | "narHash": "sha256-xGbkc7U/Roe0/Cv3iKlzijIaFBNguasI31ynL2IlEoM=", 177 | "owner": "hyprwm", 178 | "repo": "hyprland-protocols", 179 | "rev": "a7c183800e74f337753de186522b9017a07a8cee", 180 | "type": "github" 181 | }, 182 | "original": { 183 | "owner": "hyprwm", 184 | "repo": "hyprland-protocols", 185 | "type": "github" 186 | } 187 | }, 188 | "hyprland-qtutils": { 189 | "inputs": { 190 | "hyprutils": [ 191 | "hyprland", 192 | "hyprutils" 193 | ], 194 | "nixpkgs": [ 195 | "hyprland", 196 | "nixpkgs" 197 | ], 198 | "systems": [ 199 | "hyprland", 200 | "systems" 201 | ] 202 | }, 203 | "locked": { 204 | "lastModified": 1733940128, 205 | "narHash": "sha256-hmfXWj2GA9cj1QUkPFYtAAeohhs615zL4E3APy3FnvQ=", 206 | "owner": "hyprwm", 207 | "repo": "hyprland-qtutils", 208 | "rev": "3833097e50473a152dd614d4b468886840b4ea78", 209 | "type": "github" 210 | }, 211 | "original": { 212 | "owner": "hyprwm", 213 | "repo": "hyprland-qtutils", 214 | "type": "github" 215 | } 216 | }, 217 | "hyprlang": { 218 | "inputs": { 219 | "hyprutils": [ 220 | "hyprland", 221 | "hyprutils" 222 | ], 223 | "nixpkgs": [ 224 | "hyprland", 225 | "nixpkgs" 226 | ], 227 | "systems": [ 228 | "hyprland", 229 | "systems" 230 | ] 231 | }, 232 | "locked": { 233 | "lastModified": 1734364628, 234 | "narHash": "sha256-ii8fzJfI953n/EmIxVvq64ZAwhvwuuPHWfGd61/mJG8=", 235 | "owner": "hyprwm", 236 | "repo": "hyprlang", 237 | "rev": "16e59c1eb13d9fb6de066f54e7555eb5e8a4aba5", 238 | "type": "github" 239 | }, 240 | "original": { 241 | "owner": "hyprwm", 242 | "repo": "hyprlang", 243 | "type": "github" 244 | } 245 | }, 246 | "hyprutils": { 247 | "inputs": { 248 | "nixpkgs": [ 249 | "hyprland", 250 | "nixpkgs" 251 | ], 252 | "systems": [ 253 | "hyprland", 254 | "systems" 255 | ] 256 | }, 257 | "locked": { 258 | "lastModified": 1734384247, 259 | "narHash": "sha256-bl3YyJb2CgaeVKYq/l8j27vKdbkTpDNFDsnCl0dnNlY=", 260 | "owner": "hyprwm", 261 | "repo": "hyprutils", 262 | "rev": "e6cf45cd1845368702e03b8912f4cc44ebba3322", 263 | "type": "github" 264 | }, 265 | "original": { 266 | "owner": "hyprwm", 267 | "repo": "hyprutils", 268 | "type": "github" 269 | } 270 | }, 271 | "hyprwayland-scanner": { 272 | "inputs": { 273 | "nixpkgs": [ 274 | "hyprland", 275 | "nixpkgs" 276 | ], 277 | "systems": [ 278 | "hyprland", 279 | "systems" 280 | ] 281 | }, 282 | "locked": { 283 | "lastModified": 1734384417, 284 | "narHash": "sha256-noYeXcNQ15g1/gIJIYT2zdO66wzY5Z06PYz6BfKUZA8=", 285 | "owner": "hyprwm", 286 | "repo": "hyprwayland-scanner", 287 | "rev": "90e87f7fcfcce4862826d60332cbc5e2f87e1f88", 288 | "type": "github" 289 | }, 290 | "original": { 291 | "owner": "hyprwm", 292 | "repo": "hyprwayland-scanner", 293 | "type": "github" 294 | } 295 | }, 296 | "nix-filter": { 297 | "locked": { 298 | "lastModified": 1731533336, 299 | "narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=", 300 | "owner": "numtide", 301 | "repo": "nix-filter", 302 | "rev": "f7653272fd234696ae94229839a99b73c9ab7de0", 303 | "type": "github" 304 | }, 305 | "original": { 306 | "owner": "numtide", 307 | "repo": "nix-filter", 308 | "type": "github" 309 | } 310 | }, 311 | "nixpkgs": { 312 | "locked": { 313 | "lastModified": 1734119587, 314 | "narHash": "sha256-AKU6qqskl0yf2+JdRdD0cfxX4b9x3KKV5RqA6wijmPM=", 315 | "owner": "NixOS", 316 | "repo": "nixpkgs", 317 | "rev": "3566ab7246670a43abd2ffa913cc62dad9cdf7d5", 318 | "type": "github" 319 | }, 320 | "original": { 321 | "owner": "NixOS", 322 | "ref": "nixos-unstable", 323 | "repo": "nixpkgs", 324 | "type": "github" 325 | } 326 | }, 327 | "nixpkgs-stable": { 328 | "locked": { 329 | "lastModified": 1730741070, 330 | "narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=", 331 | "owner": "NixOS", 332 | "repo": "nixpkgs", 333 | "rev": "d063c1dd113c91ab27959ba540c0d9753409edf3", 334 | "type": "github" 335 | }, 336 | "original": { 337 | "owner": "NixOS", 338 | "ref": "nixos-24.05", 339 | "repo": "nixpkgs", 340 | "type": "github" 341 | } 342 | }, 343 | "pre-commit-hooks": { 344 | "inputs": { 345 | "flake-compat": "flake-compat", 346 | "gitignore": "gitignore", 347 | "nixpkgs": [ 348 | "hyprland", 349 | "nixpkgs" 350 | ], 351 | "nixpkgs-stable": "nixpkgs-stable" 352 | }, 353 | "locked": { 354 | "lastModified": 1734379367, 355 | "narHash": "sha256-Keu8z5VgT5gnCF4pmB+g7XZFftHpfl4qOn7nqBcywdE=", 356 | "owner": "cachix", 357 | "repo": "git-hooks.nix", 358 | "rev": "0bb4be58f21ff38fc3cdbd6c778eb67db97f0b99", 359 | "type": "github" 360 | }, 361 | "original": { 362 | "owner": "cachix", 363 | "repo": "git-hooks.nix", 364 | "type": "github" 365 | } 366 | }, 367 | "root": { 368 | "inputs": { 369 | "hyprland": "hyprland", 370 | "nix-filter": "nix-filter" 371 | } 372 | }, 373 | "systems": { 374 | "locked": { 375 | "lastModified": 1689347949, 376 | "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", 377 | "owner": "nix-systems", 378 | "repo": "default-linux", 379 | "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", 380 | "type": "github" 381 | }, 382 | "original": { 383 | "owner": "nix-systems", 384 | "repo": "default-linux", 385 | "type": "github" 386 | } 387 | }, 388 | "xdph": { 389 | "inputs": { 390 | "hyprland-protocols": [ 391 | "hyprland", 392 | "hyprland-protocols" 393 | ], 394 | "hyprlang": [ 395 | "hyprland", 396 | "hyprlang" 397 | ], 398 | "hyprutils": [ 399 | "hyprland", 400 | "hyprutils" 401 | ], 402 | "hyprwayland-scanner": [ 403 | "hyprland", 404 | "hyprwayland-scanner" 405 | ], 406 | "nixpkgs": [ 407 | "hyprland", 408 | "nixpkgs" 409 | ], 410 | "systems": [ 411 | "hyprland", 412 | "systems" 413 | ] 414 | }, 415 | "locked": { 416 | "lastModified": 1734422917, 417 | "narHash": "sha256-0y7DRaXslhfqVKV8a/talYTYAe2NHOQhMZG7KMNRCtc=", 418 | "owner": "hyprwm", 419 | "repo": "xdg-desktop-portal-hyprland", 420 | "rev": "3e884d941ca819c1f2e50df8bdae0debded1ed87", 421 | "type": "github" 422 | }, 423 | "original": { 424 | "owner": "hyprwm", 425 | "repo": "xdg-desktop-portal-hyprland", 426 | "type": "github" 427 | } 428 | } 429 | }, 430 | "root": "root", 431 | "version": 7 432 | } 433 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | hyprland.url = "git+https://github.com/hyprwm/Hyprland?submodules=1"; 4 | 5 | nix-filter.url = "github:numtide/nix-filter"; 6 | }; 7 | 8 | outputs = { 9 | self, 10 | hyprland, 11 | nix-filter, 12 | ... 13 | }: let 14 | inherit (hyprland.inputs) nixpkgs; 15 | forHyprlandSystems = fn: nixpkgs.lib.genAttrs (builtins.attrNames hyprland.packages) (system: fn system nixpkgs.legacyPackages.${system}); 16 | in { 17 | packages = forHyprlandSystems (system: pkgs: let 18 | hyprlandPackage = hyprland.packages.${system}.hyprland; 19 | in rec { 20 | Hypr-DarkWindow = pkgs.gcc14Stdenv.mkDerivation { 21 | pname = "Hypr-DarkWindow"; 22 | version = "2.0.0"; 23 | src = nix-filter.lib { 24 | root = ./.; 25 | include = [ 26 | "src" 27 | ./Makefile 28 | ]; 29 | }; 30 | 31 | nativeBuildInputs = with pkgs; [ pkg-config ]; 32 | buildInputs = [ hyprlandPackage.dev ] ++ hyprlandPackage.buildInputs; 33 | 34 | installPhase = '' 35 | mkdir -p $out/lib 36 | install ./out/hyprchroma.so $out/lib/libHypr-DarkWindow.so 37 | ''; 38 | 39 | meta = with pkgs.lib; { 40 | homepage = "https://github.com/micha4w/Hypr-DarkWindow"; 41 | description = "Invert the colors of specific Windows"; 42 | license = licenses.mit; 43 | platforms = platforms.linux; 44 | }; 45 | }; 46 | 47 | default = Hypr-DarkWindow; 48 | }); 49 | 50 | devShells = forHyprlandSystems (system: pkgs: { 51 | default = pkgs.mkShell { 52 | name = "Hypr-DarkWindow"; 53 | 54 | nativeBuildInputs = with pkgs; [ 55 | clang-tools_16 56 | ]; 57 | 58 | inputsFrom = [ self.packages.${system}.Hypr-DarkWindow ]; 59 | }; 60 | }); 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /hyprpm.toml: -------------------------------------------------------------------------------- 1 | [repository] 2 | name = "hyprchroma" 3 | authors = ["alexhulbert", "micha4w"] 4 | commit_pins = [ 5 | # v Hyprland v hyprchroma 6 | ["fe7b748eb668136dd0558b7c8279bfcd7ab4d759", "2629145e163f16b525bd5a3f310837e146c38e9f"], # v0.39.1 7 | ["cba1ade848feac44b2eda677503900639581c3f4", "37e73aaa3c2ea2d91aa4a42f50df7f64c182a36a"], # v0.40.0 8 | ["9e781040d9067c2711ec2e9f5b47b76ef70762b3", "8d398db4e51198a9a023e35aa55dd49c580bd726"], # v0.41.1 9 | ["0f594732b063a90d44df8c5d402d658f27471dfe", "1ba6be1e61de53c5797e230c7f87010cdda578de"], # v0.43.0 10 | ["1e424d94f87b38b272c6c765cfa09044dd6b8a40", "6158ac01f2da2eef98c25b069801a52c01dc07bd"], # v0.44.0 11 | ["4520b30d498daca8079365bdb909a8dea38e8d55", "6158ac01f2da2eef98c25b069801a52c01dc07bd"], # v0.44.1 12 | ["a425fbebe4cf4238e48a42f724ef2208959d66cf", "c2d4eb0e5f44e28128b59ae6d932c42f90a85a19"], # v0.45.0 13 | ["500d2a3580388afc8b620b0a3624147faa34f98b", "c2d4eb0e5f44e28128b59ae6d932c42f90a85a19"], # v0.45.1 14 | ["12f9a0d0b93f691d4d9923716557154d74777b0a", "c2d4eb0e5f44e28128b59ae6d932c42f90a85a19"], # v0.45.2 15 | ["788ae588979c2a1ff8a660f16e3c502ef5796755", "6ad6389aea7cb75bad5a1ffe844afc5a85dc5c04"], # v0.46.0 16 | ["254fc2bc6000075f660b4b8ed818a6af544d1d64", "6ad6389aea7cb75bad5a1ffe844afc5a85dc5c04"], # v0.46.1 17 | ["0bd541f2fd902dbfa04c3ea2ccf679395e316887", "6ad6389aea7cb75bad5a1ffe844afc5a85dc5c04"], # v0.46.2 18 | ] 19 | [hyprchroma] 20 | description = "Apply Chromakey algorithm" 21 | authors = ["alexhulbert", "micha4w"] 22 | output = "out/hyprchroma.so" 23 | build = [ 24 | "make all", 25 | ] 26 | -------------------------------------------------------------------------------- /res/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexhulbert/Hyprchroma/0a705582590e6d8cbc75dde005ceeb546ecc36d7/res/preview.png -------------------------------------------------------------------------------- /src/DecorationsWrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "WindowInverter.h" 4 | 5 | class DecorationsWrapper : public IHyprWindowDecoration { 6 | public: 7 | DecorationsWrapper(WindowInverter& inverter, std::unique_ptr&& to_wrap, const PHLWINDOW& window) 8 | : IHyprWindowDecoration(window), 9 | m_Inverter(inverter), 10 | m_Wrapped(std::move(to_wrap)) 11 | { } 12 | 13 | std::unique_ptr take() { 14 | return std::move(m_Wrapped); 15 | } 16 | IHyprWindowDecoration* get() { 17 | return m_Wrapped.get(); 18 | } 19 | 20 | virtual void draw(PHLMONITOR m, float const& a) { 21 | m_Inverter.SoftToggle(false); 22 | m_Wrapped->draw(m, a); 23 | m_Inverter.SoftToggle(true); 24 | } 25 | 26 | virtual SDecorationPositioningInfo getPositioningInfo() { return m_Wrapped->getPositioningInfo(); } 27 | virtual void onPositioningReply(const SDecorationPositioningReply& reply) { m_Wrapped->onPositioningReply(reply); } 28 | virtual eDecorationType getDecorationType() { return m_Wrapped->getDecorationType(); } 29 | virtual void updateWindow(PHLWINDOW w) { m_Wrapped->updateWindow(w); } 30 | virtual void damageEntire() { m_Wrapped->damageEntire(); } 31 | virtual bool onInputOnDeco(const eInputType i, const Vector2D& v, std::any a = {}) { return m_Wrapped->onInputOnDeco(i, v, a); } 32 | virtual eDecorationLayer getDecorationLayer() { return m_Wrapped->getDecorationLayer(); } 33 | virtual uint64_t getDecorationFlags() { return m_Wrapped->getDecorationFlags(); } 34 | virtual std::string getDisplayName() { return m_Wrapped->getDisplayName(); } 35 | private: 36 | std::unique_ptr m_Wrapped; 37 | WindowInverter& m_Inverter; 38 | }; 39 | -------------------------------------------------------------------------------- /src/Helpers.cpp: -------------------------------------------------------------------------------- 1 | #include "Helpers.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | 10 | void ShaderHolder::Init() 11 | { 12 | g_pHyprRenderer->makeEGLCurrent(); 13 | 14 | GLuint prog = CreateProgram(TEXVERTSRC, TEXFRAGSRCRGBA_DARK); 15 | RGBA.program = prog; 16 | RGBA.proj = glGetUniformLocation(prog, "proj"); 17 | RGBA.tex = glGetUniformLocation(prog, "tex"); 18 | RGBA.alpha = glGetUniformLocation(prog, "alpha"); 19 | RGBA.texAttrib = glGetAttribLocation(prog, "texcoord"); 20 | RGBA.posAttrib = glGetAttribLocation(prog, "pos"); 21 | RGBA.discardOpaque = glGetUniformLocation(prog, "discardOpaque"); 22 | RGBA.discardAlpha = glGetUniformLocation(prog, "discardAlpha"); 23 | RGBA.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue"); 24 | RGBA.topLeft = glGetUniformLocation(prog, "topLeft"); 25 | RGBA.fullSize = glGetUniformLocation(prog, "fullSize"); 26 | RGBA.radius = glGetUniformLocation(prog, "radius"); 27 | RGBA.applyTint = glGetUniformLocation(prog, "applyTint"); 28 | RGBA.tint = glGetUniformLocation(prog, "tint"); 29 | RGBA_Invert = glGetUniformLocation(prog, "doInvert"); 30 | BKGA = glGetUniformLocation(prog, "bkg"); 31 | 32 | prog = CreateProgram(TEXVERTSRC, TEXFRAGSRCRGBX_DARK); 33 | RGBX.program = prog; 34 | RGBX.tex = glGetUniformLocation(prog, "tex"); 35 | RGBX.proj = glGetUniformLocation(prog, "proj"); 36 | RGBX.alpha = glGetUniformLocation(prog, "alpha"); 37 | RGBX.texAttrib = glGetAttribLocation(prog, "texcoord"); 38 | RGBX.posAttrib = glGetAttribLocation(prog, "pos"); 39 | RGBX.discardOpaque = glGetUniformLocation(prog, "discardOpaque"); 40 | RGBX.discardAlpha = glGetUniformLocation(prog, "discardAlpha"); 41 | RGBX.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue"); 42 | RGBX.topLeft = glGetUniformLocation(prog, "topLeft"); 43 | RGBX.fullSize = glGetUniformLocation(prog, "fullSize"); 44 | RGBX.radius = glGetUniformLocation(prog, "radius"); 45 | RGBX.applyTint = glGetUniformLocation(prog, "applyTint"); 46 | RGBX.tint = glGetUniformLocation(prog, "tint"); 47 | RGBX_Invert = glGetUniformLocation(prog, "doInvert"); 48 | BKGX = glGetUniformLocation(prog, "bkg"); 49 | 50 | prog = CreateProgram(TEXVERTSRC, TEXFRAGSRCEXT_DARK); 51 | EXT.program = prog; 52 | EXT.tex = glGetUniformLocation(prog, "tex"); 53 | EXT.proj = glGetUniformLocation(prog, "proj"); 54 | EXT.alpha = glGetUniformLocation(prog, "alpha"); 55 | EXT.posAttrib = glGetAttribLocation(prog, "pos"); 56 | EXT.texAttrib = glGetAttribLocation(prog, "texcoord"); 57 | EXT.discardOpaque = glGetUniformLocation(prog, "discardOpaque"); 58 | EXT.discardAlpha = glGetUniformLocation(prog, "discardAlpha"); 59 | EXT.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue"); 60 | EXT.topLeft = glGetUniformLocation(prog, "topLeft"); 61 | EXT.fullSize = glGetUniformLocation(prog, "fullSize"); 62 | EXT.radius = glGetUniformLocation(prog, "radius"); 63 | EXT.applyTint = glGetUniformLocation(prog, "applyTint"); 64 | EXT.tint = glGetUniformLocation(prog, "tint"); 65 | EXT_Invert = glGetUniformLocation(prog, "doInvert"); 66 | BKGE = glGetUniformLocation(prog, "bkg"); 67 | 68 | g_pHyprRenderer->unsetEGL(); 69 | } 70 | 71 | void ShaderHolder::Destroy() 72 | { 73 | g_pHyprRenderer->makeEGLCurrent(); 74 | 75 | RGBA.destroy(); 76 | RGBX.destroy(); 77 | EXT.destroy(); 78 | 79 | g_pHyprRenderer->unsetEGL(); 80 | } 81 | 82 | GLuint ShaderHolder::CompileShader(const GLuint& type, std::string src) 83 | { 84 | auto shader = glCreateShader(type); 85 | 86 | auto shaderSource = src.c_str(); 87 | 88 | glShaderSource(shader, 1, (const GLchar**)&shaderSource, nullptr); 89 | glCompileShader(shader); 90 | 91 | GLint ok; 92 | glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); 93 | if (!ok) { 94 | char infoLog[512]; 95 | glGetShaderInfoLog(shader, 512, NULL, infoLog); 96 | Debug::log(ERR, "Error compiling shader: {}", infoLog); 97 | throw std::runtime_error(std::string("Error compiling shader: ") + infoLog); 98 | } 99 | 100 | return shader; 101 | } 102 | 103 | GLuint ShaderHolder::CreateProgram(const std::string& vert, const std::string& frag) 104 | { 105 | auto vertCompiled = CompileShader(GL_VERTEX_SHADER, vert); 106 | auto fragCompiled = CompileShader(GL_FRAGMENT_SHADER, frag); 107 | 108 | auto prog = glCreateProgram(); 109 | glAttachShader(prog, vertCompiled); 110 | glAttachShader(prog, fragCompiled); 111 | glLinkProgram(prog); 112 | 113 | glDetachShader(prog, vertCompiled); 114 | glDetachShader(prog, fragCompiled); 115 | glDeleteShader(vertCompiled); 116 | glDeleteShader(fragCompiled); 117 | 118 | GLint ok; 119 | glGetProgramiv(prog, GL_LINK_STATUS, &ok); 120 | if (!ok) { 121 | char infoLog[512]; 122 | glGetShaderInfoLog(prog, 512, NULL, infoLog); 123 | Debug::log(ERR, "Error linking shader: %s", infoLog); 124 | throw std::runtime_error(std::string("Error linking shader: ") + infoLog); 125 | } 126 | 127 | return prog; 128 | } 129 | -------------------------------------------------------------------------------- /src/Helpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "TexturesDark.h" 9 | 10 | 11 | namespace std 12 | { 13 | inline void swap(CShader& a, CShader& b) 14 | { 15 | // memcpy because speed! 16 | // (Would break unordered map, but those aren't in use anyway..) 17 | uint8_t c[sizeof(CShader)]; 18 | std::memcpy(&c, &a, sizeof(CShader)); 19 | std::memcpy(&a, &b, sizeof(CShader)); 20 | std::memcpy(&b, &c, sizeof(CShader)); 21 | } 22 | } 23 | 24 | struct ShaderHolder 25 | { 26 | CShader RGBA; 27 | GLint RGBA_Invert; 28 | CShader RGBX; 29 | GLint RGBX_Invert; 30 | CShader EXT; 31 | GLint EXT_Invert; 32 | 33 | // Holds the background color 34 | GLint BKGA; 35 | GLint BKGX; 36 | GLint BKGE; 37 | 38 | void Init(); 39 | void Destroy(); 40 | 41 | private: 42 | GLuint CompileShader(const GLuint& type, std::string src); 43 | GLuint CreateProgram(const std::string& vert, const std::string& frag); 44 | }; 45 | -------------------------------------------------------------------------------- /src/TexturesDark.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | 9 | inline static const std::string DARK_MODE_FUNC = R"glsl( 10 | uniform bool doInvert; 11 | uniform vec3 bkg; 12 | 13 | void invert(inout vec4 color) { 14 | if (doInvert) { 15 | // Original shader by ikz87 16 | 17 | // Apply opacity changes to pixels similar to one color 18 | // vec3 color_rgb = vec3(0,0,255); // Color to replace, in rgb format 19 | float similarity = 0.1; // How many similar colors should be affected. 20 | 21 | float amount = 1.4; // How much similar colors should be changed. 22 | float target_opacity = 0.83; 23 | // Change any of the above values to get the result you want 24 | 25 | // Set values to a 0 - 1 range 26 | vec3 chroma = vec3(bkg[0]/255.0, bkg[1]/255.0, bkg[2]/255.0); 27 | 28 | if (color.x >=chroma.x - similarity && color.x <=chroma.x + similarity && 29 | color.y >=chroma.y - similarity && color.y <=chroma.y + similarity && 30 | color.z >=chroma.z - similarity && color.z <=chroma.z + similarity && 31 | color.w >= 0.99) 32 | { 33 | // Calculate error between matched pixel and color_rgb values 34 | vec3 error = vec3(abs(chroma.x - color.x), abs(chroma.y - color.y), abs(chroma.z - color.z)); 35 | float avg_error = (error.x + error.y + error.z) / 3.0; 36 | color.w = target_opacity + (1.0 - target_opacity)*avg_error*amount/similarity; 37 | 38 | // color.rgba = vec4(0, 0, 1, 0.5); 39 | } 40 | } 41 | } 42 | )glsl"; 43 | 44 | 45 | inline const std::string TEXFRAGSRCRGBA_DARK = R"glsl( 46 | precision mediump float; 47 | varying vec2 v_texcoord; // is in 0-1 48 | uniform sampler2D tex; 49 | uniform float alpha; 50 | 51 | uniform vec2 topLeft; 52 | uniform vec2 fullSize; 53 | uniform float radius; 54 | 55 | uniform int discardOpaque; 56 | uniform int discardAlpha; 57 | uniform float discardAlphaValue; 58 | 59 | uniform int applyTint; 60 | uniform vec3 tint; 61 | 62 | )glsl" + DARK_MODE_FUNC + R"glsl( 63 | 64 | void main() { 65 | 66 | vec4 pixColor = texture2D(tex, v_texcoord); 67 | 68 | if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) 69 | discard; 70 | 71 | if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue) 72 | discard; 73 | 74 | if (applyTint == 2) { 75 | pixColor[0] = pixColor[0] * tint[0]; 76 | pixColor[1] = pixColor[1] * tint[1]; 77 | pixColor[2] = pixColor[2] * tint[2]; 78 | } 79 | 80 | invert(pixColor); 81 | 82 | if (radius > 0.0) { 83 | )glsl" + 84 | ROUNDED_SHADER_FUNC("pixColor") + R"glsl( 85 | } 86 | 87 | gl_FragColor = pixColor * alpha; 88 | })glsl"; 89 | 90 | inline const std::string TEXFRAGSRCRGBX_DARK = R"glsl( 91 | precision mediump float; 92 | varying vec2 v_texcoord; 93 | uniform sampler2D tex; 94 | uniform float alpha; 95 | 96 | uniform vec2 topLeft; 97 | uniform vec2 fullSize; 98 | uniform float radius; 99 | 100 | uniform int discardOpaque; 101 | uniform int discardAlpha; 102 | uniform int discardAlphaValue; 103 | 104 | uniform int applyTint; 105 | uniform vec3 tint; 106 | 107 | )glsl" + DARK_MODE_FUNC + R"glsl( 108 | 109 | void main() { 110 | 111 | if (discardOpaque == 1 && alpha == 1.0) 112 | discard; 113 | 114 | vec4 pixColor = vec4(texture2D(tex, v_texcoord).rgb, 1.0); 115 | 116 | if (applyTint == 2) { 117 | pixColor[0] = pixColor[0] * tint[0]; 118 | pixColor[1] = pixColor[1] * tint[1]; 119 | pixColor[2] = pixColor[2] * tint[2]; 120 | } 121 | 122 | invert(pixColor); 123 | 124 | if (radius > 0.0) { 125 | )glsl" + 126 | ROUNDED_SHADER_FUNC("pixColor") + R"glsl( 127 | } 128 | 129 | gl_FragColor = pixColor * alpha; 130 | })glsl"; 131 | 132 | inline const std::string TEXFRAGSRCEXT_DARK = R"glsl( 133 | #extension GL_OES_EGL_image_external : require 134 | 135 | precision mediump float; 136 | varying vec2 v_texcoord; 137 | uniform samplerExternalOES texture0; 138 | uniform float alpha; 139 | 140 | uniform vec2 topLeft; 141 | uniform vec2 fullSize; 142 | uniform float radius; 143 | 144 | uniform int discardOpaque; 145 | uniform int discardAlpha; 146 | uniform int discardAlphaValue; 147 | 148 | uniform int applyTint; 149 | uniform vec3 tint; 150 | 151 | )glsl" + DARK_MODE_FUNC + R"glsl( 152 | 153 | void main() { 154 | 155 | vec4 pixColor = texture2D(texture0, v_texcoord); 156 | 157 | if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) 158 | discard; 159 | 160 | if (applyTint == 2) { 161 | pixColor[0] = pixColor[0] * tint[0]; 162 | pixColor[1] = pixColor[1] * tint[1]; 163 | pixColor[2] = pixColor[2] * tint[2]; 164 | } 165 | 166 | invert(pixColor); 167 | 168 | if (radius > 0.0) { 169 | )glsl" + 170 | ROUNDED_SHADER_FUNC("pixColor") + R"glsl( 171 | } 172 | 173 | gl_FragColor = pixColor * alpha; 174 | } 175 | )glsl"; 176 | -------------------------------------------------------------------------------- /src/WindowInverter.cpp: -------------------------------------------------------------------------------- 1 | #include "WindowInverter.h" 2 | #include 3 | 4 | #include "DecorationsWrapper.h" 5 | 6 | void WindowInverter::SetBackground(GLfloat r, GLfloat g, GLfloat b) 7 | { 8 | bkgR = r; 9 | bkgG = g; 10 | bkgB = b; 11 | } 12 | 13 | void WindowInverter::OnRenderWindowPre() 14 | { 15 | auto window = g_pHyprOpenGL->m_pCurrentWindow.lock(); 16 | bool shouldInvert = 17 | (std::find(m_InvertedWindows.begin(), m_InvertedWindows.end(), window) 18 | != m_InvertedWindows.end()) ^ 19 | (std::find(m_ManuallyInvertedWindows.begin(), m_ManuallyInvertedWindows.end(), window) 20 | != m_ManuallyInvertedWindows.end()); 21 | 22 | if (shouldInvert) 23 | { 24 | glUseProgram(m_Shaders.RGBA.program); 25 | glUniform3f(m_Shaders.BKGA, bkgR, bkgG, bkgB); 26 | glUseProgram(m_Shaders.RGBX.program); 27 | glUniform3f(m_Shaders.BKGX, bkgR, bkgG, bkgB); 28 | glUseProgram(m_Shaders.EXT.program); 29 | glUniform3f(m_Shaders.BKGE, bkgR, bkgG, bkgB); 30 | std::swap(m_Shaders.EXT, g_pHyprOpenGL->m_RenderData.pCurrentMonData->m_shEXT); 31 | std::swap(m_Shaders.RGBA, g_pHyprOpenGL->m_RenderData.pCurrentMonData->m_shRGBA); 32 | std::swap(m_Shaders.RGBX, g_pHyprOpenGL->m_RenderData.pCurrentMonData->m_shRGBX); 33 | m_ShadersSwapped = true; 34 | 35 | SoftToggle(true); 36 | } 37 | } 38 | 39 | void WindowInverter::OnRenderWindowPost() 40 | { 41 | if (m_ShadersSwapped) 42 | { 43 | if (m_DecorationsWrapped) 44 | { 45 | for (auto& decoration : g_pHyprOpenGL->m_pCurrentWindow.lock()->m_dWindowDecorations) 46 | { 47 | // Debug::log(LOG, "REMOVE: Window {:p}, Decoration {:p}", (void*)g_pHyprOpenGL->m_pCurrentWindow.get(), (void*)decoration.get()); 48 | if (DecorationsWrapper* wrapper = dynamic_cast(decoration.get())) 49 | decoration.reset(wrapper->take().release()); 50 | } 51 | m_DecorationsWrapped = false; 52 | } 53 | 54 | std::swap(m_Shaders.EXT, g_pHyprOpenGL->m_RenderData.pCurrentMonData->m_shEXT); 55 | std::swap(m_Shaders.RGBA, g_pHyprOpenGL->m_RenderData.pCurrentMonData->m_shRGBA); 56 | std::swap(m_Shaders.RGBX, g_pHyprOpenGL->m_RenderData.pCurrentMonData->m_shRGBX); 57 | m_ShadersSwapped = false; 58 | } 59 | } 60 | 61 | void WindowInverter::OnWindowClose(PHLWINDOW window) 62 | { 63 | static const auto remove = [](std::vector& windows, PHLWINDOW& window) { 64 | auto windowIt = std::find(windows.begin(), windows.end(), window); 65 | if (windowIt != windows.end()) 66 | { 67 | std::swap(*windowIt, *(windows.end() - 1)); 68 | windows.pop_back(); 69 | } 70 | }; 71 | 72 | remove(m_InvertedWindows, window); 73 | remove(m_ManuallyInvertedWindows, window); 74 | } 75 | 76 | void WindowInverter::Init(HANDLE pluginHandle) 77 | { 78 | m_Shaders.Init(); 79 | 80 | m_PluginHandle = pluginHandle; 81 | } 82 | 83 | void WindowInverter::Unload() 84 | { 85 | if (m_ShadersSwapped) 86 | { 87 | std::swap(m_Shaders.EXT, g_pHyprOpenGL->m_RenderData.pCurrentMonData->m_shEXT); 88 | std::swap(m_Shaders.RGBA, g_pHyprOpenGL->m_RenderData.pCurrentMonData->m_shRGBA); 89 | std::swap(m_Shaders.RGBX, g_pHyprOpenGL->m_RenderData.pCurrentMonData->m_shRGBX); 90 | m_ShadersSwapped = false; 91 | } 92 | 93 | m_Shaders.Destroy(); 94 | } 95 | 96 | void WindowInverter::InvertIfMatches(PHLWINDOW window) 97 | { 98 | // for some reason, some events (currently `activeWindow`) sometimes pass a null pointer 99 | if (!window) return; 100 | 101 | std::vector> rules = g_pConfigManager->getMatchingRules(window); 102 | bool shouldInvert = std::any_of(rules.begin(), rules.end(), [](const SP& rule) { 103 | return rule->szRule == "plugin:chromakey"; 104 | }); 105 | 106 | auto windowIt = std::find(m_InvertedWindows.begin(), m_InvertedWindows.end(), window); 107 | if (shouldInvert != (windowIt != m_InvertedWindows.end())) 108 | { 109 | if (shouldInvert) 110 | m_InvertedWindows.push_back(window); 111 | else 112 | { 113 | std::swap(*windowIt, *(m_InvertedWindows.end() - 1)); 114 | m_InvertedWindows.pop_back(); 115 | } 116 | 117 | g_pHyprRenderer->damageWindow(window); 118 | } 119 | } 120 | 121 | 122 | void WindowInverter::ToggleInvert(PHLWINDOW window) 123 | { 124 | if (!window) 125 | return; 126 | 127 | auto windowIt = std::find(m_ManuallyInvertedWindows.begin(), m_ManuallyInvertedWindows.end(), window); 128 | if (windowIt == m_ManuallyInvertedWindows.end()) 129 | m_ManuallyInvertedWindows.push_back(window); 130 | else 131 | { 132 | std::swap(*windowIt, *(m_ManuallyInvertedWindows.end() - 1)); 133 | m_ManuallyInvertedWindows.pop_back(); 134 | } 135 | 136 | g_pHyprRenderer->damageWindow(window); 137 | } 138 | 139 | void WindowInverter::SoftToggle(bool invert) 140 | { 141 | if (m_ShadersSwapped) 142 | { 143 | const auto toggleInvert = [&](GLuint prog, GLint location) { 144 | glUseProgram(prog); 145 | glUniform1i(location, invert ? 1 : 0); 146 | }; 147 | 148 | toggleInvert(g_pHyprOpenGL->m_RenderData.pCurrentMonData->m_shEXT.program, m_Shaders.EXT_Invert); 149 | toggleInvert(g_pHyprOpenGL->m_RenderData.pCurrentMonData->m_shRGBA.program, m_Shaders.RGBA_Invert); 150 | toggleInvert(g_pHyprOpenGL->m_RenderData.pCurrentMonData->m_shRGBX.program, m_Shaders.RGBX_Invert); 151 | } 152 | } 153 | 154 | 155 | void WindowInverter::Reload() 156 | { 157 | m_InvertedWindows = {}; 158 | 159 | for (const auto& window : g_pCompositor->m_vWindows) 160 | InvertIfMatches(window); 161 | 162 | if (m_IgnoreDecorations) { 163 | Hyprlang::CConfigValue* config = HyprlandAPI::getConfigValue(m_PluginHandle, "plugin:darkwindow:ignore_decorations"); 164 | if (config && config->dataPtr()) { 165 | m_IgnoreDecorations = *((Hyprlang::INT*) config->dataPtr()) != 0; 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/WindowInverter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "Helpers.h" 7 | 8 | 9 | class WindowInverter 10 | { 11 | public: 12 | void Init(HANDLE pluginHandle); 13 | void Unload(); 14 | 15 | void SetBackground(GLfloat r, GLfloat g, GLfloat b); 16 | 17 | void InvertIfMatches(PHLWINDOW window); 18 | void ToggleInvert(PHLWINDOW window); 19 | void SoftToggle(bool invert); 20 | void Reload(); 21 | 22 | void OnRenderWindowPre(); 23 | void OnRenderWindowPost(); 24 | void OnWindowClose(PHLWINDOW window); 25 | 26 | void NoIgnoreDecorations() 27 | { 28 | m_IgnoreDecorations = {}; 29 | } 30 | 31 | private: 32 | HANDLE m_PluginHandle; 33 | 34 | std::vector m_InvertWindowRules; 35 | std::vector m_InvertedWindows; 36 | std::vector m_ManuallyInvertedWindows; 37 | 38 | std::optional m_IgnoreDecorations = true; 39 | bool m_DecorationsWrapped = false; 40 | 41 | ShaderHolder m_Shaders; 42 | bool m_ShadersSwapped = false; 43 | 44 | GLfloat bkgR = 0.0f; 45 | GLfloat bkgG = 0.0f; 46 | GLfloat bkgB = 0.0f; 47 | }; -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "WindowInverter.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "DecorationsWrapper.h" 9 | 10 | 11 | inline HANDLE PHANDLE = nullptr; 12 | 13 | inline WindowInverter g_WindowInverter; 14 | inline std::mutex g_InverterMutex; 15 | 16 | inline std::vector> g_Callbacks; 17 | CFunctionHook* g_getDataForHook; 18 | 19 | // TODO remove deprecated 20 | static void addDeprecatedEventListeners(); 21 | 22 | 23 | void* hkGetDataFor(void* thisptr, IHyprWindowDecoration* pDecoration, PHLWINDOW pWindow) { 24 | if (DecorationsWrapper* wrapper = dynamic_cast(pDecoration)) 25 | { 26 | // Debug::log(LOG, "IGNORE: Decoration {}", (void*)pDecoration); 27 | pDecoration = wrapper->get(); 28 | } 29 | 30 | return ((decltype(&hkGetDataFor))g_getDataForHook->m_pOriginal)(thisptr, pDecoration, pWindow); 31 | } 32 | 33 | APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) 34 | { 35 | PHANDLE = handle; 36 | 37 | { 38 | std::lock_guard lock(g_InverterMutex); 39 | g_WindowInverter.Init(PHANDLE); 40 | g_pConfigManager->m_bForceReload = true; 41 | } 42 | 43 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:darkwindow:ignore_decorations", Hyprlang::CConfigValue(Hyprlang::INT{ 0 })); 44 | 45 | g_Callbacks = {}; 46 | 47 | addDeprecatedEventListeners(); 48 | 49 | HyprlandAPI::addConfigKeyword( 50 | handle, "chromakey_background", 51 | [](const char* cmd, const char* val) -> Hyprlang::CParseResult { 52 | // Parse val as "r,g,b" into 3 GLfloats 53 | std::vector result; 54 | std::stringstream ss (val); 55 | std::string component; 56 | 57 | getline(ss, component, ','); 58 | GLfloat r = std::stof(component); 59 | getline(ss, component, ','); 60 | GLfloat g = std::stof(component); 61 | getline(ss, component, ','); 62 | GLfloat b = std::stof(component); 63 | 64 | g_WindowInverter.SetBackground(r, g, b); 65 | 66 | return Hyprlang::CParseResult(); // return a default CParseResult 67 | }, 68 | { .allowFlags = false } 69 | ); 70 | 71 | g_Callbacks.push_back(HyprlandAPI::registerCallbackDynamic( 72 | PHANDLE, "render", 73 | [&](void* self, SCallbackInfo&, std::any data) { 74 | std::lock_guard lock(g_InverterMutex); 75 | eRenderStage renderStage = std::any_cast(data); 76 | 77 | if (renderStage == eRenderStage::RENDER_PRE_WINDOW) 78 | g_WindowInverter.OnRenderWindowPre(); 79 | if (renderStage == eRenderStage::RENDER_POST_WINDOW) 80 | g_WindowInverter.OnRenderWindowPost(); 81 | } 82 | )); 83 | 84 | g_Callbacks.push_back(HyprlandAPI::registerCallbackDynamic( 85 | PHANDLE, "configReloaded", 86 | [&](void* self, SCallbackInfo&, std::any data) { 87 | std::lock_guard lock(g_InverterMutex); 88 | g_WindowInverter.Reload(); 89 | } 90 | )); 91 | g_Callbacks.push_back(HyprlandAPI::registerCallbackDynamic( 92 | PHANDLE, "closeWindow", 93 | [&](void* self, SCallbackInfo&, std::any data) { 94 | std::lock_guard lock(g_InverterMutex); 95 | g_WindowInverter.OnWindowClose(std::any_cast(data)); 96 | } 97 | )); 98 | g_Callbacks.push_back(HyprlandAPI::registerCallbackDynamic( 99 | PHANDLE, "windowUpdateRules", 100 | [&](void* self, SCallbackInfo&, std::any data) { 101 | std::lock_guard lock(g_InverterMutex); 102 | g_WindowInverter.InvertIfMatches(std::any_cast(data)); 103 | } 104 | )); 105 | 106 | static const auto METHOD = ([&] { 107 | auto all = HyprlandAPI::findFunctionsByName(PHANDLE, "getDataFor"); 108 | auto found = std::find_if(all.begin(), all.end(), [](const SFunctionMatch& line) { 109 | return line.demangled.starts_with("CDecorationPositioner::getDataFor"); 110 | }); 111 | if (found != all.end()) 112 | return std::optional(*found); 113 | else 114 | return std::optional(); 115 | })(); 116 | if (METHOD) 117 | { 118 | g_getDataForHook = HyprlandAPI::createFunctionHook(handle, METHOD->address, (void*)&hkGetDataFor); 119 | g_getDataForHook->hook(); 120 | } 121 | else 122 | { 123 | Debug::log(WARN, "[DarkWindow] Failed to hook CDecorationPositioner::getDataFor, cannot ignore Decorations"); 124 | g_WindowInverter.NoIgnoreDecorations(); 125 | } 126 | 127 | HyprlandAPI::addDispatcher(PHANDLE, "togglewindowchromakey", [&](std::string args) { 128 | std::lock_guard lock(g_InverterMutex); 129 | g_WindowInverter.ToggleInvert(g_pCompositor->getWindowByRegex(args)); 130 | }); 131 | HyprlandAPI::addDispatcher(PHANDLE, "togglechromakey", [&](std::string args) { 132 | std::lock_guard lock(g_InverterMutex); 133 | g_WindowInverter.ToggleInvert(g_pCompositor->m_pLastWindow.lock()); 134 | }); 135 | 136 | return { 137 | "hyprchroma", 138 | "Applies ChromaKey algorithm to windows for transparency effect", 139 | "alexhulbert", 140 | "1.0.0" 141 | }; 142 | } 143 | 144 | // TODO remove deprecated 145 | inline static bool g_DidNotify = false; 146 | Hyprlang::CParseResult onInvertKeyword(const char* COMMAND, const char* VALUE) 147 | { 148 | if (!g_DidNotify) { 149 | g_DidNotify = true; 150 | HyprlandAPI::addNotification( 151 | PHANDLE, 152 | "[Hypr-DarkWindow] The darkwindow_invert keyword was removed in favor of windowrulev2s please check the GitHub for more info.", 153 | { 0xFF'00'00'00 }, 154 | 10000 155 | ); 156 | } 157 | return {}; 158 | } 159 | 160 | // TODO remove deprecated 161 | static void addDeprecatedEventListeners() 162 | { 163 | HyprlandAPI::addConfigKeyword( 164 | PHANDLE, "chromakey_enable", 165 | onInvertKeyword, 166 | { .allowFlags = false } 167 | ); 168 | } 169 | 170 | APICALL EXPORT void PLUGIN_EXIT() 171 | { 172 | std::lock_guard lock(g_InverterMutex); 173 | g_Callbacks = {}; 174 | g_WindowInverter.Unload(); 175 | } 176 | 177 | APICALL EXPORT std::string PLUGIN_API_VERSION() 178 | { 179 | return HYPRLAND_API_VERSION; 180 | } 181 | 182 | --------------------------------------------------------------------------------