├── .github └── workflows │ └── notify.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── flake.lock ├── flake.nix ├── hyprpm.toml ├── res └── preview.png └── src ├── Helpers.cpp ├── Helpers.h ├── OpenGL.cpp.h ├── WindowInverter.cpp ├── WindowInverter.h └── main.cpp /.github/workflows/notify.yml: -------------------------------------------------------------------------------- 1 | name: Hyprland Breaking Change Notifier 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | schedule: 11 | - cron: "0 3 * * *" 12 | workflow_dispatch: {} 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | container: 18 | image: duckonaut/hyprland-arch:latest 19 | steps: 20 | - name: Install dependencies 21 | run: | 22 | sudo -u user sh -c "paru -Syu --noconfirm aquamarine-git hyprland-git hyprutils-git hyprwayland-scanner-git" 23 | - name: Checkout current repository 24 | uses: actions/checkout@v4 25 | 26 | - name: Build current repository 27 | run: make all 28 | -------------------------------------------------------------------------------- /.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 | CXX = g++ 2 | CXXFLAGS = -fPIC --no-gnu-unique -Isrc/ -g `pkg-config --cflags pixman-1 libdrm hyprland hyprlang` -std=c++2b -DWLR_USE_UNSTABLE 3 | 4 | 5 | SRC_DIR = src 6 | OBJ_DIR = out/obj 7 | OUT_DIR = out 8 | 9 | SRCS = $(wildcard $(SRC_DIR)/*.cpp) 10 | OBJS = $(patsubst $(SRC_DIR)/%.cpp, $(OBJ_DIR)/%.o, $(SRCS)) 11 | DEPS = $(patsubst $(SRC_DIR)/%.cpp, $(OBJ_DIR)/%.d, $(SRCS)) 12 | 13 | TARGET = $(OUT_DIR)/hypr-darkwindow.so 14 | 15 | all: $(TARGET) 16 | 17 | $(TARGET): $(OBJS) 18 | $(CXX) -shared $^ -o $@ 19 | 20 | $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp $(OBJ_DIR)/%.d | $(OBJ_DIR) 21 | $(CXX) $(CXXFLAGS) -MT $@ -MMD -MP -MF $(OBJ_DIR)/$*.d -c $< -o $@ 22 | 23 | $(OBJ_DIR): 24 | @mkdir -p $@ 25 | 26 | $(DEPS): 27 | 28 | include $(wildcard $(DEPS)) 29 | 30 | clean: 31 | rm -rf $(OUT_DIR) 32 | 33 | load: unload 34 | hyprctl plugin load $(shell pwd)/$(TARGET) 35 | 36 | unload: 37 | hyprctl plugin unload $(shell pwd)/$(TARGET) 38 | 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hypr-DarkWindow 2 | Hyprland plugin that adds possibility to invert the color of specific windows. 3 | 4 | ![preview](./res/preview.png) 5 | 6 | ## Configuration 7 | Adds 2 Dispatches `invertwindow WINDOW` and `invertactivewindow`. 8 | 9 | Or you can use windowrulev2 lines: 10 | ```conf 11 | # hyprland.conf 12 | windowrulev2 = plugin:invertwindow,class:(pb170.exe) 13 | windowrulev2 = plugin:invertwindow,fullscreen:1 14 | ``` 15 | 16 | > [!WARNING] 17 | > The following Config keyword was removed, please switch to the new windowrule version. 18 | 19 | ```conf 20 | # hyprland.conf 21 | darkwindow_invert = class:(pb170.exe) 22 | darkwindow_invert = fullscreen:1 23 | ``` 24 | 25 | ## Installation 26 | 27 | ### Hyprland >= v0.36.0 28 | We now support Nix, wooo! 29 | 30 | You should already have a fully working home-manager setup before adding this plugin. 31 | ```nix 32 | #flake.nix 33 | inputs = { 34 | home-manager = { ... }; 35 | hyprland = { ... }; 36 | ... 37 | hypr-darkwindow = { 38 | url = "github:micha4w/Hypr-DarkWindow/tags/v0.36.0"; 39 | inputs.hyprland.follows = "hyprland"; 40 | }; 41 | }; 42 | 43 | outputs = { 44 | home-manager, 45 | hypr-darkwindow, 46 | ... 47 | }: { 48 | ... = { 49 | home-manager.users.micha4w = { 50 | wayland.windowManager.hyprland.plugins = [ 51 | hypr-darkwindow.packages.${pkgs.system}.Hypr-DarkWindow 52 | ]; 53 | }; 54 | }; 55 | } 56 | ``` 57 | 58 | > [!NOTE] 59 | > In this example `inputs.hypr-darkwindow.url` sets the tag, Make sure that tag matches your Hyprland version. 60 | 61 | 62 | ### Hyprland >= v0.34.0 63 | Install using `hyprpm` 64 | ```sh 65 | hyprpm add https://github.com/micha4w/Hypr-DarkWindow 66 | hyprpm enable Hypr-DarkWindow 67 | hyprpm reload 68 | ``` 69 | 70 | ### Hyprland >= v0.28.0 71 | Installable using [Hyprload](https://github.com/duckonaut/hyprload) 72 | ```toml 73 | # hyprload.toml 74 | plugins = [ 75 | "micha4w/Hypr-DarkWindow", 76 | ] 77 | ``` 78 | -------------------------------------------------------------------------------- /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 = "3.0.1"; 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/hypr-darkwindow.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 = "Hypr-DarkWindow" 3 | authors = ["micha4w"] 4 | commit_pins = [ 5 | # v Hyprland v Hypr-DarkWindow 6 | ["1c460e98f870676b15871fe4e5bfeb1a32a3d6d8", "7eb7e04dea8cb92f22b7c2df4c78d4f29e28643d"], # v0.36.0 7 | ["19c90048d65a5660384d2fb865926a366696d6be", "7eb7e04dea8cb92f22b7c2df4c78d4f29e28643d"], # v0.37.0 8 | ["c5e28ebcfe00a510922779b2c568cfa52a317445", "7eb7e04dea8cb92f22b7c2df4c78d4f29e28643d"], # v0.37.1 9 | ["3875679755014997776e091ff8903acfb311dd2f", "c506360835a3049ec01b33ef0fdab13a1e20d314"], # v0.38.0 10 | ["360ede79d124ffdeebbe8401f1ac4bc0dbec2c91", "c506360835a3049ec01b33ef0fdab13a1e20d314"], # v0.38.1 11 | ["e93fbd7c4f991cb8ef03e433ccc4d43587923e15", "e847f179adf4eab3517623e3d5209f531435a61a"], # v0.39.0 12 | ["fe7b748eb668136dd0558b7c8279bfcd7ab4d759", "e847f179adf4eab3517623e3d5209f531435a61a"], # v0.39.1 13 | ["cba1ade848feac44b2eda677503900639581c3f4", "a0d1964d91e59ba9199991ddcece48a56dcb3fc9"], # v0.40.0 14 | ["ea2501d4556f84d3de86a4ae2f4b22a474555b9f", "d1c4337676d618db7199e4717c59807b4cafac5e"], # v0.41.0 15 | ["9e781040d9067c2711ec2e9f5b47b76ef70762b3", "ad5cb981f801e32b576617ec9290c282304c30d4"], # v0.41.1 16 | ["918d8340afd652b011b937d29d5eea0be08467f5", "ad5cb981f801e32b576617ec9290c282304c30d4"], # v0.41.2 17 | ["9a09eac79b85c846e3a865a9078a3f8ff65a9259", "58bb4f967c58334a1d6dbe0ab2442ac8984d2ac6"], # v0.42.0 18 | ["0f594732b063a90d44df8c5d402d658f27471dfe", "cf6795a382826218411c614b9c2d11c6bb78a368"], # v0.43.0 19 | ["0c7a7e2d569eeed9d6025f3eef4ea0690d90845d", "cf6795a382826218411c614b9c2d11c6bb78a368"], # v0.44.0 20 | ["4520b30d498daca8079365bdb909a8dea38e8d55", "dde4f7f14c3a51f244e3e8a197b468a424b39d14"], # v0.44.1 21 | ["a425fbebe4cf4238e48a42f724ef2208959d66cf", "8fae7b5698379be08ea61a2e7a3349da0b930a94"], # v0.45.0 22 | ["500d2a3580388afc8b620b0a3624147faa34f98b", "8fae7b5698379be08ea61a2e7a3349da0b930a94"], # v0.45.1 23 | ["12f9a0d0b93f691d4d9923716557154d74777b0a", "8fae7b5698379be08ea61a2e7a3349da0b930a94"], # v0.45.2 24 | ["788ae588979c2a1ff8a660f16e3c502ef5796755", "98ae7daf09a5c03e0595bc55e46059cc81d426d6"], # v0.46.0 25 | ["254fc2bc6000075f660b4b8ed818a6af544d1d64", "98ae7daf09a5c03e0595bc55e46059cc81d426d6"], # v0.46.1 26 | ["0bd541f2fd902dbfa04c3ea2ccf679395e316887", "98ae7daf09a5c03e0595bc55e46059cc81d426d6"], # v0.46.2 27 | ["04ac46c54357278fc68f0a95d26347ea0db99496", "ae5725442267ae7ae417ca2b27840b0f84edcf78"], # v0.47.0 28 | ["75dff7205f6d2bd437abfb4196f700abee92581a", "ae5725442267ae7ae417ca2b27840b0f84edcf78"], # v0.47.1 29 | ["882f7ad7d2bbfc7440d0ccaef93b1cdd78e8e3ff", "ae5725442267ae7ae417ca2b27840b0f84edcf78"], # v0.47.2 30 | ["5ee35f914f921e5696030698e74fb5566a804768", "2d2e7ebac5c52cb23f3cbf06052ceba47a73802d"], # v0.48.0 31 | ["29e2e59fdbab8ed2cc23a20e3c6043d5decb5cdc", "2d2e7ebac5c52cb23f3cbf06052ceba47a73802d"], # v0.48.1 32 | ["9958d297641b5c84dcff93f9039d80a5ad37ab00", "26300e4b5358899d060edfbc426bd1ecce670ae5"], # v0.48.1 33 | 34 | 35 | ] 36 | 37 | [Hypr-DarkWindow] 38 | description = "Invert the colors of specific Windows" 39 | authors = ["micha4w"] 40 | output = "out/hypr-darkwindow.so" 41 | build = [ 42 | "make all -j", 43 | ] 44 | -------------------------------------------------------------------------------- /res/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micha4w/Hypr-DarkWindow/c8eb492fb740a50b1251702ba5b02be3dcaf4da2/res/preview.png -------------------------------------------------------------------------------- /src/Helpers.cpp: -------------------------------------------------------------------------------- 1 | #include "Helpers.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "OpenGL.cpp.h" 9 | 10 | 11 | static std::string invert(std::string source) { 12 | size_t out = source.find("layout(location = 0) out vec4 "); 13 | std::string outVar; 14 | if (out != std::string::npos) { 15 | // es 300 16 | size_t endOut = source.find(";", out); 17 | outVar = std::string(&source[out + sizeof("layout(location = 0) out vec4 ") - 1], &source[endOut]); 18 | outVar = Hyprutils::String::trim(outVar); 19 | } else { 20 | // es 200 21 | if (!source.contains("gl_FragColor")) { 22 | Debug::log(ERR, "Failed to invert GLSL Code, no gl_FragColor:\n{}", source); 23 | throw std::runtime_error("Frag source does not contain a usage of 'gl_FragColor'"); 24 | } 25 | 26 | outVar = "gl_FragColor"; 27 | } 28 | 29 | if (!source.contains("void main(")) { 30 | Debug::log(ERR, "Failed to invert GLSL Code, no main function: {}", source); 31 | throw std::runtime_error("Frag source does not contain an occurence of 'void main('"); 32 | } 33 | 34 | Hyprutils::String::replaceInString(source, "void main(", "void main_uninverted("); 35 | 36 | source += std::format(R"glsl( 37 | void main() {{ 38 | main_uninverted(); 39 | 40 | // Invert Colors 41 | {0}.rgb = vec3(1.) - vec3(.88, .9, .92) * {0}.rgb; 42 | 43 | // Invert Hue 44 | {0}.rgb = dot(vec3(0.26312, 0.5283, 0.10488), {0}.rgb) * 2.0 - {0}.rgb; 45 | }} 46 | )glsl", outVar); 47 | 48 | return source; 49 | } 50 | 51 | void ShaderHolder::Init() 52 | { 53 | g_pHyprRenderer->makeEGLCurrent(); 54 | 55 | std::map includes; 56 | loadShaderInclude("rounding.glsl", includes); 57 | loadShaderInclude("CM.glsl", includes); 58 | 59 | const auto TEXVERTSRC = g_pHyprOpenGL->m_shaders->TEXVERTSRC; 60 | const auto TEXVERTSRC300 = g_pHyprOpenGL->m_shaders->TEXVERTSRC300; 61 | 62 | const auto TEXFRAGSRCCM = invert(processShader("CM.frag", includes)); 63 | const auto TEXFRAGSRCRGBA = invert(processShader("rgba.frag", includes)); 64 | const auto TEXFRAGSRCRGBX = invert(processShader("rgbx.frag", includes)); 65 | const auto TEXFRAGSRCEXT = invert(processShader("ext.frag", includes)); 66 | 67 | CM.program = createProgram(TEXVERTSRC300, TEXFRAGSRCCM, true, true); 68 | if (CM.program) { 69 | getCMShaderUniforms(CM); 70 | getRoundingShaderUniforms(CM); 71 | CM.proj = glGetUniformLocation(CM.program, "proj"); 72 | CM.tex = glGetUniformLocation(CM.program, "tex"); 73 | CM.texType = glGetUniformLocation(CM.program, "texType"); 74 | CM.alphaMatte = glGetUniformLocation(CM.program, "texMatte"); 75 | CM.alpha = glGetUniformLocation(CM.program, "alpha"); 76 | CM.texAttrib = glGetAttribLocation(CM.program, "texcoord"); 77 | CM.matteTexAttrib = glGetAttribLocation(CM.program, "texcoordMatte"); 78 | CM.posAttrib = glGetAttribLocation(CM.program, "pos"); 79 | CM.discardOpaque = glGetUniformLocation(CM.program, "discardOpaque"); 80 | CM.discardAlpha = glGetUniformLocation(CM.program, "discardAlpha"); 81 | CM.discardAlphaValue = glGetUniformLocation(CM.program, "discardAlphaValue"); 82 | CM.applyTint = glGetUniformLocation(CM.program, "applyTint"); 83 | CM.tint = glGetUniformLocation(CM.program, "tint"); 84 | CM.useAlphaMatte = glGetUniformLocation(CM.program, "useAlphaMatte"); 85 | CM.createVao(); 86 | } else { 87 | if (g_pHyprOpenGL->m_shaders->m_shCM.program) throw std::runtime_error("Failed to create Shader: CM.frag, check hyprland logs"); 88 | } 89 | 90 | RGBA.program = createProgram(TEXVERTSRC, TEXFRAGSRCRGBA, true, true); 91 | if (!RGBA.program) throw std::runtime_error("Failed to create Shader: rgba.frag, check hyprland logs"); 92 | getRoundingShaderUniforms(RGBA); 93 | RGBA.proj = glGetUniformLocation(RGBA.program, "proj"); 94 | RGBA.tex = glGetUniformLocation(RGBA.program, "tex"); 95 | RGBA.alphaMatte = glGetUniformLocation(RGBA.program, "texMatte"); 96 | RGBA.alpha = glGetUniformLocation(RGBA.program, "alpha"); 97 | RGBA.texAttrib = glGetAttribLocation(RGBA.program, "texcoord"); 98 | RGBA.matteTexAttrib = glGetAttribLocation(RGBA.program, "texcoordMatte"); 99 | RGBA.posAttrib = glGetAttribLocation(RGBA.program, "pos"); 100 | RGBA.discardOpaque = glGetUniformLocation(RGBA.program, "discardOpaque"); 101 | RGBA.discardAlpha = glGetUniformLocation(RGBA.program, "discardAlpha"); 102 | RGBA.discardAlphaValue = glGetUniformLocation(RGBA.program, "discardAlphaValue"); 103 | RGBA.applyTint = glGetUniformLocation(RGBA.program, "applyTint"); 104 | RGBA.tint = glGetUniformLocation(RGBA.program, "tint"); 105 | RGBA.useAlphaMatte = glGetUniformLocation(RGBA.program, "useAlphaMatte"); 106 | RGBA.createVao(); 107 | 108 | RGBX.program = createProgram(TEXVERTSRC, TEXFRAGSRCRGBX, true, true); 109 | if (!RGBX.program) throw std::runtime_error("Failed to create Shader: rgbx.frag, check hyprland logs"); 110 | getRoundingShaderUniforms(RGBX); 111 | RGBX.tex = glGetUniformLocation(RGBX.program, "tex"); 112 | RGBX.proj = glGetUniformLocation(RGBX.program, "proj"); 113 | RGBX.alpha = glGetUniformLocation(RGBX.program, "alpha"); 114 | RGBX.texAttrib = glGetAttribLocation(RGBX.program, "texcoord"); 115 | RGBX.posAttrib = glGetAttribLocation(RGBX.program, "pos"); 116 | RGBX.discardOpaque = glGetUniformLocation(RGBX.program, "discardOpaque"); 117 | RGBX.discardAlpha = glGetUniformLocation(RGBX.program, "discardAlpha"); 118 | RGBX.discardAlphaValue = glGetUniformLocation(RGBX.program, "discardAlphaValue"); 119 | RGBX.applyTint = glGetUniformLocation(RGBX.program, "applyTint"); 120 | RGBX.tint = glGetUniformLocation(RGBX.program, "tint"); 121 | RGBX.createVao(); 122 | 123 | EXT.program = createProgram(TEXVERTSRC, TEXFRAGSRCEXT, true, true); 124 | if (!EXT.program) throw std::runtime_error("Failed to create Shader: ext.frag, check hyprland logs"); 125 | getRoundingShaderUniforms(EXT); 126 | EXT.tex = glGetUniformLocation(EXT.program, "tex"); 127 | EXT.proj = glGetUniformLocation(EXT.program, "proj"); 128 | EXT.alpha = glGetUniformLocation(EXT.program, "alpha"); 129 | EXT.posAttrib = glGetAttribLocation(EXT.program, "pos"); 130 | EXT.texAttrib = glGetAttribLocation(EXT.program, "texcoord"); 131 | EXT.discardOpaque = glGetUniformLocation(EXT.program, "discardOpaque"); 132 | EXT.discardAlpha = glGetUniformLocation(EXT.program, "discardAlpha"); 133 | EXT.discardAlphaValue = glGetUniformLocation(EXT.program, "discardAlphaValue"); 134 | EXT.applyTint = glGetUniformLocation(EXT.program, "applyTint"); 135 | EXT.tint = glGetUniformLocation(EXT.program, "tint"); 136 | EXT.createVao(); 137 | 138 | g_pHyprRenderer->unsetEGL(); 139 | } 140 | 141 | void ShaderHolder::Destroy() 142 | { 143 | g_pHyprRenderer->makeEGLCurrent(); 144 | 145 | CM.destroy(); 146 | RGBA.destroy(); 147 | RGBX.destroy(); 148 | EXT.destroy(); 149 | 150 | g_pHyprRenderer->unsetEGL(); 151 | } 152 | -------------------------------------------------------------------------------- /src/Helpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | namespace std 12 | { 13 | inline void swap(SShader& a, SShader& b) 14 | { 15 | // memcpy because speed! 16 | // (Would break unordered map, but those aren't in use anyway..) 17 | uint8_t c[sizeof(SShader)]; 18 | std::memcpy(&c, &a, sizeof(SShader)); 19 | std::memcpy(&a, &b, sizeof(SShader)); 20 | std::memcpy(&b, &c, sizeof(SShader)); 21 | } 22 | } 23 | 24 | struct ShaderHolder 25 | { 26 | SShader CM; 27 | SShader RGBA; 28 | SShader RGBX; 29 | SShader EXT; 30 | 31 | void Init(); 32 | void Destroy(); 33 | }; 34 | -------------------------------------------------------------------------------- /src/OpenGL.cpp.h: -------------------------------------------------------------------------------- 1 | // This file contains functions that were yoinked from hyprland/src/render/OpenGL.cpp 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | const std::vector ASSET_PATHS = { 12 | #ifdef DATAROOTDIR 13 | DATAROOTDIR, 14 | #endif 15 | "/usr/share", 16 | "/usr/local/share", 17 | }; 18 | 19 | static void logShaderError(const GLuint& shader, bool program, bool silent = false) { 20 | GLint maxLength = 0; 21 | if (program) 22 | glGetProgramiv(shader, GL_INFO_LOG_LENGTH, &maxLength); 23 | else 24 | glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); 25 | 26 | std::vector errorLog(maxLength); 27 | if (program) 28 | glGetProgramInfoLog(shader, maxLength, &maxLength, errorLog.data()); 29 | else 30 | glGetShaderInfoLog(shader, maxLength, &maxLength, errorLog.data()); 31 | std::string errorStr(errorLog.begin(), errorLog.end()); 32 | 33 | const auto FULLERROR = (program ? "Screen shader parser: Error linking program:" : "Screen shader parser: Error compiling shader: ") + errorStr; 34 | 35 | Debug::log(ERR, "Failed to link shader: {}", FULLERROR); 36 | 37 | if (!silent) 38 | g_pConfigManager->addParseError(FULLERROR); 39 | } 40 | 41 | static GLuint compileShader(const GLuint& type, std::string src, bool dynamic, bool silent) { 42 | auto shader = glCreateShader(type); 43 | 44 | auto shaderSource = src.c_str(); 45 | 46 | glShaderSource(shader, 1, (const GLchar**)&shaderSource, nullptr); 47 | glCompileShader(shader); 48 | 49 | GLint ok; 50 | glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); 51 | 52 | if (dynamic) { 53 | if (ok == GL_FALSE) { 54 | logShaderError(shader, false, silent); 55 | return 0; 56 | } 57 | } else { 58 | if (ok != GL_TRUE) 59 | logShaderError(shader, false); 60 | RASSERT(ok != GL_FALSE, "compileShader() failed! GL_COMPILE_STATUS not OK!"); 61 | } 62 | 63 | return shader; 64 | } 65 | 66 | static GLuint createProgram(const std::string& vert, const std::string& frag, bool dynamic, bool silent) { 67 | auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert, dynamic, silent); 68 | if (dynamic) { 69 | if (vertCompiled == 0) 70 | return 0; 71 | } else 72 | RASSERT(vertCompiled, "Compiling shader failed. VERTEX nullptr! Shader source:\n\n{}", vert); 73 | 74 | auto fragCompiled = compileShader(GL_FRAGMENT_SHADER, frag, dynamic, silent); 75 | if (dynamic) { 76 | if (fragCompiled == 0) 77 | return 0; 78 | } else 79 | RASSERT(fragCompiled, "Compiling shader failed. FRAGMENT nullptr! Shader source:\n\n{}", frag); 80 | 81 | auto prog = glCreateProgram(); 82 | glAttachShader(prog, vertCompiled); 83 | glAttachShader(prog, fragCompiled); 84 | glLinkProgram(prog); 85 | 86 | glDetachShader(prog, vertCompiled); 87 | glDetachShader(prog, fragCompiled); 88 | glDeleteShader(vertCompiled); 89 | glDeleteShader(fragCompiled); 90 | 91 | GLint ok; 92 | glGetProgramiv(prog, GL_LINK_STATUS, &ok); 93 | if (dynamic) { 94 | if (ok == GL_FALSE) { 95 | logShaderError(prog, true, silent); 96 | return 0; 97 | } 98 | } else { 99 | if (ok != GL_TRUE) 100 | logShaderError(prog, true); 101 | RASSERT(ok != GL_FALSE, "createProgram() failed! GL_LINK_STATUS not OK!"); 102 | } 103 | 104 | return prog; 105 | } 106 | 107 | static std::string loadShader(const std::string& filename) { 108 | const auto home = Hyprutils::Path::getHome(); 109 | if (home.has_value()) { 110 | const auto src = NFsUtils::readFileAsString(home.value() + "/hypr/shaders/" + filename); 111 | if (src.has_value()) 112 | return src.value(); 113 | } 114 | for (auto& e : ASSET_PATHS) { 115 | const auto src = NFsUtils::readFileAsString(std::string{e} + "/hypr/shaders/" + filename); 116 | if (src.has_value()) 117 | return src.value(); 118 | } 119 | if (SHADERS.contains(filename)) 120 | return SHADERS.at(filename); 121 | throw std::runtime_error(std::format("Couldn't load shader {}", filename)); 122 | } 123 | 124 | static void loadShaderInclude(const std::string& filename, std::map& includes) { 125 | includes.insert({filename, loadShader(filename)}); 126 | } 127 | 128 | static void processShaderIncludes(std::string& source, const std::map& includes) { 129 | for (auto it = includes.begin(); it != includes.end(); ++it) { 130 | Hyprutils::String::replaceInString(source, "#include \"" + it->first + "\"", it->second); 131 | } 132 | } 133 | 134 | static std::string processShader(const std::string& filename, const std::map& includes) { 135 | auto source = loadShader(filename); 136 | processShaderIncludes(source, includes); 137 | return source; 138 | } 139 | 140 | // shader has #include "CM.glsl" 141 | static void getCMShaderUniforms(SShader& shader) { 142 | shader.skipCM = glGetUniformLocation(shader.program, "skipCM"); 143 | shader.sourceTF = glGetUniformLocation(shader.program, "sourceTF"); 144 | shader.targetTF = glGetUniformLocation(shader.program, "targetTF"); 145 | shader.srcTFRange = glGetUniformLocation(shader.program, "srcTFRange"); 146 | shader.dstTFRange = glGetUniformLocation(shader.program, "dstTFRange"); 147 | shader.targetPrimaries = glGetUniformLocation(shader.program, "targetPrimaries"); 148 | shader.maxLuminance = glGetUniformLocation(shader.program, "maxLuminance"); 149 | shader.dstMaxLuminance = glGetUniformLocation(shader.program, "dstMaxLuminance"); 150 | shader.dstRefLuminance = glGetUniformLocation(shader.program, "dstRefLuminance"); 151 | shader.sdrSaturation = glGetUniformLocation(shader.program, "sdrSaturation"); 152 | shader.sdrBrightness = glGetUniformLocation(shader.program, "sdrBrightnessMultiplier"); 153 | shader.convertMatrix = glGetUniformLocation(shader.program, "convertMatrix"); 154 | } 155 | 156 | // shader has #include "rounding.glsl" 157 | static void getRoundingShaderUniforms(SShader& shader) { 158 | shader.topLeft = glGetUniformLocation(shader.program, "topLeft"); 159 | shader.fullSize = glGetUniformLocation(shader.program, "fullSize"); 160 | shader.radius = glGetUniformLocation(shader.program, "radius"); 161 | shader.roundingPower = glGetUniformLocation(shader.program, "roundingPower"); 162 | } -------------------------------------------------------------------------------- /src/WindowInverter.cpp: -------------------------------------------------------------------------------- 1 | #include "WindowInverter.h" 2 | 3 | #include 4 | 5 | 6 | void WindowInverter::OnRenderWindowPre(PHLWINDOW window) 7 | { 8 | bool shouldInvert = 9 | (std::find(m_InvertedWindows.begin(), m_InvertedWindows.end(), window) 10 | != m_InvertedWindows.end()) ^ 11 | (std::find(m_ManuallyInvertedWindows.begin(), m_ManuallyInvertedWindows.end(), window) 12 | != m_ManuallyInvertedWindows.end()); 13 | 14 | if (shouldInvert) 15 | { 16 | std::swap(m_Shaders.EXT, g_pHyprOpenGL->m_shaders->m_shEXT); 17 | std::swap(m_Shaders.RGBA, g_pHyprOpenGL->m_shaders->m_shRGBA); 18 | std::swap(m_Shaders.RGBX, g_pHyprOpenGL->m_shaders->m_shRGBX); 19 | std::swap(m_Shaders.CM, g_pHyprOpenGL->m_shaders->m_shCM); 20 | m_ShadersSwapped = true; 21 | } 22 | } 23 | 24 | void WindowInverter::OnRenderWindowPost() 25 | { 26 | if (m_ShadersSwapped) 27 | { 28 | std::swap(m_Shaders.EXT, g_pHyprOpenGL->m_shaders->m_shEXT); 29 | std::swap(m_Shaders.RGBA, g_pHyprOpenGL->m_shaders->m_shRGBA); 30 | std::swap(m_Shaders.RGBX, g_pHyprOpenGL->m_shaders->m_shRGBX); 31 | std::swap(m_Shaders.CM, g_pHyprOpenGL->m_shaders->m_shCM); 32 | m_ShadersSwapped = false; 33 | } 34 | } 35 | 36 | void WindowInverter::OnWindowClose(PHLWINDOW window) 37 | { 38 | static const auto remove = [](std::vector& windows, PHLWINDOW& window) { 39 | auto windowIt = std::find(windows.begin(), windows.end(), window); 40 | if (windowIt != windows.end()) 41 | { 42 | std::swap(*windowIt, *(windows.end() - 1)); 43 | windows.pop_back(); 44 | } 45 | }; 46 | 47 | remove(m_InvertedWindows, window); 48 | remove(m_ManuallyInvertedWindows, window); 49 | } 50 | 51 | void WindowInverter::Init() 52 | { 53 | m_Shaders.Init(); 54 | } 55 | 56 | void WindowInverter::Unload() 57 | { 58 | if (m_ShadersSwapped) 59 | { 60 | std::swap(m_Shaders.EXT, g_pHyprOpenGL->m_shaders->m_shEXT); 61 | std::swap(m_Shaders.RGBA, g_pHyprOpenGL->m_shaders->m_shRGBA); 62 | std::swap(m_Shaders.RGBX, g_pHyprOpenGL->m_shaders->m_shRGBX); 63 | std::swap(m_Shaders.CM, g_pHyprOpenGL->m_shaders->m_shCM); 64 | m_ShadersSwapped = false; 65 | } 66 | 67 | m_Shaders.Destroy(); 68 | } 69 | 70 | void WindowInverter::InvertIfMatches(PHLWINDOW window) 71 | { 72 | // for some reason, some events (currently `activeWindow`) sometimes pass a null pointer 73 | if (!window) return; 74 | 75 | std::vector> rules = g_pConfigManager->getMatchingRules(window); 76 | bool shouldInvert = std::any_of(rules.begin(), rules.end(), [](const SP& rule) { 77 | return rule->m_rule == "plugin:invertwindow"; 78 | }); 79 | 80 | auto windowIt = std::find(m_InvertedWindows.begin(), m_InvertedWindows.end(), window); 81 | if (shouldInvert != (windowIt != m_InvertedWindows.end())) 82 | { 83 | if (shouldInvert) 84 | m_InvertedWindows.push_back(window); 85 | else 86 | { 87 | std::swap(*windowIt, *(m_InvertedWindows.end() - 1)); 88 | m_InvertedWindows.pop_back(); 89 | } 90 | 91 | g_pHyprRenderer->damageWindow(window); 92 | } 93 | } 94 | 95 | 96 | void WindowInverter::ToggleInvert(PHLWINDOW window) 97 | { 98 | if (!window) 99 | return; 100 | 101 | auto windowIt = std::find(m_ManuallyInvertedWindows.begin(), m_ManuallyInvertedWindows.end(), window); 102 | if (windowIt == m_ManuallyInvertedWindows.end()) 103 | m_ManuallyInvertedWindows.push_back(window); 104 | else 105 | { 106 | std::swap(*windowIt, *(m_ManuallyInvertedWindows.end() - 1)); 107 | m_ManuallyInvertedWindows.pop_back(); 108 | } 109 | 110 | g_pHyprRenderer->damageWindow(window); 111 | } 112 | 113 | 114 | void WindowInverter::Reload() 115 | { 116 | m_InvertedWindows = {}; 117 | 118 | for (const auto& window : g_pCompositor->m_windows) 119 | InvertIfMatches(window); 120 | } 121 | -------------------------------------------------------------------------------- /src/WindowInverter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Helpers.h" 6 | 7 | #include 8 | 9 | 10 | class WindowInverter 11 | { 12 | public: 13 | void Init(); 14 | void Unload(); 15 | 16 | void InvertIfMatches(PHLWINDOW window); 17 | void ToggleInvert(PHLWINDOW window); 18 | void Reload(); 19 | 20 | void OnRenderWindowPre(PHLWINDOW window); 21 | void OnRenderWindowPost(); 22 | void OnWindowClose(PHLWINDOW window); 23 | 24 | private: 25 | std::vector m_InvertWindowRules; 26 | std::vector m_InvertedWindows; 27 | std::vector m_ManuallyInvertedWindows; 28 | 29 | ShaderHolder m_Shaders; 30 | bool m_ShadersSwapped = false; 31 | }; -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define private public 4 | #include 5 | #undef private 6 | 7 | #include "WindowInverter.h" 8 | 9 | #include 10 | 11 | #include 12 | 13 | 14 | inline HANDLE PHANDLE = nullptr; 15 | 16 | inline WindowInverter g_WindowInverter; 17 | inline std::mutex g_InverterMutex; 18 | 19 | inline std::vector> g_Callbacks; 20 | CFunctionHook* g_surfacePassDraw; 21 | 22 | // TODO check out transformers 23 | 24 | void hkSurfacePassDraw(CSurfacePassElement* thisptr, const CRegion& damage) { 25 | { 26 | std::lock_guard lock(g_InverterMutex); 27 | g_WindowInverter.OnRenderWindowPre(thisptr->m_data.pWindow); 28 | } 29 | 30 | ((decltype(&hkSurfacePassDraw))g_surfacePassDraw->m_original)(thisptr, damage); 31 | 32 | { 33 | std::lock_guard lock(g_InverterMutex); 34 | g_WindowInverter.OnRenderWindowPost(); 35 | } 36 | } 37 | 38 | APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) 39 | { 40 | PHANDLE = handle; 41 | 42 | { 43 | std::lock_guard lock(g_InverterMutex); 44 | g_WindowInverter.Init(); 45 | } 46 | 47 | g_Callbacks = {}; 48 | g_Callbacks.push_back(HyprlandAPI::registerCallbackDynamic( 49 | PHANDLE, "configReloaded", 50 | [&](void* self, SCallbackInfo&, std::any data) { 51 | std::lock_guard lock(g_InverterMutex); 52 | g_WindowInverter.Reload(); 53 | } 54 | )); 55 | g_Callbacks.push_back(HyprlandAPI::registerCallbackDynamic( 56 | PHANDLE, "closeWindow", 57 | [&](void* self, SCallbackInfo&, std::any data) { 58 | std::lock_guard lock(g_InverterMutex); 59 | g_WindowInverter.OnWindowClose(std::any_cast(data)); 60 | } 61 | )); 62 | g_Callbacks.push_back(HyprlandAPI::registerCallbackDynamic( 63 | PHANDLE, "windowUpdateRules", 64 | [&](void* self, SCallbackInfo&, std::any data) { 65 | std::lock_guard lock(g_InverterMutex); 66 | g_WindowInverter.InvertIfMatches(std::any_cast(data)); 67 | } 68 | )); 69 | 70 | const auto findFunction = [&](const std::string& className, const std::string& name) { 71 | auto all = HyprlandAPI::findFunctionsByName(PHANDLE, name); 72 | auto found = std::find_if(all.begin(), all.end(), [&](const SFunctionMatch& line) { 73 | return line.demangled.starts_with(className + "::" + name); 74 | }); 75 | if (found != all.end()) 76 | return std::optional(*found); 77 | else 78 | return std::optional(); 79 | }; 80 | 81 | static const auto pDraw = findFunction("CSurfacePassElement", "draw"); 82 | if (!pDraw) throw std::runtime_error("Failed to find CSurfacePassElement::draw"); 83 | g_surfacePassDraw = HyprlandAPI::createFunctionHook(handle, pDraw->address, (void*)&hkSurfacePassDraw); 84 | g_surfacePassDraw->hook(); 85 | 86 | HyprlandAPI::addDispatcherV2(PHANDLE, "invertwindow", [&](std::string args) { 87 | std::lock_guard lock(g_InverterMutex); 88 | g_WindowInverter.ToggleInvert(g_pCompositor->getWindowByRegex(args)); 89 | return SDispatchResult{}; 90 | }); 91 | HyprlandAPI::addDispatcherV2(PHANDLE, "invertactivewindow", [&](std::string args) { 92 | std::lock_guard lock(g_InverterMutex); 93 | g_WindowInverter.ToggleInvert(g_pCompositor->m_lastWindow.lock()); 94 | return SDispatchResult{}; 95 | }); 96 | 97 | return { 98 | "Hypr-DarkWindow", 99 | "Allows you to set dark mode for only specific Windows", 100 | "micha4w", 101 | "3.0.1" 102 | }; 103 | } 104 | 105 | APICALL EXPORT void PLUGIN_EXIT() 106 | { 107 | std::lock_guard lock(g_InverterMutex); 108 | g_Callbacks = {}; 109 | g_WindowInverter.Unload(); 110 | } 111 | 112 | APICALL EXPORT std::string PLUGIN_API_VERSION() 113 | { 114 | return HYPRLAND_API_VERSION; 115 | } 116 | --------------------------------------------------------------------------------