├── .github └── ISSUE_TEMPLATE │ └── issue-template.md ├── .gitignore ├── CONTRIBUTORS ├── LICENSE.spdx ├── LICENSES ├── MIT └── MPL-2.0 ├── README.md ├── compfy-dbus.desktop ├── compfy.desktop ├── compfy.sample.conf ├── flake.lock ├── flake.nix ├── man ├── compfy.1 └── compfy.md ├── media ├── compfy-banner.png ├── compfy.png ├── compfy.svg └── icons │ └── compfy.png ├── meson.build ├── meson_options.txt └── src ├── atom.c ├── atom.h ├── backend ├── backend.c ├── backend.h ├── backend_common.c ├── backend_common.h ├── driver.c ├── driver.h ├── dummy │ └── dummy.c ├── gl │ ├── blur.c │ ├── egl.c │ ├── egl.h │ ├── gl_common.c │ ├── gl_common.h │ ├── glx.c │ ├── glx.h │ └── shaders.c ├── meson.build └── xrender │ └── xrender.c ├── c2.c ├── c2.h ├── cache.c ├── cache.h ├── common.h ├── compfy.c ├── compfy.h ├── compfy.modulemap ├── compiler.h ├── config.c ├── config.h ├── config_libconfig.c ├── dbus.c ├── dbus.h ├── diagnostic.c ├── diagnostic.h ├── err.h ├── event.c ├── event.h ├── file_watch.c ├── file_watch.h ├── kernel.c ├── kernel.h ├── list.h ├── log.c ├── log.h ├── meson.build ├── meta.h ├── opengl.c ├── opengl.h ├── options.c ├── options.h ├── region.h ├── render.c ├── render.h ├── string_utils.c ├── string_utils.h ├── types.h ├── update.c ├── uthash_extra.h ├── utils.c ├── utils.h ├── vsync.c ├── vsync.h ├── win.c ├── win.h ├── win_defs.h ├── x.c ├── x.h ├── xrescheck.c └── xrescheck.h /.github/ISSUE_TEMPLATE/issue-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue Template 3 | about: This template is for creating an issue about a problem or bug. 4 | title: '' 5 | labels: '' 6 | assignees: allusive-dev 7 | 8 | --- 9 | 10 | IF SOMETHING IS NOT WORKING. READ THE DOCUMENTATION BEFORE CREATING AN ISSUE!!! 11 | 12 | <-- Please fill in all the forms below that are relevant then delete this line --> 13 | 14 | **What is the issue** 15 | 16 | **What did you expect** 17 | 18 | **Relevant Images or Videos:** 19 | 20 | **Your configuration** 21 | 22 | **Package Version:** 23 | **Build Method:** 24 | **WM:** 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Nix 2 | result* 3 | 4 | # Misc 5 | .direnv 6 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Sorted in alphabetical order. Feel free to open an issue or create a 2 | pull request if you want to change or remove your mention. 3 | 4 | # Updated 14/Nov/2023 5 | 6 | Contributors to `compfy / picom-allusive` 7 | 8 | Allusive (allusive-dev) 9 | IogaMaster 10 | Alyiss Snow (aliyss) 11 | xrun1 12 | 13 | Contributors to `pijulius/picom` 14 | 15 | dccsilag 16 | Pijulius 17 | 18 | Contributors to `yshui/picom` 19 | 20 | Adam Jackson 21 | adelin-b 22 | Alexander Kapshuna 23 | Antonin Décimo 24 | Antonio Vivace 25 | Avi ד 26 | Ben Friesen 27 | Bernd Busse 28 | Bert Gijsbers 29 | bhagwan 30 | Bodhi 31 | Brottweiler 32 | Carl Worth 33 | Christopher Jeffrey 34 | Corax26 35 | Dan Elkouby 36 | Dana Jansens 37 | Daniel Kwan 38 | Dave Airlie 39 | David Schlachter 40 | dolio 41 | Duncan 42 | Dylan Araps 43 | Einar Lielmanis 44 | Eric Anholt 45 | Evgeniy Baskov 46 | Greg Flynn 47 | h7x4 48 | Harish Rajagopal 49 | hasufell 50 | i-c-u-p 51 | Ignacio Taranto 52 | Istvan Petres 53 | Jake 54 | James Cloos 55 | Jamey Sharp 56 | Jan Beich 57 | Jarrad 58 | Javeed Shaikh 59 | Jerónimo Navarro 60 | jialeens 61 | Johnny Pribyl 62 | Keith Packard 63 | Kevin Kelley 64 | ktprograms 65 | Kurenshe Nurdaulet 66 | Lukas Schmelzeisen 67 | Mark Tiefenbruck 68 | Matthew Allum 69 | Maxim Solovyov 70 | Michael Reed 71 | Michele Lambertucci 72 | mæp 73 | Namkhai Bourquin 74 | Nate Hart 75 | nia 76 | Nikolay Borodin 77 | notfoss 78 | Omar Polo 79 | oofsauce 80 | orbea 81 | Paradigm0001 82 | Patrick Collins 83 | Peter Mattern 84 | Phil Blundell 85 | Que Quotion 86 | Rafael Kitover 87 | Richard Grenville 88 | Rytis Karpuska 89 | Samuel Hand 90 | Scott Leggett 91 | scrouthtv 92 | Sebastien Waegeneire 93 | Stefan Radziuk 94 | Subhaditya Nath 95 | Tasos Sahanidis 96 | Thiago Kenji Okada 97 | Tilman Sauerbeck 98 | Tim Siegel 99 | Tim van Dalen 100 | tokyoneon78 101 | Tom Dörr 102 | Tomas Janousek 103 | Toni Jarjour 104 | Tuomas Kinnunen 105 | Uli Schlachter 106 | Walter Lapchynski 107 | Will Dietz 108 | XeCycle 109 | Yuxuan Shui 110 | zilrich 111 | ಠ_ಠ 112 | -------------------------------------------------------------------------------- /LICENSE.spdx: -------------------------------------------------------------------------------- 1 | SPDXVersion: SPDX-2.1 2 | DataLicense: CC0-1.0 3 | PackageName: compfy 4 | PackageLicenseDeclared: MPL-2.0 AND MIT 5 | -------------------------------------------------------------------------------- /LICENSES/MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 2 | 3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Compfy 2 | 3 | ## Notice 4 | 5 | Context: #61 6 | So for some reason my account wasn't deleted so I guess I'll just put it out there that this project is no longer being maintained! 7 | Why? 8 | 1. See #61 9 | 2. Picom is finally updating and adding animations support, BUT GUESS WHAT. They are not adding anything from Compfy. (sad) 10 | 11 | ### Update 12 | 13 | So i just tried to create a new repository just to let people know about my new GitHub account [@jasper-at-windswept](https://github.com/jasper-at-windswept), but I can't create repositories because my account APPARENTLY still being deleted, so yeah if this repository just dissapears in like a month you now know why. 14 | 15 | ## TOC 16 | 17 | 1. [Introduction](#introduction) 18 | 2. [Getting Started](#getting-started) 19 | - [Installation](#installation) 20 | - [Configuration](#configuration) 21 | 3. [Usage](#usage) 22 | 4. [License](#license) 23 | 24 | ![Compfy Banner](media/compfy-banner.png) 25 | 26 | ## Introduction 27 | 28 | Compfy is a Compositor for the [X11](https://www.x.org/wiki/) platform on Linux. Compfy's main purpose is to pretty up your graphical desktop environment by letting users have features like transparency, background blurring, rounded corners, animations and way more! 29 | 30 | Compfy was made as an alternative to [Picom](https://github.com/yshui/picom), another popular X11 compositor. Compfy is based on Picom but provides more features and active community support. 31 | 32 | **Before we begin I want to say I massive thank you to my sponsors as they made this work possible!** 33 | 34 | - [@SolninjaA](https://github.com/SolninjaA) ($5 One Time) (The First Ever Sponsor!) 35 | - [@maclightning2](https://github.com/maclightning2) ($3 A Month) (First Ever Monthly Sponsor) 36 | 37 | ## Getting Started 38 | 39 | Let's cover how to install Compfy on your system and briefly go over how to configure it. 40 | 41 | ### Installation 42 | 43 | Compfy is avaliable on a few packaged platforms including on the AUR and very soon coming to [Nixpkgs](https://github.com/NixOS/nixpkgs) 44 | 45 | | Linux Distribution | Method | Compatibility | 46 | | ------------------ | ------- | --------------| 47 | | Arch Linux | [AUR](https://aur.archlinux.org/packages/compfy) | :orange_circle: | 48 | | NixOS (Stable) | [Custom Package](https://github.com/allusive-dev/compfy/releases/latest) | :green_circle: | 49 | | Other | [Manually Building](#manually) | :green_circle: | 50 | 51 | *AUR package is not maintained by me anymore* 52 | 53 | #### Manually 54 | 55 | *Note if you want to enabled the `--check-for-updates` option (avaliable as of `1.7.0`) you will need to build with these commands instead. 56 | (`--check-for-updates` will print out your current version and the latest release version number if you are connected to the internet)* 57 | 58 | Without `--check-for-updates`: 59 | ``` 60 | $ meson setup . build 61 | $ ninja -C build 62 | $ ninja -C build install 63 | ``` 64 | With `--check-for-updates`: 65 | ``` 66 | $ meson setup -Dupdate_checks=true . build 67 | $ ninja -C build 68 | $ ninja -C build install 69 | ``` 70 | 71 | Dependencies: 72 | 73 | ``` 74 | libconfig 75 | libdbus 76 | libev 77 | libglvnd 78 | libx11 79 | libxcb 80 | libxdg-basedir 81 | pcre2 82 | pixman 83 | uthash 84 | xcb-util-image 85 | xcb-util-renderutil 86 | gcc (make) 87 | git (make) 88 | meson (make) 89 | ninja (make) 90 | 91 | # If you enabled -Dupdate-checks you will also need these dependencies. 92 | 93 | 'json-c' or 'json_c' 94 | 'curl' or 'libcurl' 95 | ``` 96 | 97 | ### Configuration 98 | 99 | Compfy stores its default configuration at `/etc/xdg/compfy.conf.example`(may vary depending on OS). 100 | The default configuration provides almost all the options you will ever need, some commented out. 101 | 102 | It is reccomended you store your personal configuration at `~/.config/compfy.conf` or `~/.config/compfy/compfy.conf` 103 | *You can do this by copying over the sample configuration or simply writing your own.* 104 | 105 | For a VERY detailed guide on configurating Compfy please see the Documentation. 106 | It is avaliable in the Wiki tab above and on [Donument](https://donument.com/d/Allusive/compfy/-/documents/) an up and coming git versioned database allows for more than just code to be versioned. 107 | 108 | Or if you still use `man` the Documentation is also avaliable under `man compfy`. 109 | 110 | 111 | ## Usage 112 | 113 | Compfy has very similar command line arguments to Picom. 114 | 115 | You can view all the commands and what they do with `compfy --help` but here is a few to get you started. 116 | 117 | ``` 118 | $ compfy --version 119 | # Print the current version 120 | 121 | $ compfy --help 122 | # Print all the avaliable command line options with explanations 123 | 124 | $ compfy -b 125 | or 126 | $ compfy --daemon 127 | # Run Compfy in the background, dissociating it from the terminal. 128 | ``` 129 | 130 | ## License 131 | 132 | Compfy is licensed under [MIT](LICENSES/MIT) and [MPL-2.0](LICENSES/MPL-2.0) 133 | -------------------------------------------------------------------------------- /compfy-dbus.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | NoDisplay=true 5 | Name=Compfy (dbus) 6 | GenericName=X compositor (dbus) 7 | Comment=An X compositor with dbus backend enabled 8 | Categories=Utility; 9 | Keywords=compositor;composite manager;window effects;transparency;opacity; 10 | TryExec=compfy 11 | Exec=compfy --dbus 12 | -------------------------------------------------------------------------------- /compfy.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | NoDisplay=false 5 | Name=Compfy 6 | GenericName=X compositor 7 | Comment=An X compositor 8 | Categories=Utility; 9 | Keywords=compositor;composite manager;window effects;transparency;opacity; 10 | TryExec=compfy 11 | Exec=compfy 12 | StartupNotify=false 13 | Terminal=false 14 | Icon=compfy 15 | -------------------------------------------------------------------------------- /compfy.sample.conf: -------------------------------------------------------------------------------- 1 | 2 | # Enables patches for specific window managers. 3 | # Currently patched: "awesome", "dwm", "herb" 4 | wm-support = "none"; 5 | 6 | ################################# 7 | # ANIMATIONS # 8 | ################################# 9 | 10 | # Toggles whether animations should be used for windows 11 | animations = true; 12 | 13 | # Changes animation stiffness. 14 | # What stiffness basically is inferring is how much the window geometry will be stretched, 15 | # when opening/closing windows 16 | animation-stiffness = 120; 17 | 18 | # Change the mass of windows 19 | # Modifying the mass of windows makes them virtually heavier and therefore slower to animate. 20 | animation-window-mass = 0.5; 21 | 22 | # Change dampening applied during the animation 23 | # This setting basically does what it says. It dampens the animation of windows. 24 | animation-dampening = 12; 25 | 26 | # Toggles clamping 27 | # if you are using a animation setting that would make the window extend larger than it would be after, 28 | # the animation has played then it will cut the animation off once the window reaches its physical geometry. 29 | animation-clamping = false; 30 | 31 | # Set the open window animation. 32 | # Options: ("none","zoom","fly-in","slide-up","slide-down","slide-left","slide-right") 33 | animation-for-open-window = "zoom"; 34 | 35 | # Set the closing window animation. 36 | # Options: ("none","zoom","fly-in","slide-up","slide-down","slide-left","slide-right") 37 | animation-for-unmap-window = "zoom"; 38 | 39 | # Exclude certain windows from having a open animation. 40 | 41 | # animation-open-exclude = [ 42 | # "class_g = 'Dunst'" 43 | # ]; 44 | 45 | # Exclude certain windows from having a closing animation. 46 | 47 | # animation-unmap-exclude = [ 48 | # "class_g = 'Dunst'" 49 | # ]; 50 | 51 | ################################# 52 | # Corners # 53 | ################################# 54 | 55 | # Adjusts the window corner rounding in pixels. 56 | corner-radius = 14; 57 | 58 | # Explicitly declare the corner-radius of individual windows. 59 | # 60 | # corners-rule = [ 61 | # "20:class_g = 'Polybar'", 62 | # "15:class_g = 'Dunst'", 63 | # ]; 64 | 65 | # Exclude conditions for rounded corners. 66 | # 67 | # rounded-corners-exclude = [ 68 | # "window_type = 'dock'", 69 | # "class_g = 'bar'" 70 | # ]; 71 | 72 | 73 | ################################# 74 | # Shadows # 75 | ################################# 76 | 77 | # Enabled client-side shadows on windows. Note desktop windows 78 | # (windows with '_NET_WM_WINDOW_TYPE_DESKTOP') never get shadow, 79 | # unless explicitly requested using the wintypes option. 80 | # 81 | shadow = true; 82 | 83 | # The blur radius for shadows, in pixels. (defaults to 16) 84 | shadow-radius = 16; 85 | 86 | # The opacity of shadows. (0.0 - 1.0, defaults to 0.75) 87 | # shadow-opacity = 0.75; 88 | 89 | # The left offset for shadows, in pixels. (defaults to -15) 90 | shadow-offset-x = -15; 91 | 92 | # The top offset for shadows, in pixels. (defaults to -15) 93 | shadow-offset-y = -15; 94 | 95 | # Red color value of shadow (0.0 - 1.0, defaults to 0). 96 | # shadow-red = 0; 97 | 98 | # Green color value of shadow (0.0 - 1.0, defaults to 0). 99 | # shadow-green = 0; 100 | 101 | # Blue color value of shadow (0.0 - 1.0, defaults to 0). 102 | # shadow-blue = 0; 103 | 104 | # Hex string color value of shadow (#000000 - #FFFFFF, defaults to #000000). This option will override options set shadow-(red/green/blue) 105 | # shadow-color = "#000000"; 106 | 107 | # Specify a list of conditions of windows that should have no shadow. 108 | 109 | # shadow-exclude = [ 110 | # "class_g = 'change-me'", 111 | # ]; 112 | 113 | ################################# 114 | # Fading # 115 | ################################# 116 | 117 | 118 | # Fade windows in/out when opening/closing and when opacity changes, 119 | # unless no-fading-openclose is used. 120 | # FADING IS REQUIRED FOR CLOSING ANIMATIONS 121 | fading = true; 122 | 123 | # Opacity change between steps while fading in. (0.01 - 1.0, defaults to 0.03) 124 | # 125 | # Increasing this value will result in faster fading in of windows. 126 | # Decreasing makes the fading in of windows take longer. 127 | # (This does not means the animations take longer, just the fading). 128 | fade-in-step = 0.03; 129 | 130 | # Opacity change between steps while fading out. (0.01 - 1.0, defaults to 0.03) 131 | # 132 | # Increasing this value will result in faster fading out AND animating out of windows. 133 | # Decreasing this makes fading and animating out take longer. 134 | fade-out-step = 0.03; 135 | 136 | # The time between steps in fade step, in milliseconds. (> 0, defaults to 10) 137 | # fade-delta = 10 138 | 139 | # Specify a list of conditions of windows that should not be faded. 140 | # fade-exclude = [] 141 | 142 | # Do not fade on window open/close. 143 | # no-fading-openclose = false 144 | 145 | # Do not fade destroyed ARGB windows with WM frame. Workaround of bugs in Openbox, Fluxbox, etc. 146 | # no-fading-destroyed-argb = false 147 | 148 | 149 | ################################# 150 | # Transparency / Opacity # 151 | ################################# 152 | 153 | 154 | # Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0) 155 | inactive-opacity = 1.0; 156 | 157 | # Opacity of window titlebars and borders. (0.1 - 1.0, disabled by default) 158 | frame-opacity = 1.0; 159 | 160 | # Overrides any opacities set in `opacity-rule` when set to true. 161 | inactive-opacity-override = false; 162 | 163 | # Default opacity for active windows. (0.0 - 1.0, defaults to 1.0) 164 | active-opacity = 1.0; 165 | 166 | # Dim inactive windows. (0.0 - 1.0, defaults to 0.0) 167 | # inactive-dim = 0.0; 168 | 169 | # A list of windows that should have their inactive-opacity set to whatever the active-opacity is. 170 | # Unless the window is also in active-exclude then it will be set to 1.0 or if it is set in "opacity-rule", 171 | # it will use the opacity set there as the inactive-opacity 172 | # Also excludes effects caused by inactive-dim 173 | # 174 | # inactive-exclude = [ 175 | # "class_g = 'dwm'" 176 | # ]; 177 | 178 | 179 | # A list of windows that should never have their opacity changed by active-opacity when focused. 180 | # 181 | # active-exclude = [ 182 | # "class_g = 'dwm'" 183 | # ]; 184 | 185 | 186 | # Specify a list of opacity rules, in the format `PERCENT:PATTERN`, 187 | # like `50:name *= "Firefox"`. 188 | 189 | # opacity-rule = [ 190 | # "80:class_g = 'Alacritty'" 191 | # ]; 192 | 193 | 194 | ################################# 195 | # Background-Blurring # 196 | ################################# 197 | 198 | # Blur background of windows. 199 | # 200 | blur-background = true; 201 | 202 | # Parameters for background blurring, see the *BLUR* section for more information. 203 | blur-method = "dual_kawase"; 204 | # 205 | # blur-size = 12 206 | # 207 | # blur-deviation = false 208 | # 209 | blur-strength = 5; 210 | 211 | # Blur kernel preset. Play around and see what looks best. 212 | # Options "3x3box", "5x5box", "7x7box", "3x3gaussian", "5x5gaussian", "7x7gaussian", "9x9gaussian", "11x11gaussian" 213 | # 214 | # blur-kern = "3x3box"; 215 | 216 | # Toggle whether you want to use a blacklist or whitelist. 217 | # Defaults to "true" 218 | blur-whitelist = true; 219 | 220 | # Whitelist for windows to have background blurring 221 | blur-include = [ 222 | "class_g = 'Alacritty'", 223 | "class_g = 'kitty'" 224 | ]; 225 | 226 | # Blacklist for background blurring. 227 | # Only works if "blur-whitelist = false;" 228 | # 229 | # blur-exclude = [ 230 | # "class_g = 'Firefox'" 231 | # ]; 232 | 233 | ################################# 234 | # General Settings # 235 | ################################# 236 | 237 | # Enable remote control via D-Bus. See the man page for more details. 238 | # dbus = true 239 | 240 | # Daemonize process. Fork to background after initialization. Causes issues with certain (badly-written) drivers. 241 | # daemon = false 242 | 243 | # Specify the backend to use: `xrender`, `glx`, or `xr_glx_hybrid`. 244 | backend = "glx"; 245 | 246 | # Enable/disable VSync. 247 | vsync = true; 248 | 249 | log-level = "info"; 250 | 251 | ################################# 252 | # ADVANCED # 253 | ################################# 254 | 255 | # Set settings for specific window types. See Wiki for more information 256 | # Below is an example of how to disabled shadows on Firefox/Librewolf menus, 257 | # and also make sure they are considered focused so that they cannot be affected by inactive window settings. 258 | # 259 | # wintypes: 260 | # { 261 | # utility = { shadow = false; focus = true; }; 262 | # popup_menu = { shadow = false; focus = true; }; 263 | # }; -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1694529238, 9 | "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1698890957, 24 | "narHash": "sha256-DJ+SppjpPBoJr0Aro9TAcP3sxApCSieY6BYBCoWGUX8=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "c082856b850ec60cda9f0a0db2bc7bd8900d708c", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixpkgs-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 4 | flake-utils.url = "github:numtide/flake-utils"; 5 | }; 6 | 7 | outputs = { 8 | self, 9 | nixpkgs, 10 | flake-utils, 11 | ... 12 | }: 13 | flake-utils.lib.eachDefaultSystem ( 14 | system: let 15 | pkgs = nixpkgs.legacyPackages.${system}; 16 | nativeBuildInputs = with pkgs; []; 17 | buildInputs = with pkgs; [ pcre2 json_c curl ]; 18 | in { 19 | devShells.default = pkgs.mkShell {inherit nativeBuildInputs buildInputs;}; 20 | 21 | packages = rec { 22 | default = compfy; 23 | compfy = pkgs.picom.overrideAttrs (_old: { 24 | src = ./.; 25 | name = "compfy"; 26 | buildInputs = buildInputs ++ _old.buildInputs; 27 | nativeBuildInputs = nativeBuildInputs ++ _old.nativeBuildInputs; 28 | postInstall = ''''; 29 | mainProgram = "compfy"; 30 | }); 31 | }; 32 | 33 | overlays = rec { 34 | default = compfy; 35 | compfy = self.packages.default; 36 | }; 37 | } 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /man/compfy.md: -------------------------------------------------------------------------------- 1 | # Compfy 2 | 3 | ## Guide for Configuring Compfy 4 | 5 | #### Config can be placed at either `~/.config/compfy.conf` or `~/.config/compfy/compfy.conf` 6 | 7 | ## Basic Syntax 8 | 9 | Variables are formatted like this 10 | 11 | ``` 12 | variable-name = value; 13 | ``` 14 | All values require a semi-colon on the end as shown above. 15 | 16 | When declaring a animation-name you need to use double quotes like such 17 | 18 | ``` 19 | animation-for-unmap-window = "slide-down"; 20 | ``` 21 | 22 | When using rule-sets there are two ways to provide windows to it. 23 | 24 | With almost every rule-set it will follow this syntax and structure. 25 | 26 | ``` 27 | rule-set = [ 28 | "class_g = 'foo'" # Select windows by classname 29 | "name = 'bar'" # Select windows by name 30 | ]; 31 | ``` 32 | 33 | In the example you replace `foo` with the `WM_CLASS` which can be found using the `xprop` command provided by [xorg-xprop](https://archlinux.org/packages/extra/x86_64/xorg-xprop/) on Arch and `xorg.xprop` on NixOS. 34 | 35 | Sometimes `xprop` will output multiple class names. If the first class name does not work, try the second one. 36 | 37 | 38 | # Options 39 | 40 | ## corner-radius 41 | 42 | _By default this is set to 12 in the sample config_ 43 | 44 | Adjusts the window corner rounding in pixels. 45 | 46 | ## rounded-corners-exclude [] 47 | 48 | _By default this rule-set is empty_ 49 | 50 | Here you can declare rules of what windows will not have their borders rounded. This can be good for some games that go full screen but still have rounded borders. 51 | 52 | Here is an example 53 | 54 | ``` 55 | rounded-corners-exclude = [ 56 | "class_g = 'Alacritty'", 57 | ]; 58 | ``` 59 | 60 | ## corners-rule [] 61 | 62 | _By default this rule-set is empty_ 63 | 64 | Within this rule-set you can explicitly declare the corner-radius of individual windows based on their name or class name attributes. 65 | 66 | It functions in a similar way to `opacity-rule` in that the number you put before the window name is the amount of rounding you want applied to that window. 67 | 68 | Currently you can only use number 1-100. This may be fixed in later updates. 69 | 70 | ``` 71 | corners-rule = [ 72 | "20:class_g = 'Polybar'", 73 | "15:class_g = 'Dunst'", 74 | ]; 75 | ``` 76 | 77 | ## shadow 78 | 79 | _By default this will be set to `false` in the sample config_ 80 | 81 | Enables shadows for windows. 82 | 83 | Shadows tend to be a bit finicky on some window managers, so make sure to play around with it and see what is best for you. 84 | 85 | ## shadow-radius 86 | 87 | _By default this is set to 12_ 88 | 89 | Shadow radius/roundness in pixels. 90 | 91 | ## shadow-opacity 92 | 93 | _By default this is set to 0.75 (0.0 ~ 1.0)_ 94 | 95 | Set the opacity of the window shadow. 96 | 97 | ## shadow-offset-x 98 | 99 | _By default this is set to -15 (-100 ~ 100)_ 100 | 101 | Set the offset of the shadow along the x-axis. 102 | 103 | ## shadow-offset-y 104 | 105 | _By default this is set to -15 (-100 ~ 100)_ 106 | 107 | Set the offset of the shadow along the y-axis. 108 | 109 | ## shadow-color 110 | 111 | The color for shadows as a HEX code. 112 | 113 | ``` 114 | shadow-color = "#101010"; 115 | ``` 116 | 117 | ## shadow-exclude [] 118 | 119 | _By default this rule-set is empty_ 120 | 121 | Declare windows which will not render a shadow. 122 | 123 | ## fading 124 | 125 | _By default this is set to `true` in the sample config_ 126 | 127 | _Fade windows in/out when opening/closing and when opacity changes_ 128 | 129 | Fading is required for unmap animations to work. 130 | 131 | ## fade-in-step 132 | 133 | _By default this is set to `0.03` in the sample config_ 134 | 135 | _Opacity change between steps while fading in. (0.01 - 1.0)_ 136 | 137 | Increasing this value will result in faster fading in of windows. 138 | Decreasing makes the fading in of windows take longer. (This does not means the animations take longer, just the fading). 139 | 140 | ## fade-out-step 141 | 142 | _By default this is set to `0.03` in the sample config_ 143 | 144 | _Opacity change between steps while fading out. (0.01 - 1.0)_ 145 | 146 | Increasing this value will result in faster fading out AND animating out of windows. 147 | Decreasing this makes fading and animating out take longer. 148 | 149 | ## fade-delta 150 | 151 | _By default this is set to `5`_ 152 | 153 | _The time between steps in fade step, in milliseconds. ( > 0)_ 154 | 155 | Currently unsure what this does but it is recommended to keep it at the default. 156 | 157 | ## fade-exclude [] 158 | 159 | _Empty by default_ 160 | 161 | _Specify a list of windows that should not be faded._ 162 | 163 | ``` 164 | fade-exclude = [ 165 | "class_g = 'foo'" 166 | ] 167 | ``` 168 | 169 | ## inactive-opacity 170 | 171 | _By default this is set to 1.0 (0.0 - 1.0)_ 172 | 173 | Declares the opacity of unfocused windows. 174 | 175 | ## frame-opacity 176 | 177 | _By default this is set to 1.0 (0.0 - 1.0)_ 178 | 179 | Declares the opacity of window borders and title-bars. 180 | 181 | ## inactive-opacity-override 182 | 183 | _By default this is set to false (true/false)_ 184 | 185 | If this is not enabled then windows which have their opacity set in `opacity-rule` or `wintypes` will not be affected by `inactive-opacity`. Enable this to fix that. 186 | 187 | ## active-opacity 188 | 189 | _By default this is set to 1.0 (0.0 - 1.0)_ 190 | 191 | Declares the opacity of focused windows. 192 | 193 | ## active-opacity-exclude [] 194 | 195 | _By default this rule-set is empty_ 196 | 197 | A list of windows that should never have their opacity changed by `active-opacity` when focused. 198 | 199 | ## inactive-exclude [] 200 | 201 | _By default this rule-set is empty_ 202 | 203 | A list of windows that should have their `inactive-opacity` set to whatever the `active-opacity` is. Unless the window is also in `active-opacity-exclude` then it will be set to `1.0` or if it is set in `opacity-rule` it will use the opacity set there as the `inactive-opacity`. 204 | 205 | Also excludes inactive dimming. 206 | 207 | ## focus-exclude [] 208 | 209 | _By default this rule-set is empty_ 210 | 211 | Declare windows that should always be considered to be focused by the compositor. 212 | 213 | ## opacity-rule [] 214 | 215 | _By default this rule-set is empty_ 216 | 217 | Declare windows that should have their opacity hard set. As an example. Where 95 is can be anything between (0 - 100) 218 | 219 | ``` 220 | opacity-rule = [ 221 | "95:class_g = 'Alacritty'", 222 | ]; 223 | ``` 224 | 225 | ## blur-background 226 | 227 | _By default this is set to false_ 228 | 229 | Toggle background blurring 230 | 231 | ## blur-method 232 | 233 | _By default this is set to `none`_ 234 | 235 | Selects the blur method. Available options are: 236 | 237 | - none 238 | - kernel 239 | - gaussian 240 | - box 241 | - dual_kawase (most used) 242 | 243 | ## blur-radius 244 | 245 | _By default this is set to 3_ 246 | 247 | Sets the radius of the blur. 248 | 249 | ## blur-strength 250 | 251 | _By default this is set to 5_ 252 | 253 | Sets the strength/intensity of the blur. 254 | 255 | ## blur-whitelist 256 | 257 | _Defaults to `true` (true / false)_ 258 | 259 | Toggle whether you want to use blurring on a whitelist basis(blur-include) or a blacklist basis(blur-exclude). 260 | 261 | ## blur-include 262 | 263 | _By default this rule set is empty_ 264 | 265 | This acts a a whitelist for blurring the background of windows. See the example below. 266 | Using a whitelist helps to reduce hardware consumption. 267 | 268 | ``` 269 | blur-rule = [ 270 | "class_g = 'kitty'", 271 | "class_g = 'Emacs'", 272 | "class_g = 'Rofi'" 273 | ]; 274 | ``` 275 | 276 | ## blur-exclude 277 | 278 | _By default this rule set is empty_ 279 | 280 | When used with `blur-whitelist = false;` this will exclude specific windows from having their background blurred. 281 | 282 | ``` 283 | blur-exclude = [ 284 | "class_g = 'Firefox'" 285 | ]; 286 | ``` 287 | 288 | The part of the wiki you have probably all come for. 289 | 290 | ## animations 291 | 292 | _By default this option is set to `true`_(true/false) 293 | 294 | _Toggles whether animations should be used for windows_ 295 | 296 | 297 | ## animation-stiffness 298 | 299 | _By default this is set to `100`_ 300 | 301 | _Changes the stiffness of animations_ 302 | 303 | What stiffness basically is inferring is how much the window geometry will be stretched when opening/closing windows. It's best illustrated in the video below. 304 | 305 | With a higher stiffness the windows go to the final animation position faster resulting in a snappier looking transition. 306 | 307 | ## animation-window-mass 308 | 309 | _By default this is set to `0.5`_ 310 | 311 | _Changes the mass of windows_ 312 | 313 | Modifying the mass of windows makes them virtually heavier and therefore slower to animate. 314 | 315 | ## animation-dampening 316 | 317 | _By default this is set to `10`_ 318 | 319 | _Changes the dampening applied to windows during the animation_ 320 | 321 | This setting basically does what it says. It dampens the animation of windows. 322 | 323 | The more windows are dampened, the slower/softer they come into and out of view. 324 | 325 | ## animation-clamping 326 | 327 | _By default this is set to `false`_ (true/false) 328 | 329 | _Toggles window clamping_ 330 | 331 | Stop the animation from making the window exceed its set geometry. Shoutout to [@thecodsman](https://github.com/thecodsman) for finding this out. 332 | 333 | Basically if you are using a animation setting that would make the window extend larger than it would be after the animation has played then it will cut the animation off once the window reaches its physical geometry. 334 | 335 | ## animation-for-open-window 336 | 337 | _By default this is set to `zoom`_ 338 | 339 | Options: 340 | - `fly-in` _Newly opened windows will fly in from a random position on the edge of the screen_ 341 | - `zoom` _Newly opened windows will zoom in from the center point of wherever they were going to appear_ 342 | - `slide-up` _Newly opened windows will slide up from the bottom of screen_ 343 | - `slide-down` _Newly opened windows will slide down from the top of the screen_ 344 | - `slide-left` _Newly opened windows will slide in from the right of where they are opened_ 345 | - `slide-right` _Newly opened windows will slide in from the left of where they are opened_ 346 | - `auto` _Newly opened windows will not have an animation_ 347 | 348 | Feel free to play around with these options to see which animation you prefer. 349 | 350 | ## animation-for-unmap-window 351 | 352 | _By default this is set to `zoom`_ 353 | 354 | _Defines the animation for when windows are closed/killed_ 355 | 356 | Options: 357 | - `fly-in` _Newly closed windows will fly out to a random position on the edge of the screen_ 358 | - `zoom` _Newly closed windows will zoom out from the center point of the window_ 359 | - `slide-up` _Newly closed windows will slide up from where they were closed_ 360 | - `slide-down` _Newly closed windows will slide down from where they were closed_ 361 | - `slide-left` _Newly closed windows will slide out from the right of where they are opened_ 362 | - `slide-right` _Newly closed windows will slide out from the left of where they are opened_ 363 | - `auto` _Newly closed windows will not have an animation_ 364 | 365 | Feel free to play around with these options to see which animation you prefer. 366 | 367 | ## animation-open-exclude 368 | 369 | _By default this rule-set is empty_ 370 | 371 | Define windows that will not render any open animation. 372 | 373 | Example 374 | ``` 375 | animation-open-exclude = [ 376 | "class_g = 'Alacritty'" 377 | ]; 378 | ``` 379 | 380 | ## animation-unmap-exclude 381 | 382 | _By default this rule-set is empty_ 383 | 384 | Define windows that will not render any closing animation. 385 | 386 | Example 387 | ``` 388 | animation-unmap-exclude = [ 389 | "class_g = 'Alacritty'" 390 | ]; 391 | ``` 392 | 393 | # Using wintypes. 394 | 395 | In picom you can set many options on the basis of what the windows 'type' is. 396 | 397 | Listed below are the available types. 398 | I can't say what applications or windows all of these link to so you will have to play with them to find what works. 399 | ``` 400 | wintypes: 401 | { 402 | desktop = {}; 403 | dock = {}; 404 | toolbar = {}; 405 | menu = {}; 406 | utility = {}; 407 | splash = {}; 408 | dialog = {}; 409 | normal = {}; 410 | dropdown_menu = {}; 411 | popup_menu = {}; 412 | tooltip = {}; 413 | notification = {}; 414 | combo = {}; 415 | dnd = {}; 416 | } 417 | ``` 418 | 419 | ## Wintype Options 420 | 421 | You can pass multiple options into one window type. Below is an example: 422 | 423 | ``` 424 | wintypes: 425 | { 426 | normal = { shadow = true; fade = false; animation = "slide-down"; }; 427 | } 428 | ``` 429 | 430 | 431 | Below is all the options you can pass into wintypes. 432 | 433 | ### shadow 434 | 435 | Toggle shadows. ( true / false ) 436 | 437 | ``` 438 | wintypes: 439 | { 440 | popup_menu = { shadow = true; }; 441 | } 442 | ``` 443 | 444 | ### fade 445 | 446 | Toggle fading. ( true / false ) 447 | 448 | ``` 449 | wintypes: 450 | { 451 | popup_menu = { fade = false; }; 452 | } 453 | ``` 454 | 455 | ### focus 456 | 457 | If focus was true, then that wintype would always be considered focus and therefore not be affected by inactive-opacity or inactive-dim. 458 | If false, it is always considered inactive/unfocused and then always applies those effects. 459 | 460 | ( true / false ) 461 | 462 | ``` 463 | wintypes: 464 | { 465 | menu = { focus = true; }; 466 | } 467 | ``` 468 | 469 | ### blur-background 470 | 471 | Toggle background blurring for windows under that type. ( true / false ) 472 | 473 | Setting this to false will not actually do anything since blurring is done on a whitelist basis. 474 | 475 | ``` 476 | wintypes: 477 | { 478 | desktop = { blur-background = true; }; 479 | } 480 | ``` 481 | 482 | ### full-shadow 483 | 484 | Toggle full shadow. ( true / false ) 485 | 486 | Not sure what full shadow is at the moment. 487 | 488 | ``` 489 | wintypes: 490 | { 491 | dropdown_menu = { full-shadow = false; }; 492 | } 493 | ``` 494 | 495 | ### redir-ignore 496 | 497 | Toggle redirect ignoring. ( true / false ) 498 | 499 | ``` 500 | wintypes: 501 | { 502 | dock = { redir-ignore = true; }; 503 | } 504 | ``` 505 | 506 | ### clip-shadow-above 507 | 508 | Toggle clipping shadows above a window. ( true / false ) 509 | 510 | ``` 511 | wintypes: 512 | { 513 | utility = { clip-shadow-above = true; }; 514 | } 515 | ``` 516 | 517 | ### opacity 518 | 519 | Set the opacity, both inactive and active. ( 0.0 ~ 1.0 ) 520 | 521 | ``` 522 | wintypes: 523 | { 524 | popup_menu = { opacity = 0.5; }; 525 | } 526 | ``` 527 | 528 | ### animation 529 | 530 | Set the open animation for specific wintypes. ( See `animation-for-open-window` under the Animations page for available options ) 531 | 532 | ``` 533 | wintypes: 534 | { 535 | popup_menu = { animation = "slide-right"; }; 536 | } 537 | ``` 538 | 539 | ### animation-unmap 540 | 541 | Set the close animation for specific wintypes. ( See `animation-for-unmap-window` under the Animations page for available options ) 542 | 543 | ``` 544 | wintypes: 545 | { 546 | tooltip = { animation-unmap = "fly-in"; }; 547 | } 548 | ``` 549 | -------------------------------------------------------------------------------- /media/compfy-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allusive-dev/compfy/f7d326cfab03d46133ad60be4ccabc134d8fadcb/media/compfy-banner.png -------------------------------------------------------------------------------- /media/compfy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allusive-dev/compfy/f7d326cfab03d46133ad60be4ccabc134d8fadcb/media/compfy.png -------------------------------------------------------------------------------- /media/icons/compfy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allusive-dev/compfy/f7d326cfab03d46133ad60be4ccabc134d8fadcb/media/icons/compfy.png -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('compfy', 'c', version: '1.7.2', 2 | default_options: ['c_std=c11', 'warning_level=1', 'buildtype=release']) 3 | 4 | cc = meson.get_compiler('c') 5 | 6 | # use project version by default 7 | version = 'v'+meson.project_version() 8 | 9 | add_global_arguments('-DCOMPFY_VERSION="'+version+'"', language: 'c') 10 | 11 | if get_option('buildtype') == 'release' 12 | add_global_arguments('-DNDEBUG', language: 'c') 13 | endif 14 | 15 | add_global_arguments('-D_GNU_SOURCE', language: 'c') 16 | 17 | if cc.has_header('stdc-predef.h') 18 | add_global_arguments('-DHAS_STDC_PREDEF_H', language: 'c') 19 | endif 20 | 21 | if get_option('warning_level') != '0' 22 | warns = [ 'cast-function-type', 'ignored-qualifiers', 'missing-parameter-type', 23 | 'nonnull', 'shadow', 'no-type-limits', 'old-style-declaration', 'override-init', 24 | 'sign-compare', 'type-limits', 'uninitialized', 'shift-negative-value', 25 | 'unused-but-set-parameter', 'unused-parameter', 'implicit-fallthrough=2', 26 | 'no-unknown-warning-option', 'no-missing-braces', 'conversion', 'empty-body' ] 27 | foreach w : warns 28 | if cc.has_argument('-W'+w) 29 | add_global_arguments('-W'+w, language: 'c') 30 | endif 31 | endforeach 32 | endif 33 | 34 | if get_option('with_docs') 35 | install_data('./man/compfy.1', install_dir: join_paths(get_option('mandir'), 'man1')) 36 | endif 37 | 38 | subdir('src') 39 | 40 | install_data('compfy.desktop', install_dir: 'share/applications') 41 | install_data('compfy.desktop', install_dir: get_option('sysconfdir') / 'xdg' / 'autostart') 42 | install_data('media/compfy.svg', install_dir: 'share/icons/hicolor/scalable/apps') 43 | install_data('media/icons/compfy.png', install_dir: 'share/icons/hicolor/48x48/apps') 44 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('config_file', type: 'boolean', value: true, description: 'Enable config file support') 2 | 3 | option('regex', type: 'boolean', value: true, description: 'Enable regex support in window conditions') 4 | 5 | option('vsync_drm', type: 'boolean', value: false, description: 'Enable support for using drm for vsync') 6 | 7 | option('opengl', type: 'boolean', value: true, description: 'Enable features that require opengl (opengl backend, and opengl vsync methods)') 8 | 9 | option('dbus', type: 'boolean', value: true, description: 'Enable support for D-Bus remote control') 10 | 11 | option('xrescheck', type: 'boolean', value: false, description: 'Enable X resource leak checker (for debug only)') 12 | 13 | option('with_docs', type: 'boolean', value: true, description: 'Build documentation and man pages') 14 | 15 | option('update_checks', type: 'boolean', value: false, description: 'Gives access to --check-for-updates option') 16 | 17 | option('unittest', type: 'boolean', value: false, description: 'Enable unittests in the code') 18 | -------------------------------------------------------------------------------- /src/atom.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "atom.h" 5 | #include "common.h" 6 | #include "log.h" 7 | #include "utils.h" 8 | 9 | static inline void *atom_getter(void *ud, const char *atom_name, int *err) { 10 | xcb_connection_t *c = ud; 11 | xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply( 12 | c, xcb_intern_atom(c, 0, to_u16_checked(strlen(atom_name)), atom_name), NULL); 13 | 14 | xcb_atom_t atom = XCB_NONE; 15 | if (reply) { 16 | log_debug("Atom %s is %d", atom_name, reply->atom); 17 | atom = reply->atom; 18 | free(reply); 19 | } else { 20 | log_error("Failed to intern atoms"); 21 | *err = 1; 22 | } 23 | return (void *)(intptr_t)atom; 24 | } 25 | 26 | /** 27 | * Create a new atom structure and fetch all predefined atoms 28 | */ 29 | struct atom *init_atoms(xcb_connection_t *c) { 30 | auto atoms = ccalloc(1, struct atom); 31 | atoms->c = new_cache((void *)c, atom_getter, NULL); 32 | #define ATOM_GET(x) atoms->a##x = (xcb_atom_t)(intptr_t)cache_get(atoms->c, #x, NULL) 33 | LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST1); 34 | LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST2); 35 | #undef ATOM_GET 36 | return atoms; 37 | } 38 | -------------------------------------------------------------------------------- /src/atom.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | 6 | #include "meta.h" 7 | #include "cache.h" 8 | 9 | // clang-format off 10 | // Splitted into 2 lists because of the limitation of our macros 11 | #define ATOM_LIST1 \ 12 | _NET_WM_WINDOW_OPACITY, \ 13 | _NET_FRAME_EXTENTS, \ 14 | WM_STATE, \ 15 | _NET_WM_NAME, \ 16 | _NET_WM_PID, \ 17 | WM_NAME, \ 18 | WM_CLASS, \ 19 | WM_ICON_NAME, \ 20 | WM_TRANSIENT_FOR, \ 21 | WM_WINDOW_ROLE, \ 22 | WM_CLIENT_LEADER, \ 23 | WM_CLIENT_MACHINE, \ 24 | _NET_ACTIVE_WINDOW, \ 25 | _COMPTON_SHADOW, \ 26 | _NET_WM_WINDOW_TYPE, \ 27 | _NET_CURRENT_DESKTOP 28 | 29 | #define ATOM_LIST2 \ 30 | _NET_WM_WINDOW_TYPE_DESKTOP, \ 31 | _NET_WM_WINDOW_TYPE_DOCK, \ 32 | _NET_WM_WINDOW_TYPE_TOOLBAR, \ 33 | _NET_WM_WINDOW_TYPE_MENU, \ 34 | _NET_WM_WINDOW_TYPE_UTILITY, \ 35 | _NET_WM_WINDOW_TYPE_SPLASH, \ 36 | _NET_WM_WINDOW_TYPE_DIALOG, \ 37 | _NET_WM_WINDOW_TYPE_NORMAL, \ 38 | _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, \ 39 | _NET_WM_WINDOW_TYPE_POPUP_MENU, \ 40 | _NET_WM_WINDOW_TYPE_TOOLTIP, \ 41 | _NET_WM_WINDOW_TYPE_NOTIFICATION, \ 42 | _NET_WM_WINDOW_TYPE_COMBO, \ 43 | _NET_WM_WINDOW_TYPE_DND, \ 44 | _NET_WM_STATE, \ 45 | _NET_WM_STATE_FULLSCREEN, \ 46 | _NET_WM_BYPASS_COMPOSITOR, \ 47 | UTF8_STRING, \ 48 | C_STRING 49 | // clang-format on 50 | 51 | #define ATOM_DEF(x) xcb_atom_t a##x 52 | 53 | struct atom { 54 | struct cache *c; 55 | LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST1); 56 | LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST2); 57 | }; 58 | 59 | struct atom *init_atoms(xcb_connection_t *); 60 | 61 | static inline xcb_atom_t get_atom(struct atom *a, const char *key) { 62 | return (xcb_atom_t)(intptr_t)cache_get(a->c, key, NULL); 63 | } 64 | 65 | static inline void destroy_atoms(struct atom *a) { 66 | cache_free(a->c); 67 | free(a); 68 | } 69 | -------------------------------------------------------------------------------- /src/backend/backend.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2018, Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include "compiler.h" 9 | #include "config.h" 10 | #include "driver.h" 11 | #include "kernel.h" 12 | #include "region.h" 13 | #include "types.h" 14 | #include "x.h" 15 | 16 | typedef struct session session_t; 17 | struct managed_win; 18 | 19 | struct backend_shadow_context; 20 | 21 | struct ev_loop; 22 | struct backend_operations; 23 | 24 | typedef struct backend_base { 25 | struct backend_operations *ops; 26 | xcb_connection_t *c; 27 | xcb_window_t root; 28 | struct ev_loop *loop; 29 | 30 | /// Whether the backend can accept new render request at the moment 31 | bool busy; 32 | // ... 33 | } backend_t; 34 | 35 | typedef struct geometry { 36 | int width; 37 | int height; 38 | } geometry_t; 39 | 40 | typedef struct coord { 41 | int x, y; 42 | } coord_t; 43 | 44 | typedef void (*backend_ready_callback_t)(void *); 45 | 46 | // This mimics OpenGL's ARB_robustness extension, which enables detection of GPU context 47 | // resets. 48 | // See: https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_robustness.txt, section 49 | // 2.6 "Graphics Reset Recovery". 50 | enum device_status { 51 | DEVICE_STATUS_NORMAL, 52 | DEVICE_STATUS_RESETTING, 53 | }; 54 | 55 | // When image properties are actually applied to the image, they are applied in a 56 | // particular order: 57 | // 58 | // Corner radius -> Color inversion -> Dimming -> Opacity multiply -> Limit maximum 59 | // brightness 60 | enum image_properties { 61 | // Whether the color of the image is inverted 62 | // 1 boolean, default: false 63 | IMAGE_PROPERTY_INVERTED, 64 | // How much the image is dimmed 65 | // 1 double, default: 0 66 | IMAGE_PROPERTY_DIM_LEVEL, 67 | // Image opacity, i.e. an alpha value multiplied to the alpha channel 68 | // 1 double, default: 1 69 | IMAGE_PROPERTY_OPACITY, 70 | // The effective size of the image, the image will be tiled to fit. 71 | // 2 int, default: the actual size of the image 72 | IMAGE_PROPERTY_EFFECTIVE_SIZE, 73 | // Limit how bright image can be. The image brightness is estimated by averaging 74 | // the pixels in the image, and dimming will be applied to scale the average 75 | // brightness down to the max brightness value. 76 | // 1 double, default: 1 77 | IMAGE_PROPERTY_MAX_BRIGHTNESS, 78 | // Gives the image a rounded corner. 79 | // 1 double, default: 0 80 | IMAGE_PROPERTY_CORNER_RADIUS, 81 | // Border width 82 | // 1 int, default: 0 83 | IMAGE_PROPERTY_BORDER_WIDTH, 84 | // Custom shader for this window. 85 | // 1 pointer to shader struct, default: NULL 86 | IMAGE_PROPERTY_CUSTOM_SHADER, 87 | }; 88 | 89 | enum image_operations { 90 | // Multiply the alpha channel by the argument 91 | IMAGE_OP_APPLY_ALPHA, 92 | }; 93 | 94 | enum shader_attributes { 95 | // Whether the shader needs to be render regardless of whether the window is 96 | // updated. 97 | SHADER_ATTRIBUTE_ANIMATED = 1, 98 | }; 99 | 100 | struct gaussian_blur_args { 101 | int size; 102 | double deviation; 103 | }; 104 | 105 | struct box_blur_args { 106 | int size; 107 | }; 108 | 109 | struct kernel_blur_args { 110 | struct conv **kernels; 111 | int kernel_count; 112 | }; 113 | 114 | struct dual_kawase_blur_args { 115 | int size; 116 | int strength; 117 | }; 118 | 119 | struct backend_operations { 120 | // =========== Initialization =========== 121 | 122 | /// Initialize the backend, prepare for rendering to the target window. 123 | /// Here is how you should choose target window: 124 | /// 1) if ps->overlay is not XCB_NONE, use that 125 | /// 2) use ps->root otherwise 126 | // TODO(yshui) make the target window a parameter 127 | backend_t *(*init)(session_t *)attr_nonnull(1); 128 | void (*deinit)(backend_t *backend_data) attr_nonnull(1); 129 | 130 | /// Called when rendering will be stopped for an unknown amount of 131 | /// time (e.g. when screen is unredirected). Free some resources. 132 | /// 133 | /// Optional, not yet used 134 | void (*pause)(backend_t *backend_data, session_t *ps); 135 | 136 | /// Called before rendering is resumed 137 | /// 138 | /// Optional, not yet used 139 | void (*resume)(backend_t *backend_data, session_t *ps); 140 | 141 | /// Called when root property changed, returns the new 142 | /// backend_data. Even if the backend_data changed, all 143 | /// the existing image data returned by this backend should 144 | /// remain valid. 145 | /// 146 | /// Optional 147 | void *(*root_change)(backend_t *backend_data, session_t *ps); 148 | 149 | // =========== Rendering ============ 150 | 151 | // NOTE: general idea about reg_paint/reg_op vs reg_visible is that reg_visible is 152 | // merely a hint. Ignoring reg_visible entirely don't affect the correctness of 153 | // the operation performed. OTOH reg_paint/reg_op is part of the parameters of the 154 | // operation, and must be honored in order to complete the operation correctly. 155 | 156 | // NOTE: due to complications introduced by use-damage and blur, the rendering API 157 | // is a bit weird. The idea is, `compose` and `blur` have to update a temporary 158 | // buffer, because `blur` requires data from an area slightly larger than the area 159 | // that will be visible. So the area outside the visible area has to be rendered, 160 | // but we have to discard the result (because the result of blurring that area 161 | // will be wrong). That's why we cannot render into the back buffer directly. 162 | // After rendering is done, `present` is called to update a portion of the actual 163 | // back buffer, then present it to the target (or update the target directly, 164 | // if not back buffered). 165 | 166 | /// Called before when a new frame starts. 167 | /// 168 | /// Optional 169 | void (*prepare)(backend_t *backend_data, const region_t *reg_damage); 170 | 171 | /** 172 | * Paint the content of an image onto the rendering buffer. 173 | * 174 | * @param backend_data the backend data 175 | * @param image_data the image to paint 176 | * @param dst_x, dst_y the top left corner of the image in the target 177 | * @param mask the mask image, the top left of the mask is aligned with 178 | * the top left of the image 179 | * @param reg_paint the clip region, in target coordinates 180 | * @param reg_visible the visible region, in target coordinates 181 | */ 182 | void (*compose)(backend_t *backend_data, void *image_data, coord_t image_dst, 183 | void *mask, coord_t mask_dst, const region_t *reg_paint, 184 | const region_t *reg_visible); 185 | 186 | /// Fill rectangle of the rendering buffer, mostly for debug purposes, optional. 187 | void (*fill)(backend_t *backend_data, struct color, const region_t *clip); 188 | 189 | /// Blur a given region of the rendering buffer. 190 | /// 191 | /// The blur is limited by `mask`. `mask_dst` specifies the top left corner of the 192 | /// mask is. 193 | bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx, void *mask, 194 | coord_t mask_dst, const region_t *reg_blur, 195 | const region_t *reg_visible) attr_nonnull(1, 3, 4, 6, 7); 196 | 197 | /// Update part of the back buffer with the rendering buffer, then present the 198 | /// back buffer onto the target window (if not back buffered, update part of the 199 | /// target window directly). 200 | /// 201 | /// Optional, if NULL, indicates the backend doesn't have render output 202 | /// 203 | /// @param region part of the target that should be updated 204 | void (*present)(backend_t *backend_data, const region_t *region) attr_nonnull(1, 2); 205 | 206 | /** 207 | * Bind a X pixmap to the backend's internal image data structure. 208 | * 209 | * @param backend_data backend data 210 | * @param pixmap X pixmap to bind 211 | * @param fmt information of the pixmap's visual 212 | * @param owned whether the ownership of the pixmap is transfered to the backend 213 | * @return backend internal data structure bound with this pixmap 214 | */ 215 | void *(*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap, 216 | struct xvisual_info fmt, bool owned); 217 | 218 | /// Create a shadow context for rendering shadows with radius `radius`. 219 | /// Default implementation: default_backend_create_shadow_context 220 | struct backend_shadow_context *(*create_shadow_context)(backend_t *backend_data, 221 | double radius); 222 | /// Destroy a shadow context 223 | /// Default implementation: default_backend_destroy_shadow_context 224 | void (*destroy_shadow_context)(backend_t *backend_data, 225 | struct backend_shadow_context *ctx); 226 | 227 | /// Create a shadow image based on the parameters. Resulting image should have a 228 | /// size of `width + radisu * 2` x `height + radius * 2`. Radius is set when the 229 | /// shadow context is created. 230 | /// Default implementation: default_backend_render_shadow 231 | /// 232 | /// Required. 233 | void *(*render_shadow)(backend_t *backend_data, int width, int height, 234 | struct backend_shadow_context *ctx, struct color color); 235 | 236 | /// Create a shadow by blurring a mask. `size` is the size of the blur. The 237 | /// backend can use whichever blur method is the fastest. The shadow produced 238 | /// shoule be consistent with `render_shadow`. 239 | /// 240 | /// Optional. 241 | void *(*shadow_from_mask)(backend_t *backend_data, void *mask, 242 | struct backend_shadow_context *ctx, struct color color); 243 | 244 | /// Create a mask image from region `reg`. This region can be used to create 245 | /// shadow, or used as a mask for composing. When used as a mask, it should mask 246 | /// out everything that is not inside the region used to create it. 247 | /// 248 | /// Image properties might be set on masks too, at least the INVERTED and 249 | /// CORNER_RADIUS properties must be supported. Inversion should invert the inside 250 | /// and outside of the mask. Corner radius should exclude the corners from the 251 | /// mask. Corner radius should be applied before the inversion. 252 | /// 253 | /// Required. 254 | void *(*make_mask)(backend_t *backend_data, geometry_t size, const region_t *reg); 255 | 256 | // ============ Resource management =========== 257 | 258 | /// Free resources associated with an image data structure 259 | void (*release_image)(backend_t *backend_data, void *img_data) attr_nonnull(1, 2); 260 | 261 | /// Create a shader object from a shader source. 262 | /// 263 | /// Optional 264 | void *(*create_shader)(backend_t *backend_data, const char *source)attr_nonnull(1, 2); 265 | 266 | /// Free a shader object. 267 | /// 268 | /// Required if create_shader is present. 269 | void (*destroy_shader)(backend_t *backend_data, void *shader) attr_nonnull(1, 2); 270 | 271 | // =========== Query =========== 272 | 273 | /// Get the attributes of a shader. 274 | /// 275 | /// Optional, Returns a bitmask of attributes, see `shader_attributes`. 276 | uint64_t (*get_shader_attributes)(backend_t *backend_data, void *shader) 277 | attr_nonnull(1, 2); 278 | 279 | /// Return if image is not completely opaque. 280 | /// 281 | /// This function is needed because some backend might change the content of the 282 | /// window (e.g. when using a custom shader with the glx backend), so only the 283 | /// backend knows if an image is transparent. 284 | bool (*is_image_transparent)(backend_t *backend_data, void *image_data) 285 | attr_nonnull(1, 2); 286 | 287 | /// Get the age of the buffer content we are currently rendering ontop 288 | /// of. The buffer that has just been `present`ed has a buffer age of 1. 289 | /// Everytime `present` is called, buffers get older. Return -1 if the 290 | /// buffer is empty. 291 | /// 292 | /// Optional 293 | int (*buffer_age)(backend_t *backend_data); 294 | 295 | /// The maximum number buffer_age might return. 296 | int max_buffer_age; 297 | 298 | // =========== Post-processing ============ 299 | 300 | /* TODO(yshui) Consider preserving the order of image ops. 301 | * Currently in both backends, the image ops are applied lazily when needed. 302 | * However neither backends preserve the order of image ops, they just applied all 303 | * pending lazy ops in a pre-determined fixed order, regardless in which order 304 | * they were originally applied. This might lead to inconsistencies.*/ 305 | 306 | /** 307 | * Change image properties 308 | * 309 | * @param backend_data backend data 310 | * @param prop the property to change 311 | * @param image_data an image data structure returned by the backend 312 | * @param args property value 313 | * @return whether the operation is successful 314 | */ 315 | bool (*set_image_property)(backend_t *backend_data, enum image_properties prop, 316 | void *image_data, void *args); 317 | 318 | /** 319 | * Manipulate an image. Image properties are untouched. 320 | * 321 | * @param backend_data backend data 322 | * @param op the operation to perform 323 | * @param image_data an image data structure returned by the backend 324 | * @param reg_op the clip region, define the part of the image to be 325 | * operated on. 326 | * @param reg_visible define the part of the image that will eventually 327 | * be visible on target. this is a hint to the backend 328 | * for optimization purposes. 329 | * @param args extra arguments, operation specific 330 | * @return whether the operation is successful 331 | */ 332 | bool (*image_op)(backend_t *backend_data, enum image_operations op, void *image_data, 333 | const region_t *reg_op, const region_t *reg_visible, void *args); 334 | 335 | /// Create another instance of the `image_data`. All `image_op` and 336 | /// `set_image_property` calls on the returned image should not affect the 337 | /// original image 338 | void *(*clone_image)(backend_t *base, const void *image_data, 339 | const region_t *reg_visible); 340 | 341 | /// Create a blur context that can be used to call `blur` 342 | void *(*create_blur_context)(backend_t *base, enum blur_method, void *args); 343 | /// Destroy a blur context 344 | void (*destroy_blur_context)(backend_t *base, void *ctx); 345 | /// Get how many pixels outside of the blur area is needed for blur 346 | void (*get_blur_size)(void *blur_context, int *width, int *height); 347 | 348 | // =========== Hooks ============ 349 | /// Let the backend hook into the event handling queue 350 | /// Not implemented yet 351 | void (*set_ready_callback)(backend_t *, backend_ready_callback_t cb); 352 | /// Called right after the core has handled its events. 353 | /// Not implemented yet 354 | void (*handle_events)(backend_t *); 355 | // =========== Misc ============ 356 | /// Return the driver that is been used by the backend 357 | enum driver (*detect_driver)(backend_t *backend_data); 358 | 359 | void (*diagnostics)(backend_t *backend_data); 360 | 361 | enum device_status (*device_status)(backend_t *backend_data); 362 | }; 363 | 364 | extern struct backend_operations *backend_list[]; 365 | 366 | void paint_all_new(session_t *ps, struct managed_win *const t, bool ignore_damage) 367 | attr_nonnull(1); 368 | -------------------------------------------------------------------------------- /src/backend/backend_common.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "backend.h" 11 | #include "config.h" 12 | #include "region.h" 13 | 14 | typedef struct session session_t; 15 | typedef struct win win; 16 | typedef struct conv conv; 17 | typedef struct backend_base backend_t; 18 | struct backend_operations; 19 | 20 | struct dual_kawase_params { 21 | /// Number of downsample passes 22 | int iterations; 23 | /// Pixel offset for down- and upsample 24 | float offset; 25 | /// Save area around blur target (@ref resize_width, @ref resize_height) 26 | int expand; 27 | }; 28 | 29 | struct backend_image_inner_base { 30 | int refcount; 31 | bool has_alpha; 32 | }; 33 | 34 | struct backend_image { 35 | // Backend dependent inner image data 36 | struct backend_image_inner_base *inner; 37 | double opacity; 38 | double dim; 39 | double max_brightness; 40 | double corner_radius; 41 | // Effective size of the image 42 | int ewidth, eheight; 43 | bool color_inverted; 44 | int border_width; 45 | }; 46 | 47 | bool build_shadow(xcb_connection_t *, xcb_drawable_t, double opacity, int width, 48 | int height, const conv *kernel, xcb_render_picture_t shadow_pixel, 49 | xcb_pixmap_t *pixmap, xcb_render_picture_t *pict); 50 | 51 | xcb_render_picture_t solid_picture(xcb_connection_t *, xcb_drawable_t, bool argb, 52 | double a, double r, double g, double b); 53 | 54 | xcb_image_t * 55 | make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, int height); 56 | 57 | /// The default implementation of `is_win_transparent`, it simply looks at win::mode. So 58 | /// this is not suitable for backends that alter the content of windows 59 | bool default_is_win_transparent(void *, win *, void *); 60 | 61 | /// The default implementation of `is_frame_transparent`, it uses win::frame_opacity. Same 62 | /// caveat as `default_is_win_transparent` applies. 63 | bool default_is_frame_transparent(void *, win *, void *); 64 | 65 | void *default_backend_render_shadow(backend_t *backend_data, int width, int height, 66 | struct backend_shadow_context *sctx, struct color color); 67 | 68 | /// Implement `render_shadow` with `shadow_from_mask`. 69 | void * 70 | backend_render_shadow_from_mask(backend_t *backend_data, int width, int height, 71 | struct backend_shadow_context *sctx, struct color color); 72 | struct backend_shadow_context * 73 | default_create_shadow_context(backend_t *backend_data, double radius); 74 | 75 | void default_destroy_shadow_context(backend_t *backend_data, 76 | struct backend_shadow_context *sctx); 77 | 78 | void init_backend_base(struct backend_base *base, session_t *ps); 79 | 80 | struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count); 81 | struct dual_kawase_params *generate_dual_kawase_params(void *args); 82 | 83 | void *default_clone_image(backend_t *base, const void *image_data, const region_t *reg); 84 | void *default_resize_image(backend_t *base, const void *image_data, uint16_t desired_width, 85 | uint16_t desired_height, const region_t *reg); 86 | bool default_is_image_transparent(backend_t *base attr_unused, void *image_data); 87 | bool default_set_image_property(backend_t *base attr_unused, enum image_properties op, 88 | void *image_data, void *arg); 89 | struct backend_image *default_new_backend_image(int w, int h); 90 | -------------------------------------------------------------------------------- /src/backend/driver.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "backend/backend.h" 10 | #include "backend/driver.h" 11 | #include "common.h" 12 | #include "compiler.h" 13 | #include "log.h" 14 | 15 | /// Apply driver specified global workarounds. It's safe to call this multiple times. 16 | void apply_driver_workarounds(struct session *ps, enum driver driver) { 17 | if (driver & DRIVER_NVIDIA) { 18 | // setenv("__GL_YIELD", "usleep", true); 19 | setenv("__GL_MaxFramesAllowed", "1", true); 20 | ps->o.xrender_sync_fence = true; 21 | } 22 | } 23 | 24 | enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) { 25 | enum driver ret = 0; 26 | // First we try doing backend agnostic detection using RANDR 27 | // There's no way to query the X server about what driver is loaded, so RANDR is 28 | // our best shot. 29 | auto randr_version = xcb_randr_query_version_reply( 30 | c, xcb_randr_query_version(c, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), 31 | NULL); 32 | if (randr_version && 33 | (randr_version->major_version > 1 || randr_version->minor_version >= 4)) { 34 | auto r = xcb_randr_get_providers_reply( 35 | c, xcb_randr_get_providers(c, window), NULL); 36 | if (r == NULL) { 37 | log_warn("Failed to get RANDR providers"); 38 | free(randr_version); 39 | return 0; 40 | } 41 | 42 | auto providers = xcb_randr_get_providers_providers(r); 43 | for (auto i = 0; i < xcb_randr_get_providers_providers_length(r); i++) { 44 | auto r2 = xcb_randr_get_provider_info_reply( 45 | c, xcb_randr_get_provider_info(c, providers[i], r->timestamp), NULL); 46 | if (r2 == NULL) { 47 | continue; 48 | } 49 | if (r2->num_outputs == 0) { 50 | free(r2); 51 | continue; 52 | } 53 | 54 | auto name_len = xcb_randr_get_provider_info_name_length(r2); 55 | assert(name_len >= 0); 56 | auto name = 57 | strndup(xcb_randr_get_provider_info_name(r2), (size_t)name_len); 58 | if (strcasestr(name, "modesetting") != NULL) { 59 | ret |= DRIVER_MODESETTING; 60 | } else if (strcasestr(name, "Radeon") != NULL) { 61 | // Be conservative, add both radeon drivers 62 | ret |= DRIVER_AMDGPU | DRIVER_RADEON; 63 | } else if (strcasestr(name, "NVIDIA") != NULL) { 64 | ret |= DRIVER_NVIDIA; 65 | } else if (strcasestr(name, "nouveau") != NULL) { 66 | ret |= DRIVER_NOUVEAU; 67 | } else if (strcasestr(name, "Intel") != NULL) { 68 | ret |= DRIVER_INTEL; 69 | } 70 | free(name); 71 | free(r2); 72 | } 73 | free(r); 74 | } 75 | free(randr_version); 76 | 77 | // If the backend supports driver detection, use that as well 78 | if (backend_data && backend_data->ops->detect_driver) { 79 | ret |= backend_data->ops->detect_driver(backend_data); 80 | } 81 | return ret; 82 | } 83 | -------------------------------------------------------------------------------- /src/backend/driver.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "utils.h" 11 | 12 | struct session; 13 | struct backend_base; 14 | 15 | // A list of known driver quirks: 16 | // * NVIDIA driver doesn't like seeing the same pixmap under different 17 | // ids, so avoid naming the pixmap again when it didn't actually change. 18 | 19 | /// A list of possible drivers. 20 | /// The driver situation is a bit complicated. There are two drivers we care about: the 21 | /// DDX, and the OpenGL driver. They are usually paired, but not always, since there is 22 | /// also the generic modesetting driver. 23 | /// This enum represents _both_ drivers. 24 | enum driver { 25 | DRIVER_AMDGPU = 1, // AMDGPU for DDX, radeonsi for OpenGL 26 | DRIVER_RADEON = 2, // ATI for DDX, mesa r600 for OpenGL 27 | DRIVER_FGLRX = 4, 28 | DRIVER_NVIDIA = 8, 29 | DRIVER_NOUVEAU = 16, 30 | DRIVER_INTEL = 32, 31 | DRIVER_MODESETTING = 64, 32 | }; 33 | 34 | static const char *driver_names[] = { 35 | "AMDGPU", "Radeon", "fglrx", "NVIDIA", "nouveau", "Intel", "modesetting", 36 | }; 37 | 38 | /// Return a list of all drivers currently in use by the X server. 39 | /// Note, this is a best-effort test, so no guarantee all drivers will be detected. 40 | enum driver detect_driver(xcb_connection_t *, struct backend_base *, xcb_window_t); 41 | 42 | /// Apply driver specified global workarounds. It's safe to call this multiple times. 43 | void apply_driver_workarounds(struct session *ps, enum driver); 44 | 45 | // Print driver names to stdout, for diagnostics 46 | static inline void print_drivers(enum driver drivers) { 47 | const char *seen_drivers[ARR_SIZE(driver_names)]; 48 | int driver_count = 0; 49 | for (size_t i = 0; i < ARR_SIZE(driver_names); i++) { 50 | if (drivers & (1ul << i)) { 51 | seen_drivers[driver_count++] = driver_names[i]; 52 | } 53 | } 54 | 55 | if (driver_count > 0) { 56 | printf("%s", seen_drivers[0]); 57 | for (int i = 1; i < driver_count; i++) { 58 | printf(", %s", seen_drivers[i]); 59 | } 60 | } 61 | printf("\n"); 62 | } 63 | -------------------------------------------------------------------------------- /src/backend/dummy/dummy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "backend/backend.h" 5 | #include "backend/backend_common.h" 6 | #include "common.h" 7 | #include "compiler.h" 8 | #include "config.h" 9 | #include "log.h" 10 | #include "region.h" 11 | #include "types.h" 12 | #include "uthash_extra.h" 13 | #include "utils.h" 14 | #include "x.h" 15 | 16 | struct dummy_image { 17 | xcb_pixmap_t pixmap; 18 | bool transparent; 19 | int *refcount; 20 | UT_hash_handle hh; 21 | }; 22 | 23 | struct dummy_data { 24 | struct backend_base base; 25 | struct dummy_image *images; 26 | 27 | struct backend_image mask; 28 | }; 29 | 30 | struct backend_base *dummy_init(struct session *ps attr_unused) { 31 | auto ret = (struct backend_base *)ccalloc(1, struct dummy_data); 32 | ret->c = ps->c; 33 | ret->loop = ps->loop; 34 | ret->root = ps->root; 35 | ret->busy = false; 36 | return ret; 37 | } 38 | 39 | void dummy_deinit(struct backend_base *data) { 40 | auto dummy = (struct dummy_data *)data; 41 | HASH_ITER2(dummy->images, img) { 42 | log_warn("Backend image for pixmap %#010x is not freed", img->pixmap); 43 | HASH_DEL(dummy->images, img); 44 | free(img->refcount); 45 | free(img); 46 | } 47 | free(dummy); 48 | } 49 | 50 | static void dummy_check_image(struct backend_base *base, const struct dummy_image *img) { 51 | auto dummy = (struct dummy_data *)base; 52 | if (img == (struct dummy_image *)&dummy->mask) { 53 | return; 54 | } 55 | struct dummy_image *tmp = NULL; 56 | HASH_FIND_INT(dummy->images, &img->pixmap, tmp); 57 | if (!tmp) { 58 | log_warn("Using an invalid (possibly freed) image"); 59 | assert(false); 60 | } 61 | assert(*tmp->refcount > 0); 62 | } 63 | 64 | void dummy_compose(struct backend_base *base, void *image, coord_t dst attr_unused, 65 | void *mask attr_unused, coord_t mask_dst attr_unused, 66 | const region_t *reg_paint attr_unused, 67 | const region_t *reg_visible attr_unused) { 68 | auto dummy attr_unused = (struct dummy_data *)base; 69 | dummy_check_image(base, image); 70 | assert(mask == NULL || mask == &dummy->mask); 71 | } 72 | 73 | void dummy_fill(struct backend_base *backend_data attr_unused, struct color c attr_unused, 74 | const region_t *clip attr_unused) { 75 | } 76 | 77 | bool dummy_blur(struct backend_base *backend_data attr_unused, double opacity attr_unused, 78 | void *blur_ctx attr_unused, void *mask attr_unused, 79 | coord_t mask_dst attr_unused, const region_t *reg_blur attr_unused, 80 | const region_t *reg_visible attr_unused) { 81 | return true; 82 | } 83 | 84 | void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap, 85 | struct xvisual_info fmt, bool owned attr_unused) { 86 | auto dummy = (struct dummy_data *)base; 87 | struct dummy_image *img = NULL; 88 | HASH_FIND_INT(dummy->images, &pixmap, img); 89 | if (img) { 90 | (*img->refcount)++; 91 | return img; 92 | } 93 | 94 | img = ccalloc(1, struct dummy_image); 95 | img->pixmap = pixmap; 96 | img->transparent = fmt.alpha_size != 0; 97 | img->refcount = ccalloc(1, int); 98 | *img->refcount = 1; 99 | 100 | HASH_ADD_INT(dummy->images, pixmap, img); 101 | return (void *)img; 102 | } 103 | 104 | void dummy_release_image(backend_t *base, void *image) { 105 | auto dummy = (struct dummy_data *)base; 106 | if (image == &dummy->mask) { 107 | return; 108 | } 109 | auto img = (struct dummy_image *)image; 110 | assert(*img->refcount > 0); 111 | (*img->refcount)--; 112 | if (*img->refcount == 0) { 113 | HASH_DEL(dummy->images, img); 114 | free(img->refcount); 115 | free(img); 116 | } 117 | } 118 | 119 | bool dummy_is_image_transparent(struct backend_base *base, void *image) { 120 | auto img = (struct dummy_image *)image; 121 | dummy_check_image(base, img); 122 | return img->transparent; 123 | } 124 | 125 | int dummy_buffer_age(struct backend_base *base attr_unused) { 126 | return 2; 127 | } 128 | 129 | bool dummy_image_op(struct backend_base *base, enum image_operations op attr_unused, 130 | void *image, const region_t *reg_op attr_unused, 131 | const region_t *reg_visible attr_unused, void *args attr_unused) { 132 | dummy_check_image(base, image); 133 | return true; 134 | } 135 | 136 | void *dummy_make_mask(struct backend_base *base, geometry_t size attr_unused, 137 | const region_t *reg attr_unused) { 138 | return &(((struct dummy_data *)base)->mask); 139 | } 140 | 141 | bool dummy_set_image_property(struct backend_base *base, enum image_properties prop attr_unused, 142 | void *image, void *arg attr_unused) { 143 | dummy_check_image(base, image); 144 | return true; 145 | } 146 | 147 | void *dummy_clone_image(struct backend_base *base, const void *image, 148 | const region_t *reg_visible attr_unused) { 149 | auto img = (const struct dummy_image *)image; 150 | dummy_check_image(base, img); 151 | (*img->refcount)++; 152 | return (void *)img; 153 | } 154 | 155 | void *dummy_create_blur_context(struct backend_base *base attr_unused, 156 | enum blur_method method attr_unused, void *args attr_unused) { 157 | static int dummy_context; 158 | return &dummy_context; 159 | } 160 | 161 | void dummy_destroy_blur_context(struct backend_base *base attr_unused, void *ctx attr_unused) { 162 | } 163 | 164 | void dummy_get_blur_size(void *ctx attr_unused, int *width, int *height) { 165 | // These numbers are arbitrary, to make sure the reisze_region code path is 166 | // covered. 167 | *width = 5; 168 | *height = 5; 169 | } 170 | 171 | struct backend_operations dummy_ops = { 172 | .init = dummy_init, 173 | .deinit = dummy_deinit, 174 | .compose = dummy_compose, 175 | .fill = dummy_fill, 176 | .blur = dummy_blur, 177 | .bind_pixmap = dummy_bind_pixmap, 178 | .create_shadow_context = default_create_shadow_context, 179 | .destroy_shadow_context = default_destroy_shadow_context, 180 | .render_shadow = default_backend_render_shadow, 181 | .make_mask = dummy_make_mask, 182 | .release_image = dummy_release_image, 183 | .is_image_transparent = dummy_is_image_transparent, 184 | .buffer_age = dummy_buffer_age, 185 | .max_buffer_age = 5, 186 | 187 | .image_op = dummy_image_op, 188 | .clone_image = dummy_clone_image, 189 | .set_image_property = dummy_set_image_property, 190 | .create_blur_context = dummy_create_blur_context, 191 | .destroy_blur_context = dummy_destroy_blur_context, 192 | .get_blur_size = dummy_get_blur_size, 193 | 194 | }; 195 | -------------------------------------------------------------------------------- /src/backend/gl/egl.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | #pragma once 4 | #include 5 | // Older version of glx.h defines function prototypes for these extensions... 6 | // Rename them to avoid conflicts 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "compiler.h" 15 | #include "log.h" 16 | #include "utils.h" 17 | #include "x.h" 18 | 19 | struct eglext_info { 20 | bool initialized; 21 | bool has_EGL_MESA_query_driver; 22 | bool has_EGL_EXT_buffer_age; 23 | bool has_EGL_EXT_create_context_robustness; 24 | bool has_EGL_KHR_image_pixmap; 25 | }; 26 | 27 | extern struct eglext_info eglext; 28 | 29 | #ifdef EGL_MESA_query_driver 30 | extern PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName; 31 | #endif 32 | 33 | void eglext_init(EGLDisplay); 34 | -------------------------------------------------------------------------------- /src/backend/gl/gl_common.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | #pragma once 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "backend/backend.h" 10 | #include "log.h" 11 | #include "region.h" 12 | 13 | #define CASESTRRET(s) \ 14 | case s: return #s 15 | struct gl_blur_context; 16 | 17 | static inline GLint glGetUniformLocationChecked(GLuint p, const char *name) { 18 | auto ret = glGetUniformLocation(p, name); 19 | if (ret < 0) { 20 | log_info("Failed to get location of uniform '%s'. This is normal when " 21 | "using custom shaders.", 22 | name); 23 | } 24 | return ret; 25 | } 26 | 27 | #define bind_uniform(shader, uniform) \ 28 | (shader)->uniform_##uniform = glGetUniformLocationChecked((shader)->prog, #uniform) 29 | 30 | // Program and uniforms for window shader 31 | typedef struct { 32 | UT_hash_handle hh; 33 | uint32_t id; 34 | GLuint prog; 35 | GLint uniform_opacity; 36 | GLint uniform_invert_color; 37 | GLint uniform_tex; 38 | GLint uniform_effective_size; 39 | GLint uniform_dim; 40 | GLint uniform_brightness; 41 | GLint uniform_max_brightness; 42 | GLint uniform_corner_radius; 43 | GLint uniform_border_width; 44 | GLint uniform_time; 45 | 46 | GLint uniform_mask_tex; 47 | GLint uniform_mask_offset; 48 | GLint uniform_mask_corner_radius; 49 | GLint uniform_mask_inverted; 50 | } gl_win_shader_t; 51 | 52 | // Program and uniforms for brightness shader 53 | typedef struct { 54 | GLuint prog; 55 | } gl_brightness_shader_t; 56 | 57 | typedef struct { 58 | GLuint prog; 59 | GLint uniform_color; 60 | } gl_shadow_shader_t; 61 | 62 | // Program and uniforms for blur shader 63 | typedef struct { 64 | GLuint prog; 65 | GLint uniform_pixel_norm; 66 | GLint uniform_opacity; 67 | GLint texorig_loc; 68 | GLint scale_loc; 69 | 70 | GLint uniform_mask_tex; 71 | GLint uniform_mask_offset; 72 | GLint uniform_mask_corner_radius; 73 | GLint uniform_mask_inverted; 74 | } gl_blur_shader_t; 75 | 76 | typedef struct { 77 | GLuint prog; 78 | GLint color_loc; 79 | } gl_fill_shader_t; 80 | 81 | /// @brief Wrapper of a binded GLX texture. 82 | struct gl_texture { 83 | int refcount; 84 | bool has_alpha; 85 | GLuint texture; 86 | int width, height; 87 | bool y_inverted; 88 | 89 | // Textures for auxiliary uses. 90 | GLuint auxiliary_texture[2]; 91 | gl_win_shader_t *shader; 92 | void *user_data; 93 | }; 94 | 95 | struct gl_data { 96 | backend_t base; 97 | // If we are using proprietary NVIDIA driver 98 | bool is_nvidia; 99 | // If ARB_robustness extension is present 100 | bool has_robustness; 101 | // If EXT_EGL_image_storage extension is present 102 | bool has_egl_image_storage; 103 | // Height and width of the root window 104 | int height, width; 105 | // Hash-table of window shaders 106 | gl_win_shader_t *default_shader; 107 | gl_brightness_shader_t brightness_shader; 108 | gl_fill_shader_t fill_shader; 109 | gl_shadow_shader_t shadow_shader; 110 | GLuint back_texture, back_fbo; 111 | GLuint present_prog; 112 | 113 | GLuint default_mask_texture; 114 | 115 | /// Called when an gl_texture is decoupled from the texture it refers. Returns 116 | /// the decoupled user_data 117 | void *(*decouple_texture_user_data)(backend_t *base, void *user_data); 118 | 119 | /// Release the user data attached to a gl_texture 120 | void (*release_user_data)(backend_t *base, struct gl_texture *); 121 | 122 | struct log_target *logger; 123 | }; 124 | 125 | typedef struct session session_t; 126 | 127 | #define GL_PROG_MAIN_INIT \ 128 | { .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, .unifm_tex = -1, } 129 | 130 | void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst, 131 | int extent_height, int texture_height, int root_height, 132 | bool y_inverted, GLint *coord, GLuint *indices); 133 | 134 | GLuint gl_create_shader(GLenum shader_type, const char *shader_str); 135 | GLuint gl_create_program(const GLuint *const shaders, int nshaders); 136 | GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str); 137 | GLuint gl_create_program_from_strv(const char **vert_shaders, const char **frag_shaders); 138 | void *gl_create_window_shader(backend_t *backend_data, const char *source); 139 | void gl_destroy_window_shader(backend_t *backend_data, void *shader); 140 | uint64_t gl_get_shader_attributes(backend_t *backend_data, void *shader); 141 | bool gl_set_image_property(backend_t *backend_data, enum image_properties prop, 142 | void *image_data, void *args); 143 | 144 | /** 145 | * @brief Render a region with texture data. 146 | */ 147 | void gl_compose(backend_t *, void *image_data, coord_t image_dst, void *mask, 148 | coord_t mask_dst, const region_t *reg_tgt, const region_t *reg_visible); 149 | 150 | void gl_resize(struct gl_data *, int width, int height); 151 | 152 | bool gl_init(struct gl_data *gd, session_t *); 153 | void gl_deinit(struct gl_data *gd); 154 | 155 | GLuint gl_new_texture(GLenum target); 156 | 157 | bool gl_image_op(backend_t *base, enum image_operations op, void *image_data, 158 | const region_t *reg_op, const region_t *reg_visible, void *arg); 159 | 160 | void gl_release_image(backend_t *base, void *image_data); 161 | void *gl_make_mask(backend_t *base, geometry_t size, const region_t *reg); 162 | 163 | void *gl_clone(backend_t *base, const void *image_data, const region_t *reg_visible); 164 | 165 | bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst, 166 | const region_t *reg_blur, const region_t *reg_visible); 167 | bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, 168 | coord_t mask_dst, const region_t *reg_blur, 169 | const region_t *reg_visible attr_unused, GLuint source_texture, 170 | geometry_t source_size, GLuint target_fbo, GLuint default_mask); 171 | void *gl_create_blur_context(backend_t *base, enum blur_method, void *args); 172 | void gl_destroy_blur_context(backend_t *base, void *ctx); 173 | struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double radius); 174 | void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shadow_context *ctx); 175 | void *gl_shadow_from_mask(backend_t *base, void *mask, 176 | struct backend_shadow_context *sctx, struct color color); 177 | void gl_get_blur_size(void *blur_context, int *width, int *height); 178 | 179 | void gl_fill(backend_t *base, struct color, const region_t *clip); 180 | 181 | void gl_present(backend_t *base, const region_t *); 182 | bool gl_read_pixel(backend_t *base, void *image_data, int x, int y, struct color *output); 183 | enum device_status gl_device_status(backend_t *base); 184 | 185 | static inline void gl_delete_texture(GLuint texture) { 186 | glDeleteTextures(1, &texture); 187 | } 188 | 189 | /** 190 | * Get a textual representation of an OpenGL error. 191 | */ 192 | static inline const char *gl_get_err_str(GLenum err) { 193 | switch (err) { 194 | CASESTRRET(GL_NO_ERROR); 195 | CASESTRRET(GL_INVALID_ENUM); 196 | CASESTRRET(GL_INVALID_VALUE); 197 | CASESTRRET(GL_INVALID_OPERATION); 198 | // CASESTRRET(GL_INVALID_FRAMEBUFFER_OPERATION); 199 | CASESTRRET(GL_OUT_OF_MEMORY); 200 | CASESTRRET(GL_STACK_UNDERFLOW); 201 | CASESTRRET(GL_STACK_OVERFLOW); 202 | CASESTRRET(GL_FRAMEBUFFER_UNDEFINED); 203 | CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT); 204 | CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT); 205 | CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER); 206 | CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER); 207 | CASESTRRET(GL_FRAMEBUFFER_UNSUPPORTED); 208 | CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE); 209 | CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS); 210 | } 211 | return NULL; 212 | } 213 | 214 | /** 215 | * Check for GLX error. 216 | * 217 | * http://blog.nobel-joergensen.com/2013/01/29/debugging-opengl-using-glgeterror/ 218 | */ 219 | 220 | // Hide GL Errors Temporarily. -Allusive 221 | 222 | // const char *func, int line 223 | 224 | static inline void gl_check_err_(const char *func, int line) { 225 | GLenum err = GL_NO_ERROR; 226 | 227 | while (GL_NO_ERROR != (err = glGetError())) { 228 | const char *errtext = gl_get_err_str(err); 229 | if (errtext) { 230 | log_printf(tls_logger, LOG_LEVEL_ERROR, func, 231 | "GL error at line %d: %s", line, errtext); 232 | } else if (err != 1286) { 233 | log_printf(tls_logger, LOG_LEVEL_ERROR, func, 234 | "GL error at line %d: %d", line, err); 235 | } 236 | } 237 | } 238 | 239 | static inline void gl_clear_err(void) { 240 | while (glGetError() != GL_NO_ERROR) 241 | ; 242 | } 243 | 244 | #define gl_check_err() gl_check_err_(__func__, __LINE__) 245 | 246 | /** 247 | * Check for GL framebuffer completeness. 248 | */ 249 | static inline bool gl_check_fb_complete_(const char *func, int line, GLenum fb) { 250 | GLenum status = glCheckFramebufferStatus(fb); 251 | 252 | if (status == GL_FRAMEBUFFER_COMPLETE) { 253 | return true; 254 | } 255 | 256 | const char *stattext = gl_get_err_str(status); 257 | if (stattext) { 258 | log_printf(tls_logger, LOG_LEVEL_ERROR, func, 259 | "Framebuffer attachment failed at line %d: %s", line, stattext); 260 | } else { 261 | log_printf(tls_logger, LOG_LEVEL_ERROR, func, 262 | "Framebuffer attachment failed at line %d: %d", line, status); 263 | } 264 | 265 | return false; 266 | } 267 | 268 | #define gl_check_fb_complete(fb) gl_check_fb_complete_(__func__, __LINE__, (fb)) 269 | 270 | /** 271 | * Check if a GLX extension exists. 272 | */ 273 | static inline bool gl_has_extension(const char *ext) { 274 | int nexts = 0; 275 | glGetIntegerv(GL_NUM_EXTENSIONS, &nexts); 276 | for (int i = 0; i < nexts || !nexts; i++) { 277 | const char *exti = (const char *)glGetStringi(GL_EXTENSIONS, (GLuint)i); 278 | if (exti == NULL) { 279 | break; 280 | } 281 | if (strcmp(ext, exti) == 0) { 282 | return true; 283 | } 284 | } 285 | gl_clear_err(); 286 | // log_info("Missing GL extension %s.", ext); 287 | return false; 288 | } 289 | 290 | static const GLuint vert_coord_loc = 0; 291 | static const GLuint vert_in_texcoord_loc = 1; 292 | 293 | #define GLSL(version, ...) "#version " #version "\n" #__VA_ARGS__ 294 | #define QUOTE(...) #__VA_ARGS__ 295 | 296 | extern const char vertex_shader[], copy_with_mask_frag[], masking_glsl[], dummy_frag[], 297 | fill_frag[], fill_vert[], interpolating_frag[], interpolating_vert[], win_shader_glsl[], 298 | win_shader_default[], present_vertex_shader[], shadow_colorization_frag[]; 299 | -------------------------------------------------------------------------------- /src/backend/gl/glx.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | #pragma once 4 | #include 5 | // Older version of glx.h defines function prototypes for these extensions... 6 | // Rename them to avoid conflicts 7 | #define glXSwapIntervalMESA glXSwapIntervalMESA_ 8 | #define glXBindTexImageEXT glXBindTexImageEXT_ 9 | #define glXReleaseTexImageEXT glXReleaseTexImageEXT 10 | #include 11 | #undef glXSwapIntervalMESA 12 | #undef glXBindTexImageEXT 13 | #undef glXReleaseTexImageEXT 14 | #include 15 | #include 16 | #include 17 | 18 | #include "log.h" 19 | #include "compiler.h" 20 | #include "utils.h" 21 | #include "x.h" 22 | 23 | struct glx_fbconfig_info { 24 | GLXFBConfig cfg; 25 | int texture_tgts; 26 | int texture_fmt; 27 | int y_inverted; 28 | }; 29 | 30 | /// The search criteria for glx_find_fbconfig 31 | struct glx_fbconfig_criteria { 32 | /// Bit width of the red component 33 | int red_size; 34 | /// Bit width of the green component 35 | int green_size; 36 | /// Bit width of the blue component 37 | int blue_size; 38 | /// Bit width of the alpha component 39 | int alpha_size; 40 | /// The depth of X visual 41 | int visual_depth; 42 | }; 43 | 44 | struct glx_fbconfig_info *glx_find_fbconfig(Display *, int screen, struct xvisual_info); 45 | 46 | 47 | struct glxext_info { 48 | bool initialized; 49 | bool has_GLX_SGI_video_sync; 50 | bool has_GLX_SGI_swap_control; 51 | bool has_GLX_OML_sync_control; 52 | bool has_GLX_MESA_swap_control; 53 | bool has_GLX_EXT_swap_control; 54 | bool has_GLX_EXT_texture_from_pixmap; 55 | bool has_GLX_ARB_create_context; 56 | bool has_GLX_EXT_buffer_age; 57 | bool has_GLX_MESA_query_renderer; 58 | bool has_GLX_ARB_create_context_robustness; 59 | }; 60 | 61 | extern struct glxext_info glxext; 62 | 63 | extern PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI; 64 | extern PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI; 65 | extern PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML; 66 | extern PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML; 67 | extern PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT; 68 | extern PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI; 69 | extern PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA; 70 | extern PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT; 71 | extern PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT; 72 | extern PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB; 73 | 74 | #ifdef GLX_MESA_query_renderer 75 | extern PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA; 76 | #endif 77 | 78 | void glxext_init(Display *, int screen); 79 | -------------------------------------------------------------------------------- /src/backend/gl/shaders.c: -------------------------------------------------------------------------------- 1 | #include "gl_common.h" 2 | 3 | // clang-format off 4 | const char dummy_frag[] = GLSL(330, 5 | uniform sampler2D tex; 6 | in vec2 texcoord; 7 | void main() { 8 | gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0); 9 | } 10 | ); 11 | 12 | const char copy_with_mask_frag[] = GLSL(330, 13 | uniform sampler2D tex; 14 | in vec2 texcoord; 15 | float mask_factor(); 16 | void main() { 17 | gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0) * mask_factor(); 18 | } 19 | ); 20 | 21 | const char fill_frag[] = GLSL(330, 22 | uniform vec4 color; 23 | void main() { 24 | gl_FragColor = color; 25 | } 26 | ); 27 | 28 | const char fill_vert[] = GLSL(330, 29 | layout(location = 0) in vec2 in_coord; 30 | uniform mat4 projection; 31 | void main() { 32 | gl_Position = projection * vec4(in_coord, 0, 1); 33 | } 34 | ); 35 | 36 | const char interpolating_frag[] = GLSL(330, 37 | uniform sampler2D tex; 38 | in vec2 texcoord; 39 | void main() { 40 | gl_FragColor = vec4(texture2D(tex, vec2(texcoord.xy), 0).rgb, 1); 41 | } 42 | ); 43 | 44 | const char interpolating_vert[] = GLSL(330, 45 | uniform mat4 projection; 46 | uniform vec2 texsize; 47 | layout(location = 0) in vec2 in_coord; 48 | layout(location = 1) in vec2 in_texcoord; 49 | out vec2 texcoord; 50 | void main() { 51 | gl_Position = projection * vec4(in_coord, 0, 1); 52 | texcoord = in_texcoord / texsize; 53 | } 54 | ); 55 | const char masking_glsl[] = GLSL(330, 56 | uniform sampler2D mask_tex; 57 | uniform vec2 mask_offset; 58 | uniform float mask_corner_radius; 59 | uniform bool mask_inverted; 60 | in vec2 texcoord; 61 | float mask_rectangle_sdf(vec2 point, vec2 half_size) { 62 | vec2 d = abs(point) - half_size; 63 | return length(max(d, 0.0)); 64 | } 65 | float mask_factor() { 66 | vec2 mask_size = textureSize(mask_tex, 0); 67 | vec2 maskcoord = texcoord - mask_offset; 68 | vec4 mask = texture2D(mask_tex, maskcoord / mask_size); 69 | if (mask_corner_radius != 0) { 70 | vec2 inner_size = mask_size - vec2(mask_corner_radius) * 2.0f; 71 | float dist = mask_rectangle_sdf(maskcoord - mask_size / 2.0f, 72 | inner_size / 2.0f) - mask_corner_radius; 73 | if (dist > 0.0f) { 74 | mask.r *= (1.0f - clamp(dist, 0.0f, 1.0f)); 75 | } 76 | } 77 | if (mask_inverted) { 78 | mask.rgb = 1.0 - mask.rgb; 79 | } 80 | return mask.r; 81 | } 82 | ); 83 | const char win_shader_glsl[] = GLSL(330, 84 | uniform float opacity; 85 | uniform float dim; 86 | uniform float corner_radius; 87 | uniform float border_width; 88 | uniform bool invert_color; 89 | in vec2 texcoord; 90 | uniform sampler2D tex; 91 | uniform vec2 effective_size; 92 | uniform sampler2D brightness; 93 | uniform float max_brightness; 94 | // Signed distance field for rectangle center at (0, 0), with size of 95 | // half_size * 2 96 | float rectangle_sdf(vec2 point, vec2 half_size) { 97 | vec2 d = abs(point) - half_size; 98 | return length(max(d, 0.0)); 99 | } 100 | 101 | vec4 default_post_processing(vec4 c) { 102 | vec4 border_color = texture(tex, vec2(0.0, 0.5)); 103 | if (invert_color) { 104 | c = vec4(c.aaa - c.rgb, c.a); 105 | border_color = vec4(border_color.aaa - border_color.rgb, border_color.a); 106 | } 107 | c = vec4(c.rgb * (1.0 - dim), c.a) * opacity; 108 | border_color = vec4(border_color.rgb * (1.0 - dim), border_color.a) * opacity; 109 | 110 | vec3 rgb_brightness = texelFetch(brightness, ivec2(0, 0), 0).rgb; 111 | // Ref: https://en.wikipedia.org/wiki/Relative_luminance 112 | float brightness = rgb_brightness.r * 0.21 + 113 | rgb_brightness.g * 0.72 + 114 | rgb_brightness.b * 0.07; 115 | if (brightness > max_brightness) { 116 | c.rgb = c.rgb * (max_brightness / brightness); 117 | border_color.rgb = border_color.rgb * (max_brightness / brightness); 118 | } 119 | 120 | // Rim color is the color of the outer rim of the window, if there is no 121 | // border, it's the color of the window itself, otherwise it's the border. 122 | // Using mix() to avoid a branch here. 123 | vec4 rim_color = mix(c, border_color, clamp(border_width, 0.0f, 1.0f)); 124 | 125 | vec2 outer_size = effective_size; 126 | vec2 inner_size = outer_size - vec2(corner_radius) * 2.0f; 127 | float rect_distance = rectangle_sdf(texcoord - outer_size / 2.0f, 128 | inner_size / 2.0f) - corner_radius; 129 | if (rect_distance > 0.0f) { 130 | c = (1.0f - clamp(rect_distance, 0.0f, 1.0f)) * rim_color; 131 | } else { 132 | float factor = clamp(rect_distance + border_width, 0.0f, 1.0f); 133 | c = (1.0f - factor) * c + factor * border_color; 134 | } 135 | 136 | return c; 137 | } 138 | 139 | vec4 window_shader(); 140 | float mask_factor(); 141 | 142 | void main() { 143 | gl_FragColor = window_shader() * mask_factor(); 144 | } 145 | ); 146 | 147 | const char win_shader_default[] = GLSL(330, 148 | in vec2 texcoord; 149 | uniform sampler2D tex; 150 | vec4 default_post_processing(vec4 c); 151 | vec4 window_shader() { 152 | vec2 texsize = textureSize(tex, 0); 153 | vec4 c = texture2D(tex, texcoord / texsize, 0); 154 | return default_post_processing(c); 155 | } 156 | ); 157 | 158 | const char present_vertex_shader[] = GLSL(330, 159 | uniform mat4 projection; 160 | layout(location = 0) in vec2 coord; 161 | out vec2 texcoord; 162 | void main() { 163 | gl_Position = projection * vec4(coord, 0, 1); 164 | texcoord = coord; 165 | } 166 | ); 167 | const char vertex_shader[] = GLSL(330, 168 | uniform mat4 projection; 169 | uniform float scale = 1.0; 170 | uniform vec2 texorig; 171 | layout(location = 0) in vec2 coord; 172 | layout(location = 1) in vec2 in_texcoord; 173 | out vec2 texcoord; 174 | void main() { 175 | gl_Position = projection * vec4(coord, 0, scale); 176 | texcoord = in_texcoord + texorig; 177 | } 178 | ); 179 | const char shadow_colorization_frag[] = GLSL(330, 180 | uniform vec4 color; 181 | uniform sampler2D tex; 182 | in vec2 texcoord; 183 | out vec4 out_color; 184 | void main() { 185 | vec4 c = texelFetch(tex, ivec2(texcoord), 0); 186 | out_color = c.r * color; 187 | } 188 | ); 189 | // clang-format on 190 | -------------------------------------------------------------------------------- /src/backend/meson.build: -------------------------------------------------------------------------------- 1 | # enable xrender 2 | srcs += [ files('backend_common.c', 'xrender/xrender.c', 'dummy/dummy.c', 'backend.c', 'driver.c') ] 3 | 4 | # enable opengl 5 | if get_option('opengl') 6 | srcs += [ files('gl/gl_common.c', 'gl/glx.c', 'gl/blur.c', 'gl/shaders.c', 'gl/egl.c') ] 7 | endif 8 | -------------------------------------------------------------------------------- /src/c2.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | typedef struct _c2_lptr c2_lptr_t; 9 | typedef struct session session_t; 10 | struct managed_win; 11 | 12 | typedef void (*c2_userdata_free)(void *); 13 | c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data); 14 | 15 | c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f); 16 | 17 | bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst, 18 | void **pdata); 19 | 20 | bool c2_list_postprocess(session_t *ps, c2_lptr_t *list); 21 | typedef bool (*c2_list_foreach_cb_t)(const c2_lptr_t *cond, void *data); 22 | bool c2_list_foreach(const c2_lptr_t *list, c2_list_foreach_cb_t cb, void *data); 23 | /// Return user data stored in a condition. 24 | void *c2_list_get_data(const c2_lptr_t *condlist); 25 | 26 | /** 27 | * Destroy a condition list. 28 | */ 29 | static inline void c2_list_free(c2_lptr_t **pcondlst, c2_userdata_free f) { 30 | while ((*pcondlst = c2_free_lptr(*pcondlst, f))) { 31 | } 32 | *pcondlst = NULL; 33 | } 34 | -------------------------------------------------------------------------------- /src/cache.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cache.h" 4 | #include "compiler.h" 5 | #include "utils.h" 6 | 7 | struct cache_entry { 8 | char *key; 9 | void *value; 10 | UT_hash_handle hh; 11 | }; 12 | 13 | struct cache { 14 | cache_getter_t getter; 15 | cache_free_t free; 16 | void *user_data; 17 | struct cache_entry *entries; 18 | }; 19 | 20 | void cache_set(struct cache *c, const char *key, void *data) { 21 | struct cache_entry *e = NULL; 22 | HASH_FIND_STR(c->entries, key, e); 23 | CHECK(!e); 24 | 25 | e = ccalloc(1, struct cache_entry); 26 | e->key = strdup(key); 27 | e->value = data; 28 | HASH_ADD_STR(c->entries, key, e); 29 | } 30 | 31 | void *cache_get(struct cache *c, const char *key, int *err) { 32 | struct cache_entry *e; 33 | HASH_FIND_STR(c->entries, key, e); 34 | if (e) { 35 | return e->value; 36 | } 37 | 38 | int tmperr; 39 | if (!err) { 40 | err = &tmperr; 41 | } 42 | 43 | *err = 0; 44 | e = ccalloc(1, struct cache_entry); 45 | e->key = strdup(key); 46 | e->value = c->getter(c->user_data, key, err); 47 | if (*err) { 48 | free(e->key); 49 | free(e); 50 | return NULL; 51 | } 52 | 53 | HASH_ADD_STR(c->entries, key, e); 54 | return e->value; 55 | } 56 | 57 | static inline void _cache_invalidate(struct cache *c, struct cache_entry *e) { 58 | if (c->free) { 59 | c->free(c->user_data, e->value); 60 | } 61 | free(e->key); 62 | HASH_DEL(c->entries, e); 63 | free(e); 64 | } 65 | 66 | void cache_invalidate(struct cache *c, const char *key) { 67 | struct cache_entry *e; 68 | HASH_FIND_STR(c->entries, key, e); 69 | 70 | if (e) { 71 | _cache_invalidate(c, e); 72 | } 73 | } 74 | 75 | void cache_invalidate_all(struct cache *c) { 76 | struct cache_entry *e, *tmpe; 77 | HASH_ITER(hh, c->entries, e, tmpe) { 78 | _cache_invalidate(c, e); 79 | } 80 | } 81 | 82 | void *cache_free(struct cache *c) { 83 | void *ret = c->user_data; 84 | cache_invalidate_all(c); 85 | free(c); 86 | return ret; 87 | } 88 | 89 | struct cache *new_cache(void *ud, cache_getter_t getter, cache_free_t f) { 90 | auto c = ccalloc(1, struct cache); 91 | c->user_data = ud; 92 | c->getter = getter; 93 | c->free = f; 94 | return c; 95 | } 96 | -------------------------------------------------------------------------------- /src/cache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct cache; 4 | 5 | typedef void *(*cache_getter_t)(void *user_data, const char *key, int *err); 6 | typedef void (*cache_free_t)(void *user_data, void *data); 7 | 8 | /// Create a cache with `getter`, and a free function `f` which is used to free the cache 9 | /// value when they are invalidated. 10 | /// 11 | /// `user_data` will be passed to `getter` and `f` when they are called. 12 | struct cache *new_cache(void *user_data, cache_getter_t getter, cache_free_t f); 13 | 14 | /// Fetch a value from the cache. If the value doesn't present in the cache yet, the 15 | /// getter will be called, and the returned value will be stored into the cache. 16 | void *cache_get(struct cache *, const char *key, int *err); 17 | 18 | /// Invalidate a value in the cache. 19 | void cache_invalidate(struct cache *, const char *key); 20 | 21 | /// Invalidate all values in the cache. 22 | void cache_invalidate_all(struct cache *); 23 | 24 | /// Invalidate all values in the cache and free it. Returns the user data passed to 25 | /// `new_cache` 26 | void *cache_free(struct cache *); 27 | 28 | /// Insert a key-value pair into the cache. Only used for internal testing. Takes 29 | /// ownership of `data` 30 | /// 31 | /// If `key` already exists in the cache, this function will abort the program. 32 | void cache_set(struct cache *c, const char *key, void *data); 33 | -------------------------------------------------------------------------------- /src/compfy.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 3 | 4 | // Throw everything in here. 5 | // !!! DON'T !!! 6 | 7 | // === Includes === 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include "backend/backend.h" 16 | #include "c2.h" 17 | #include "common.h" 18 | #include "compiler.h" 19 | #include "config.h" 20 | #include "log.h" // XXX clean up 21 | #include "region.h" 22 | #include "render.h" 23 | #include "types.h" 24 | #include "utils.h" 25 | #include "win.h" 26 | #include "x.h" 27 | 28 | enum root_flags { 29 | ROOT_FLAGS_SCREEN_CHANGE = 1, // Received RandR screen change notify, we 30 | // use this to track refresh rate changes 31 | ROOT_FLAGS_CONFIGURED = 2 // Received configure notify on the root window 32 | }; 33 | 34 | // == Functions == 35 | 36 | void add_damage(session_t *ps, const region_t *damage); 37 | 38 | uint32_t determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode); 39 | 40 | void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce); 41 | 42 | void root_damaged(session_t *ps); 43 | 44 | void cxinerama_upd_scrs(session_t *ps); 45 | 46 | void queue_redraw(session_t *ps); 47 | 48 | void discard_ignore(session_t *ps, unsigned long sequence); 49 | 50 | void set_root_flags(session_t *ps, uint64_t flags); 51 | 52 | void quit(session_t *ps); 53 | 54 | xcb_window_t session_get_target_window(session_t *); 55 | 56 | uint8_t session_redirection_mode(session_t *ps); 57 | 58 | /** 59 | * Set a switch_t array of all unset wintypes to true. 60 | */ 61 | static inline void wintype_arr_enable_unset(switch_t arr[]) { 62 | wintype_t i; 63 | 64 | for (i = 0; i < NUM_WINTYPES; ++i) 65 | if (UNSET == arr[i]) 66 | arr[i] = ON; 67 | } 68 | 69 | /** 70 | * Check if a window ID exists in an array of window IDs. 71 | * 72 | * @param arr the array of window IDs 73 | * @param count amount of elements in the array 74 | * @param wid window ID to search for 75 | */ 76 | static inline bool array_wid_exists(const xcb_window_t *arr, int count, xcb_window_t wid) { 77 | while (count--) { 78 | if (arr[count] == wid) { 79 | return true; 80 | } 81 | } 82 | 83 | return false; 84 | } 85 | 86 | #ifndef CONFIG_OPENGL 87 | static inline void free_paint_glx(session_t *ps attr_unused, paint_t *p attr_unused) { 88 | } 89 | static inline void 90 | free_win_res_glx(session_t *ps attr_unused, struct managed_win *w attr_unused) { 91 | } 92 | #endif 93 | 94 | /** 95 | * Dump an drawable's info. 96 | */ 97 | static inline void dump_drawable(session_t *ps, xcb_drawable_t drawable) { 98 | auto r = xcb_get_geometry_reply(ps->c, xcb_get_geometry(ps->c, drawable), NULL); 99 | if (!r) { 100 | log_trace("Drawable %#010x: Failed", drawable); 101 | return; 102 | } 103 | log_trace("Drawable %#010x: x = %u, y = %u, wid = %u, hei = %d, b = %u, d = %u", 104 | drawable, r->x, r->y, r->width, r->height, r->border_width, r->depth); 105 | free(r); 106 | } 107 | -------------------------------------------------------------------------------- /src/compfy.modulemap: -------------------------------------------------------------------------------- 1 | // modulemap 2 | 3 | module compiler { 4 | header "compiler.h" 5 | } 6 | module string_utils { 7 | header "string_utils.h" 8 | } 9 | module dbus { 10 | header "dbus.h" 11 | } 12 | module kernel { 13 | header "kernel.h" 14 | } 15 | module utils { 16 | // Has macros expands to calloc/malloc 17 | header "utils.h" 18 | export libc.stdlib 19 | } 20 | module region { 21 | header "region.h" 22 | } 23 | module compfy { 24 | header "compfy.h" 25 | } 26 | module curl { 27 | header "curl.h" 28 | } 29 | module json-c { 30 | header "json.h" 31 | } 32 | module types { 33 | header "types.h" 34 | } 35 | module c2 { 36 | header "c2.h" 37 | } 38 | module render { 39 | header "render.h" 40 | } 41 | module options { 42 | header "options.h" 43 | } 44 | module opengl { 45 | header "opengl.h" 46 | } 47 | module diagnostic { 48 | header "diagnostic.h" 49 | } 50 | module win_defs { 51 | header "win_defs.h" 52 | } 53 | module win { 54 | header "win.h" 55 | export win_defs 56 | } 57 | module log { 58 | header "log.h" 59 | export compiler 60 | } 61 | module x { 62 | header "x.h" 63 | } 64 | module vsync { 65 | header "vsync.h" 66 | } 67 | module common { 68 | header "common.h" 69 | } 70 | module config { 71 | header "config.h" 72 | } 73 | module xrescheck { 74 | header "xrescheck.h" 75 | } 76 | module cache { 77 | header "cache.h" 78 | } 79 | module backend { 80 | module gl { 81 | module gl_common { 82 | header "backend/gl/gl_common.h" 83 | } 84 | module glx { 85 | header "backend/gl/glx.h" 86 | export GL.glx 87 | } 88 | } 89 | module backend { 90 | header "backend/backend.h" 91 | } 92 | module backend_common { 93 | header "backend/backend_common.h" 94 | } 95 | } 96 | module xcb [system] { 97 | module xcb { 98 | header "/usr/include/xcb/xcb.h" 99 | export * 100 | } 101 | module randr { 102 | header "/usr/include/xcb/randr.h" 103 | export * 104 | } 105 | module render { 106 | header "/usr/include/xcb/render.h" 107 | export * 108 | } 109 | module sync { 110 | header "/usr/include/xcb/sync.h" 111 | export * 112 | } 113 | module composite { 114 | header "/usr/include/xcb/composite.h" 115 | export * 116 | } 117 | module xfixes { 118 | header "/usr/include/xcb/xfixes.h" 119 | export * 120 | } 121 | module damage { 122 | header "/usr/include/xcb/damage.h" 123 | export * 124 | } 125 | module xproto { 126 | header "/usr/include/xcb/xproto.h" 127 | export * 128 | } 129 | module present { 130 | header "/usr/include/xcb/present.h" 131 | } 132 | module util { 133 | module render { 134 | header "/usr/include/xcb/xcb_renderutil.h" 135 | export * 136 | } 137 | } 138 | } 139 | module X11 [system] { 140 | module Xlib { 141 | header "/usr/include/X11/Xlib.h" 142 | export * 143 | } 144 | module Xutil { 145 | header "/usr/include/X11/Xutil.h" 146 | export * 147 | } 148 | } 149 | module GL [system] { 150 | module glx { 151 | header "/usr/include/GL/glx.h" 152 | export * 153 | } 154 | module gl { 155 | header "/usr/include/GL/gl.h" 156 | export * 157 | } 158 | } 159 | module libc [system] { 160 | export * 161 | module assert { 162 | export * 163 | textual header "/usr/include/assert.h" 164 | } 165 | module string { 166 | export * 167 | header "/usr/include/string.h" 168 | } 169 | module ctype { 170 | export * 171 | header "/usr/include/ctype.h" 172 | } 173 | module errno { 174 | export * 175 | header "/usr/include/errno.h" 176 | } 177 | module fenv { 178 | export * 179 | header "/usr/include/fenv.h" 180 | } 181 | module inttypes { 182 | export * 183 | header "/usr/include/inttypes.h" 184 | } 185 | module math { 186 | export * 187 | header "/usr/include/math.h" 188 | } 189 | module setjmp { 190 | export * 191 | header "/usr/include/setjmp.h" 192 | } 193 | module stdio { 194 | export * 195 | header "/usr/include/stdio.h" 196 | } 197 | 198 | module stdlib [system] { 199 | export * 200 | header "/usr/include/stdlib.h" 201 | } 202 | } 203 | 204 | // glib specific header. In it's own module because it 205 | // doesn't exist on some systems with unpatched glib 2.26+ 206 | module "xlocale.h" [system] { 207 | export * 208 | header "/usr/include/xlocale.h" 209 | } 210 | 211 | // System header that we have difficult with merging. 212 | module "sys_types.h" [system] { 213 | export * 214 | header "/usr/include/sys/types.h" 215 | } 216 | 217 | module "signal.h" [system] { 218 | export * 219 | header "/usr/include/signal.h" 220 | } 221 | -------------------------------------------------------------------------------- /src/compiler.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2018 Yuxuan Shui 3 | #pragma once 4 | 5 | #ifdef HAS_STDC_PREDEF_H 6 | #include 7 | #endif 8 | 9 | // clang-format off 10 | #define auto __auto_type 11 | #define likely(x) __builtin_expect(!!(x), 1) 12 | #define unlikely(x) __builtin_expect(!!(x), 0) 13 | #define likely_if(x) if (likely(x)) 14 | #define unlikely_if(x) if (unlikely(x)) 15 | 16 | #ifndef __has_attribute 17 | # if __GNUC__ >= 4 18 | # define __has_attribute(x) 1 19 | # else 20 | # define __has_attribute(x) 0 21 | # endif 22 | #endif 23 | 24 | #if __has_attribute(const) 25 | # define attr_const __attribute__((const)) 26 | #else 27 | # define attr_const 28 | #endif 29 | 30 | #if __has_attribute(format) 31 | # define attr_printf(a, b) __attribute__((format(printf, a, b))) 32 | #else 33 | # define attr_printf(a, b) 34 | #endif 35 | 36 | #if __has_attribute(pure) 37 | # define attr_pure __attribute__((pure)) 38 | #else 39 | # define attr_pure 40 | #endif 41 | 42 | #if __has_attribute(unused) 43 | # define attr_unused __attribute__((unused)) 44 | #else 45 | # define attr_unused 46 | #endif 47 | 48 | #if __has_attribute(warn_unused_result) 49 | # define attr_warn_unused_result __attribute__((warn_unused_result)) 50 | #else 51 | # define attr_warn_unused_result 52 | #endif 53 | // An alias for conveninence 54 | #define must_use attr_warn_unused_result 55 | 56 | #if __has_attribute(nonnull) 57 | # define attr_nonnull(...) __attribute__((nonnull(__VA_ARGS__))) 58 | # define attr_nonnull_all __attribute__((nonnull)) 59 | #else 60 | # define attr_nonnull(...) 61 | # define attr_nonnull_all 62 | #endif 63 | 64 | #if __has_attribute(returns_nonnull) 65 | # define attr_ret_nonnull __attribute__((returns_nonnull)) 66 | #else 67 | # define attr_ret_nonnull 68 | #endif 69 | 70 | #if __has_attribute(deprecated) 71 | # define attr_deprecated __attribute__((deprecated)) 72 | #else 73 | # define attr_deprecated 74 | #endif 75 | 76 | #if __has_attribute(malloc) 77 | # define attr_malloc __attribute__((malloc)) 78 | #else 79 | # define attr_malloc 80 | #endif 81 | 82 | #if __has_attribute(fallthrough) 83 | # define fallthrough() __attribute__((fallthrough)) 84 | #else 85 | # define fallthrough() 86 | #endif 87 | 88 | #if __has_attribute(cleanup) 89 | # define cleanup(func) __attribute__((cleanup(func))) 90 | #else 91 | # error "Compiler is missing cleanup attribute" 92 | #endif 93 | 94 | #if __STDC_VERSION__ >= 201112L 95 | # define attr_noret _Noreturn 96 | #else 97 | # if __has_attribute(noreturn) 98 | # define attr_noret __attribute__((noreturn)) 99 | # else 100 | # define attr_noret 101 | # endif 102 | #endif 103 | 104 | #if defined(__GNUC__) || defined(__clang__) 105 | # define unreachable __builtin_unreachable() 106 | #else 107 | # define unreachable do {} while(0) 108 | #endif 109 | 110 | #ifndef __has_include 111 | #define __has_include(x) 0 112 | #endif 113 | 114 | #if !defined(__STDC_NO_THREADS__) && __has_include() 115 | # include 116 | #elif __STDC_VERSION__ >= 201112L 117 | # define thread_local _Thread_local 118 | #elif defined(__GNUC__) || defined(__clang__) 119 | # define thread_local __thread 120 | #else 121 | # define thread_local _Pragma("GCC error \"No thread local storage support\"") __error__ 122 | #endif 123 | // clang-format on 124 | 125 | typedef unsigned long ulong; 126 | typedef unsigned int uint; 127 | 128 | static inline int attr_const popcntul(unsigned long a) { 129 | return __builtin_popcountl(a); 130 | } 131 | -------------------------------------------------------------------------------- /src/dbus.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | 5 | #include 6 | 7 | typedef struct session session_t; 8 | struct win; 9 | 10 | /** 11 | * Return a string representation of a D-Bus message type. 12 | */ 13 | static inline const char *cdbus_repr_msgtype(DBusMessage *msg) { 14 | return dbus_message_type_to_string(dbus_message_get_type(msg)); 15 | } 16 | 17 | /** 18 | * Initialize D-Bus connection. 19 | */ 20 | bool cdbus_init(session_t *ps, const char *uniq_name); 21 | 22 | /** 23 | * Destroy D-Bus connection. 24 | */ 25 | void cdbus_destroy(session_t *ps); 26 | 27 | /// Generate dbus win_added signal 28 | void cdbus_ev_win_added(session_t *ps, struct win *w); 29 | 30 | /// Generate dbus win_destroyed signal 31 | void cdbus_ev_win_destroyed(session_t *ps, struct win *w); 32 | 33 | /// Generate dbus win_mapped signal 34 | void cdbus_ev_win_mapped(session_t *ps, struct win *w); 35 | 36 | /// Generate dbus win_unmapped signal 37 | void cdbus_ev_win_unmapped(session_t *ps, struct win *w); 38 | 39 | /// Generate dbus win_focusout signal 40 | void cdbus_ev_win_focusout(session_t *ps, struct win *w); 41 | 42 | /// Generate dbus win_focusin signal 43 | void cdbus_ev_win_focusin(session_t *ps, struct win *w); 44 | 45 | // vim: set noet sw=8 ts=8 : 46 | -------------------------------------------------------------------------------- /src/diagnostic.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2018 Yuxuan Shui 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "backend/driver.h" 9 | #include "common.h" 10 | #include "config.h" 11 | #include "diagnostic.h" 12 | #include "compfy.h" 13 | 14 | void print_diagnostics(session_t *ps, const char *config_file, bool compositor_running) { 15 | printf("**Version:** " COMPFY_VERSION "\n"); 16 | //printf("**CFLAGS:** %s\n", "??"); 17 | printf("\n### Extensions:\n\n"); 18 | printf("* Shape: %s\n", ps->shape_exists ? "Yes" : "No"); 19 | printf("* XRandR: %s\n", ps->randr_exists ? "Yes" : "No"); 20 | printf("* Present: %s\n", ps->present_exists ? "Present" : "Not Present"); 21 | printf("\n### Misc:\n\n"); 22 | printf("* Use Overlay: %s\n", ps->overlay != XCB_NONE ? "Yes" : "No"); 23 | if (ps->overlay == XCB_NONE) { 24 | if (compositor_running) { 25 | printf(" (Another compositor is already running)\n"); 26 | } else if (session_redirection_mode(ps) != XCB_COMPOSITE_REDIRECT_MANUAL) { 27 | printf(" (Not in manual redirection mode)\n"); 28 | } else { 29 | printf("\n"); 30 | } 31 | } 32 | #ifdef __FAST_MATH__ 33 | printf("* Fast Math: Yes\n"); 34 | #endif 35 | printf("* Config file used: %s\n", config_file ?: "None"); 36 | printf("\n### Drivers (inaccurate):\n\n"); 37 | print_drivers(ps->drivers); 38 | 39 | for (int i = 0; i < NUM_BKEND; i++) { 40 | if (backend_list[i] && backend_list[i]->diagnostics) { 41 | printf("\n### Backend: %s\n\n", BACKEND_STRS[i]); 42 | auto data = backend_list[i]->init(ps); 43 | if (!data) { 44 | printf(" Cannot initialize this backend\n"); 45 | } else { 46 | backend_list[i]->diagnostics(data); 47 | backend_list[i]->deinit(data); 48 | } 49 | } 50 | } 51 | } 52 | 53 | // vim: set noet sw=8 ts=8 : 54 | -------------------------------------------------------------------------------- /src/diagnostic.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2018 Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | 7 | typedef struct session session_t; 8 | 9 | void print_diagnostics(session_t *, const char *config_file, bool compositor_running); 10 | -------------------------------------------------------------------------------- /src/err.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2019 Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | #include 7 | #include "compiler.h" 8 | 9 | // Functions for error reporting, adopted from Linux 10 | 11 | // INFO in user space we can probably be more liberal about what pointer we consider 12 | // error. e.g. In x86_64 Linux, all addresses with the highest bit set is invalid in user 13 | // space. 14 | #define MAX_ERRNO 4095 15 | 16 | static inline void *must_use ERR_PTR(intptr_t err) { 17 | return (void *)err; 18 | } 19 | 20 | static inline intptr_t must_use PTR_ERR(void *ptr) { 21 | return (intptr_t)ptr; 22 | } 23 | 24 | static inline bool must_use IS_ERR(void *ptr) { 25 | return unlikely((uintptr_t)ptr > (uintptr_t)-MAX_ERRNO); 26 | } 27 | 28 | static inline bool must_use IS_ERR_OR_NULL(void *ptr) { 29 | return unlikely(!ptr) || IS_ERR(ptr); 30 | } 31 | 32 | static inline intptr_t must_use PTR_ERR_OR_ZERO(void *ptr) { 33 | if (IS_ERR(ptr)) { 34 | return PTR_ERR(ptr); 35 | } 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /src/event.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2019, Yuxuan Shui 3 | 4 | #include 5 | 6 | #include "common.h" 7 | 8 | void ev_handle(session_t *ps, xcb_generic_event_t *ev); 9 | -------------------------------------------------------------------------------- /src/file_watch.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #ifdef HAS_INOTIFY 4 | #include 5 | #elif HAS_KQUEUE 6 | // clang-format off 7 | #include 8 | // clang-format on 9 | #include 10 | #undef EV_ERROR // Avoid clashing with libev's EV_ERROR 11 | #include // For O_RDONLY 12 | #include // For struct timespec 13 | #include // For open 14 | #endif 15 | 16 | #include 17 | #include 18 | 19 | #include "file_watch.h" 20 | #include "list.h" 21 | #include "log.h" 22 | #include "utils.h" 23 | 24 | struct watched_file { 25 | int wd; 26 | void *ud; 27 | file_watch_cb_t cb; 28 | 29 | UT_hash_handle hh; 30 | }; 31 | 32 | struct file_watch_registry { 33 | struct ev_io w; 34 | 35 | struct watched_file *reg; 36 | }; 37 | 38 | static void file_watch_ev_cb(EV_P attr_unused, struct ev_io *w, int revent attr_unused) { 39 | auto fwr = (struct file_watch_registry *)w; 40 | 41 | while (true) { 42 | int wd = -1; 43 | #ifdef HAS_INOTIFY 44 | struct inotify_event inotify_event; 45 | auto ret = read(w->fd, &inotify_event, sizeof(struct inotify_event)); 46 | if (ret < 0) { 47 | if (errno != EAGAIN) { 48 | log_error_errno("Failed to read from inotify fd"); 49 | } 50 | break; 51 | } 52 | wd = inotify_event.wd; 53 | #elif HAS_KQUEUE 54 | struct kevent ev; 55 | struct timespec timeout = {0}; 56 | int ret = kevent(fwr->w.fd, NULL, 0, &ev, 1, &timeout); 57 | if (ret <= 0) { 58 | if (ret < 0) { 59 | log_error_errno("Failed to get kevent"); 60 | } 61 | break; 62 | } 63 | wd = (int)ev.ident; 64 | #else 65 | assert(false); 66 | #endif 67 | 68 | struct watched_file *wf = NULL; 69 | HASH_FIND_INT(fwr->reg, &wd, wf); 70 | if (!wf) { 71 | log_warn("Got notification for a file I didn't watch."); 72 | continue; 73 | } 74 | wf->cb(wf->ud); 75 | } 76 | } 77 | 78 | void *file_watch_init(EV_P) { 79 | log_debug("Starting watching for file changes"); 80 | int fd = -1; 81 | #ifdef HAS_INOTIFY 82 | fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 83 | if (fd < 0) { 84 | log_error_errno("inotify_init1 failed"); 85 | return NULL; 86 | } 87 | #elif HAS_KQUEUE 88 | fd = kqueue(); 89 | if (fd < 0) { 90 | log_error_errno("Failed to create kqueue"); 91 | return NULL; 92 | } 93 | #else 94 | log_info("No file watching support found on the host system."); 95 | return NULL; 96 | #endif 97 | auto fwr = ccalloc(1, struct file_watch_registry); 98 | ev_io_init(&fwr->w, file_watch_ev_cb, fd, EV_READ); 99 | ev_io_start(EV_A_ & fwr->w); 100 | 101 | return fwr; 102 | } 103 | 104 | void file_watch_destroy(EV_P_ void *_fwr) { 105 | log_debug("Stopping watching for file changes"); 106 | auto fwr = (struct file_watch_registry *)_fwr; 107 | struct watched_file *i, *tmp; 108 | 109 | HASH_ITER(hh, fwr->reg, i, tmp) { 110 | HASH_DEL(fwr->reg, i); 111 | #ifdef HAS_KQUEUE 112 | // kqueue watch descriptors are file descriptors of 113 | // the files we are watching, so we need to close 114 | // them 115 | close(i->wd); 116 | #endif 117 | free(i); 118 | } 119 | 120 | ev_io_stop(EV_A_ & fwr->w); 121 | close(fwr->w.fd); 122 | free(fwr); 123 | } 124 | 125 | bool file_watch_add(void *_fwr, const char *filename, file_watch_cb_t cb, void *ud) { 126 | log_debug("Adding \"%s\" to watched files", filename); 127 | auto fwr = (struct file_watch_registry *)_fwr; 128 | int wd = -1; 129 | 130 | struct stat statbuf; 131 | int ret = stat(filename, &statbuf); 132 | if (ret < 0) { 133 | log_error_errno("Failed to retrieve information about file \"%s\"", filename); 134 | return false; 135 | } 136 | if (!S_ISREG(statbuf.st_mode)) { 137 | log_info("\"%s\" is not a regular file, not watching it.", filename); 138 | return false; 139 | } 140 | 141 | #ifdef HAS_INOTIFY 142 | wd = inotify_add_watch(fwr->w.fd, filename, 143 | IN_CLOSE_WRITE | IN_MOVE_SELF | IN_DELETE_SELF); 144 | if (wd < 0) { 145 | log_error_errno("Failed to watch file \"%s\"", filename); 146 | return false; 147 | } 148 | #elif HAS_KQUEUE 149 | wd = open(filename, O_RDONLY); 150 | if (wd < 0) { 151 | log_error_errno("Cannot open file \"%s\" for watching", filename); 152 | return false; 153 | } 154 | 155 | uint32_t fflags = NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE | NOTE_ATTRIB; 156 | // NOTE_CLOSE_WRITE is relatively new, so we cannot just use it 157 | #ifdef NOTE_CLOSE_WRITE 158 | fflags |= NOTE_CLOSE_WRITE; 159 | #else 160 | // NOTE_WRITE will receive notification more frequent than necessary, so is less 161 | // preferrable 162 | fflags |= NOTE_WRITE; 163 | #endif 164 | struct kevent ev = { 165 | .ident = (unsigned int)wd, // the wd < 0 case is checked above 166 | .filter = EVFILT_VNODE, 167 | .flags = EV_ADD | EV_CLEAR, 168 | .fflags = fflags, 169 | .data = 0, 170 | .udata = NULL, 171 | }; 172 | if (kevent(fwr->w.fd, &ev, 1, NULL, 0, NULL) < 0) { 173 | log_error_errno("Failed to register kevent"); 174 | close(wd); 175 | return false; 176 | } 177 | #else 178 | assert(false); 179 | #endif // HAS_KQUEUE 180 | 181 | auto w = ccalloc(1, struct watched_file); 182 | w->wd = wd; 183 | w->cb = cb; 184 | w->ud = ud; 185 | 186 | HASH_ADD_INT(fwr->reg, wd, w); 187 | return true; 188 | } 189 | -------------------------------------------------------------------------------- /src/file_watch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | 6 | typedef void (*file_watch_cb_t)(void *); 7 | 8 | void *file_watch_init(EV_P); 9 | bool file_watch_add(void *, const char *, file_watch_cb_t, void *); 10 | void file_watch_destroy(EV_P_ void *); 11 | -------------------------------------------------------------------------------- /src/kernel.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #include 5 | #include 6 | 7 | #include "compiler.h" 8 | #include "kernel.h" 9 | #include "log.h" 10 | #include "utils.h" 11 | 12 | /// Sum a region convolution kernel. Region is defined by a width x height rectangle whose 13 | /// top left corner is at (x, y) 14 | double sum_kernel(const conv *map, int x, int y, int width, int height) { 15 | double ret = 0; 16 | 17 | // Compute sum of values which are "in range" 18 | int xstart = normalize_i_range(x, 0, map->w), 19 | xend = normalize_i_range(width + x, 0, map->w); 20 | int ystart = normalize_i_range(y, 0, map->h), 21 | yend = normalize_i_range(height + y, 0, map->h); 22 | assert(yend >= ystart && xend >= xstart); 23 | 24 | int d = map->w; 25 | if (map->rsum) { 26 | // See sum_kernel_preprocess 27 | double v1 = xstart ? map->rsum[(yend - 1) * d + xstart - 1] : 0; 28 | double v2 = ystart ? map->rsum[(ystart - 1) * d + xend - 1] : 0; 29 | double v3 = (xstart && ystart) ? map->rsum[(ystart - 1) * d + xstart - 1] : 0; 30 | return map->rsum[(yend - 1) * d + xend - 1] - v1 - v2 + v3; 31 | } 32 | 33 | for (int yi = ystart; yi < yend; yi++) { 34 | for (int xi = xstart; xi < xend; xi++) { 35 | ret += map->data[yi * d + xi]; 36 | } 37 | } 38 | 39 | return ret; 40 | } 41 | 42 | double sum_kernel_normalized(const conv *map, int x, int y, int width, int height) { 43 | double ret = sum_kernel(map, x, y, width, height); 44 | if (ret < 0) { 45 | ret = 0; 46 | } 47 | if (ret > 1) { 48 | ret = 1; 49 | } 50 | return ret; 51 | } 52 | 53 | static inline double attr_const gaussian(double r, double x, double y) { 54 | // Formula can be found here: 55 | // https://en.wikipedia.org/wiki/Gaussian_blur#Mathematics 56 | // Except a special case for r == 0 to produce sharp shadows 57 | if (r == 0) 58 | return 1; 59 | return exp(-0.5 * (x * x + y * y) / (r * r)) / (2 * M_PI * r * r); 60 | } 61 | 62 | conv *gaussian_kernel(double r, int size) { 63 | conv *c; 64 | int center = size / 2; 65 | double t; 66 | assert(size % 2 == 1); 67 | 68 | c = cvalloc(sizeof(conv) + (size_t)(size * size) * sizeof(double)); 69 | c->w = c->h = size; 70 | c->rsum = NULL; 71 | t = 0.0; 72 | 73 | for (int y = 0; y < size; y++) { 74 | for (int x = 0; x < size; x++) { 75 | double g = gaussian(r, x - center, y - center); 76 | t += g; 77 | c->data[y * size + x] = g; 78 | } 79 | } 80 | 81 | for (int y = 0; y < size; y++) { 82 | for (int x = 0; x < size; x++) { 83 | c->data[y * size + x] /= t; 84 | } 85 | } 86 | 87 | return c; 88 | } 89 | 90 | /// Estimate the element of the sum of the first row in a gaussian kernel with standard 91 | /// deviation `r` and size `size`, 92 | static inline double estimate_first_row_sum(double size, double r) { 93 | // `factor` is integral of gaussian from -size to size 94 | double factor = erf(size / r / sqrt(2)); 95 | // `a` is gaussian at (size, 0) 96 | double a = exp(-0.5 * size * size / (r * r)) / sqrt(2 * M_PI) / r; 97 | // The sum of the whole kernel is normalized to 1, i.e. each element is divided by 98 | // factor sqaured. So the sum of the first row is a * factor / factor^2 = a / 99 | // factor 100 | return a / factor; 101 | } 102 | 103 | /// Pick a suitable gaussian kernel standard deviation for a given kernel size. The 104 | /// returned radius is the maximum possible radius (<= size*2) that satisfies no sum of 105 | /// the rows in the kernel are less than `row_limit` (up to certain precision). 106 | double gaussian_kernel_std_for_size(double size, double row_limit) { 107 | assert(size > 0); 108 | if (row_limit >= 1.0 / 2.0 / size) { 109 | return size * 2; 110 | } 111 | double l = 0, r = size * 2; 112 | while (r - l > 1e-2) { 113 | double mid = (l + r) / 2.0; 114 | double vmid = estimate_first_row_sum(size, mid); 115 | if (vmid > row_limit) { 116 | r = mid; 117 | } else { 118 | l = mid; 119 | } 120 | } 121 | return (l + r) / 2.0; 122 | } 123 | 124 | /// Create a gaussian kernel with auto detected standard deviation. The choosen standard 125 | /// deviation tries to make sure the outer most pixels of the shadow are completely 126 | /// transparent, so the transition from shadow to the background is smooth. 127 | /// 128 | /// @param[in] shadow_radius the radius of the shadow 129 | conv *gaussian_kernel_autodetect_deviation(double shadow_radius) { 130 | assert(shadow_radius >= 0); 131 | int size = (int)(shadow_radius * 2 + 1); 132 | 133 | if (shadow_radius == 0) { 134 | return gaussian_kernel(0, size); 135 | } 136 | double std = gaussian_kernel_std_for_size(shadow_radius, 0.5 / 256.0); 137 | return gaussian_kernel(std, size); 138 | } 139 | 140 | /// preprocess kernels to make shadow generation faster 141 | /// shadow_sum[x*d+y] is the sum of the kernel from (0, 0) to (x, y), inclusive 142 | void sum_kernel_preprocess(conv *map) { 143 | if (map->rsum) { 144 | free(map->rsum); 145 | } 146 | 147 | auto sum = map->rsum = ccalloc(map->w * map->h, double); 148 | sum[0] = map->data[0]; 149 | 150 | for (int x = 1; x < map->w; x++) { 151 | sum[x] = sum[x - 1] + map->data[x]; 152 | } 153 | 154 | const int d = map->w; 155 | for (int y = 1; y < map->h; y++) { 156 | sum[y * d] = sum[(y - 1) * d] + map->data[y * d]; 157 | for (int x = 1; x < map->w; x++) { 158 | double tmp = sum[(y - 1) * d + x] + sum[y * d + x - 1] - 159 | sum[(y - 1) * d + x - 1]; 160 | sum[y * d + x] = tmp + map->data[y * d + x]; 161 | } 162 | } 163 | } 164 | 165 | // vim: set noet sw=8 ts=8 : 166 | -------------------------------------------------------------------------------- /src/kernel.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | #include "compiler.h" 7 | 8 | /// Code for generating convolution kernels 9 | 10 | typedef struct conv { 11 | int w, h; 12 | double *rsum; 13 | double data[]; 14 | } conv; 15 | 16 | /// Calculate the sum of a rectangle part of the convolution kernel 17 | /// the rectangle is defined by top left (x, y), and a size (width x height) 18 | double attr_pure sum_kernel(const conv *map, int x, int y, int width, int height); 19 | double attr_pure sum_kernel_normalized(const conv *map, int x, int y, int width, int height); 20 | 21 | /// Create a kernel with gaussian distribution with standard deviation `r`, and size 22 | /// `size`. 23 | conv *gaussian_kernel(double r, int size); 24 | 25 | /// Estimate the best standard deviation for a give kernel size. 26 | double gaussian_kernel_std_for_size(double size, double row_limit); 27 | 28 | /// Create a gaussian kernel with auto detected standard deviation. The choosen standard 29 | /// deviation tries to make sure the outer most pixels of the shadow are completely 30 | /// transparent. 31 | /// 32 | /// @param[in] shadow_radius the radius of the shadow 33 | conv *gaussian_kernel_autodetect_deviation(double shadow_radius); 34 | 35 | /// preprocess kernels to make shadow generation faster 36 | /// shadow_sum[x*d+y] is the sum of the kernel from (0, 0) to (x, y), inclusive 37 | void sum_kernel_preprocess(conv *map); 38 | 39 | static inline void free_conv(conv *k) { 40 | free(k->rsum); 41 | free(k); 42 | } 43 | -------------------------------------------------------------------------------- /src/list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | /** 6 | * container_of - cast a member of a structure out to the containing structure 7 | * @ptr: the pointer to the member. 8 | * @type: the type of the container struct this is embedded in. 9 | * @member: the name of the member within the struct. 10 | * 11 | */ 12 | #define container_of(ptr, type, member) \ 13 | ({ \ 14 | const __typeof__(((type *)0)->member) *__mptr = (ptr); \ 15 | (type *)((char *)__mptr - offsetof(type, member)); \ 16 | }) 17 | 18 | struct list_node { 19 | struct list_node *next, *prev; 20 | }; 21 | 22 | #define list_entry(ptr, type, node) container_of(ptr, type, node) 23 | #define list_next_entry(ptr, node) list_entry((ptr)->node.next, __typeof__(*(ptr)), node) 24 | #define list_prev_entry(ptr, node) list_entry((ptr)->node.prev, __typeof__(*(ptr)), node) 25 | 26 | /// Insert a new node between two adjacent nodes in the list 27 | static inline void __list_insert_between(struct list_node *prev, struct list_node *next, 28 | struct list_node *new_) { 29 | new_->prev = prev; 30 | new_->next = next; 31 | next->prev = new_; 32 | prev->next = new_; 33 | } 34 | 35 | /// Insert a new node after `curr` 36 | static inline void list_insert_after(struct list_node *curr, struct list_node *new_) { 37 | __list_insert_between(curr, curr->next, new_); 38 | } 39 | 40 | /// Insert a new node before `curr` 41 | static inline void list_insert_before(struct list_node *curr, struct list_node *new_) { 42 | __list_insert_between(curr->prev, curr, new_); 43 | } 44 | 45 | /// Link two nodes in the list, so `next` becomes the successor node of `prev` 46 | static inline void __list_link(struct list_node *prev, struct list_node *next) { 47 | next->prev = prev; 48 | prev->next = next; 49 | } 50 | 51 | /// Remove a node from the list 52 | static inline void list_remove(struct list_node *to_remove) { 53 | __list_link(to_remove->prev, to_remove->next); 54 | to_remove->prev = (void *)-1; 55 | to_remove->next = (void *)-2; 56 | } 57 | 58 | /// Move `to_move` so that it's before `new_next` 59 | static inline void list_move_before(struct list_node *to_move, struct list_node *new_next) { 60 | list_remove(to_move); 61 | list_insert_before(new_next, to_move); 62 | } 63 | 64 | /// Move `to_move` so that it's after `new_prev` 65 | static inline void list_move_after(struct list_node *to_move, struct list_node *new_prev) { 66 | list_remove(to_move); 67 | list_insert_after(new_prev, to_move); 68 | } 69 | 70 | /// Initialize a list node that's intended to be the head node 71 | static inline void list_init_head(struct list_node *head) { 72 | head->next = head->prev = head; 73 | } 74 | 75 | /// Replace list node `old` with `n` 76 | static inline void list_replace(struct list_node *old, struct list_node *n) { 77 | __list_insert_between(old->prev, old->next, n); 78 | old->prev = (void *)-1; 79 | old->next = (void *)-2; 80 | } 81 | 82 | /// Return true if head is the only node in the list. Under usual circumstances this means 83 | /// the list is empty 84 | static inline bool list_is_empty(const struct list_node *head) { 85 | return head->prev == head; 86 | } 87 | 88 | /// Return true if `to_check` is the first node in list headed by `head` 89 | static inline bool 90 | list_node_is_first(const struct list_node *head, const struct list_node *to_check) { 91 | return head->next == to_check; 92 | } 93 | 94 | /// Return true if `to_check` is the last node in list headed by `head` 95 | static inline bool 96 | list_node_is_last(const struct list_node *head, const struct list_node *to_check) { 97 | return head->prev == to_check; 98 | } 99 | 100 | #define list_foreach(type, i, head, member) \ 101 | for (type *i = list_entry((head)->next, type, member); &i->member != (head); \ 102 | i = list_next_entry(i, member)) 103 | 104 | /// Like list_for_each, but it's safe to remove the current list node from the list 105 | #define list_foreach_safe(type, i, head, member) \ 106 | for (type *i = list_entry((head)->next, type, member), \ 107 | *__tmp = list_next_entry(i, member); \ 108 | &i->member != (head); i = __tmp, __tmp = list_next_entry(i, member)) 109 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #ifdef CONFIG_OPENGL 12 | #include 13 | #include "backend/gl/gl_common.h" 14 | #include "backend/gl/glx.h" 15 | #endif 16 | 17 | #include "compiler.h" 18 | #include "log.h" 19 | #include "utils.h" 20 | 21 | thread_local struct log *tls_logger; 22 | 23 | struct log_target; 24 | 25 | struct log { 26 | struct log_target *head; 27 | 28 | int log_level; 29 | }; 30 | 31 | struct log_target { 32 | const struct log_ops *ops; 33 | struct log_target *next; 34 | }; 35 | 36 | struct log_ops { 37 | void (*write)(struct log_target *, const char *, size_t); 38 | void (*writev)(struct log_target *, const struct iovec *, int vcnt); 39 | void (*destroy)(struct log_target *); 40 | 41 | /// Additional strings to print around the log_level string 42 | const char *(*colorize_begin)(enum log_level); 43 | const char *(*colorize_end)(enum log_level); 44 | }; 45 | 46 | /// Fallback writev for targets don't implement it 47 | static attr_unused void 48 | log_default_writev(struct log_target *tgt, const struct iovec *vec, int vcnt) { 49 | size_t total = 0; 50 | for (int i = 0; i < vcnt; i++) { 51 | total += vec[i].iov_len; 52 | } 53 | 54 | if (!total) { 55 | // Nothing to write 56 | return; 57 | } 58 | char *buf = ccalloc(total, char); 59 | total = 0; 60 | for (int i = 0; i < vcnt; i++) { 61 | memcpy(buf + total, vec[i].iov_base, vec[i].iov_len); 62 | total += vec[i].iov_len; 63 | } 64 | tgt->ops->write(tgt, buf, total); 65 | free(buf); 66 | } 67 | 68 | static attr_const const char *log_level_to_string(enum log_level level) { 69 | switch (level) { 70 | case LOG_LEVEL_TRACE: return "TRACE"; 71 | case LOG_LEVEL_DEBUG: return "DEBUG"; 72 | case LOG_LEVEL_INFO: return "INFO"; 73 | case LOG_LEVEL_WARN: return "WARN"; 74 | case LOG_LEVEL_ERROR: return "ERROR"; 75 | case LOG_LEVEL_FATAL: return "FATAL ERROR"; 76 | default: return "????"; 77 | } 78 | } 79 | 80 | enum log_level string_to_log_level(const char *str) { 81 | if (strcasecmp(str, "TRACE") == 0) 82 | return LOG_LEVEL_TRACE; 83 | else if (strcasecmp(str, "DEBUG") == 0) 84 | return LOG_LEVEL_DEBUG; 85 | else if (strcasecmp(str, "INFO") == 0) 86 | return LOG_LEVEL_INFO; 87 | else if (strcasecmp(str, "WARN") == 0) 88 | return LOG_LEVEL_WARN; 89 | else if (strcasecmp(str, "ERROR") == 0) 90 | return LOG_LEVEL_ERROR; 91 | return LOG_LEVEL_INVALID; 92 | } 93 | 94 | struct log *log_new(void) { 95 | auto ret = cmalloc(struct log); 96 | ret->log_level = LOG_LEVEL_WARN; 97 | ret->head = NULL; 98 | return ret; 99 | } 100 | 101 | void log_add_target(struct log *l, struct log_target *tgt) { 102 | assert(tgt->ops->writev); 103 | tgt->next = l->head; 104 | l->head = tgt; 105 | } 106 | 107 | /// Remove a previously added log target for a log struct, and destroy it. If the log 108 | /// target was never added, nothing happens. 109 | void log_remove_target(struct log *l, struct log_target *tgt) { 110 | struct log_target *now = l->head, **prev = &l->head; 111 | while (now) { 112 | if (now == tgt) { 113 | *prev = now->next; 114 | tgt->ops->destroy(tgt); 115 | break; 116 | } 117 | prev = &now->next; 118 | now = now->next; 119 | } 120 | } 121 | 122 | /// Destroy a log struct and every log target added to it 123 | void log_destroy(struct log *l) { 124 | // free all tgt 125 | struct log_target *head = l->head; 126 | while (head) { 127 | auto next = head->next; 128 | head->ops->destroy(head); 129 | head = next; 130 | } 131 | free(l); 132 | } 133 | 134 | void log_set_level(struct log *l, int level) { 135 | assert(level <= LOG_LEVEL_FATAL && level >= 0); 136 | l->log_level = level; 137 | } 138 | 139 | enum log_level log_get_level(const struct log *l) { 140 | return l->log_level; 141 | } 142 | 143 | attr_printf(4, 5) void log_printf(struct log *l, int level, const char *func, 144 | const char *fmt, ...) { 145 | assert(level <= LOG_LEVEL_FATAL && level >= 0); 146 | if (level < l->log_level) 147 | return; 148 | 149 | char *buf = NULL; 150 | va_list args; 151 | 152 | va_start(args, fmt); 153 | int blen = vasprintf(&buf, fmt, args); 154 | va_end(args); 155 | 156 | if (blen < 0 || !buf) { 157 | free(buf); 158 | return; 159 | } 160 | 161 | struct timespec ts; 162 | timespec_get(&ts, TIME_UTC); 163 | struct tm now; 164 | localtime_r(&ts.tv_sec, &now); 165 | char time_buf[100]; 166 | strftime(time_buf, sizeof time_buf, "%x %T", &now); 167 | 168 | char *time = NULL; 169 | int tlen = asprintf(&time, "%s.%03ld", time_buf, ts.tv_nsec / 1000000); 170 | if (tlen < 0 || !time) { 171 | free(buf); 172 | free(time); 173 | return; 174 | } 175 | 176 | const char *log_level_str = log_level_to_string(level); 177 | size_t llen = strlen(log_level_str); 178 | size_t flen = strlen(func); 179 | 180 | struct log_target *head = l->head; 181 | while (head) { 182 | const char *p = "", *s = ""; 183 | size_t plen = 0, slen = 0; 184 | 185 | if (head->ops->colorize_begin) { 186 | // construct target specific prefix 187 | p = head->ops->colorize_begin(level); 188 | plen = strlen(p); 189 | if (head->ops->colorize_end) { 190 | s = head->ops->colorize_end(level); 191 | slen = strlen(s); 192 | } 193 | } 194 | head->ops->writev( 195 | head, 196 | (struct iovec[]){{.iov_base = "[ ", .iov_len = 2}, 197 | {.iov_base = time, .iov_len = (size_t)tlen}, 198 | {.iov_base = " ", .iov_len = 1}, 199 | {.iov_base = (void *)func, .iov_len = flen}, 200 | {.iov_base = " ", .iov_len = 1}, 201 | {.iov_base = (void *)p, .iov_len = plen}, 202 | {.iov_base = (void *)log_level_str, .iov_len = llen}, 203 | {.iov_base = (void *)s, .iov_len = slen}, 204 | {.iov_base = " ] ", .iov_len = 3}, 205 | {.iov_base = buf, .iov_len = (size_t)blen}, 206 | {.iov_base = "\n", .iov_len = 1}}, 207 | 11); 208 | head = head->next; 209 | } 210 | free(time); 211 | free(buf); 212 | } 213 | 214 | /// A trivial deinitializer that simply frees the memory 215 | static attr_unused void logger_trivial_destroy(struct log_target *tgt) { 216 | free(tgt); 217 | } 218 | 219 | /// A null log target that does nothing 220 | static const struct log_ops null_logger_ops; 221 | static struct log_target null_logger_target = { 222 | .ops = &null_logger_ops, 223 | }; 224 | 225 | struct log_target *null_logger_new(void) { 226 | return &null_logger_target; 227 | } 228 | 229 | static void null_logger_write(struct log_target *tgt attr_unused, 230 | const char *str attr_unused, size_t len attr_unused) { 231 | return; 232 | } 233 | 234 | static void null_logger_writev(struct log_target *tgt attr_unused, 235 | const struct iovec *vec attr_unused, int vcnt attr_unused) { 236 | return; 237 | } 238 | 239 | static const struct log_ops null_logger_ops = { 240 | .write = null_logger_write, 241 | .writev = null_logger_writev, 242 | }; 243 | 244 | /// A file based logger that writes to file (or stdout/stderr) 245 | struct file_logger { 246 | struct log_target tgt; 247 | FILE *f; 248 | struct log_ops ops; 249 | }; 250 | 251 | static void file_logger_write(struct log_target *tgt, const char *str, size_t len) { 252 | auto f = (struct file_logger *)tgt; 253 | fwrite(str, 1, len, f->f); 254 | } 255 | 256 | static void file_logger_writev(struct log_target *tgt, const struct iovec *vec, int vcnt) { 257 | auto f = (struct file_logger *)tgt; 258 | fflush(f->f); 259 | writev(fileno(f->f), vec, vcnt); 260 | } 261 | 262 | static void file_logger_destroy(struct log_target *tgt) { 263 | auto f = (struct file_logger *)tgt; 264 | fclose(f->f); 265 | free(tgt); 266 | } 267 | 268 | #define ANSI(x) "\033[" x "m" 269 | static const char *terminal_colorize_begin(enum log_level level) { 270 | switch (level) { 271 | case LOG_LEVEL_TRACE: return ANSI("30;2"); 272 | case LOG_LEVEL_DEBUG: return ANSI("37;2"); 273 | case LOG_LEVEL_INFO: return ANSI("92"); 274 | case LOG_LEVEL_WARN: return ANSI("33"); 275 | case LOG_LEVEL_ERROR: return ANSI("31;1"); 276 | case LOG_LEVEL_FATAL: return ANSI("30;103;1"); 277 | default: return ""; 278 | } 279 | } 280 | 281 | static const char *terminal_colorize_end(enum log_level level attr_unused) { 282 | return ANSI("0"); 283 | } 284 | #undef PREFIX 285 | 286 | static const struct log_ops file_logger_ops = { 287 | .write = file_logger_write, 288 | .writev = file_logger_writev, 289 | .destroy = file_logger_destroy, 290 | }; 291 | 292 | struct log_target *file_logger_new(const char *filename) { 293 | FILE *f = fopen(filename, "a"); 294 | if (!f) { 295 | return NULL; 296 | } 297 | 298 | auto ret = cmalloc(struct file_logger); 299 | ret->tgt.ops = &ret->ops; 300 | ret->f = f; 301 | 302 | // Always assume a file is not a terminal 303 | ret->ops = file_logger_ops; 304 | 305 | return &ret->tgt; 306 | } 307 | 308 | struct log_target *stderr_logger_new(void) { 309 | int fd = dup(STDERR_FILENO); 310 | if (fd < 0) { 311 | return NULL; 312 | } 313 | 314 | FILE *f = fdopen(fd, "w"); 315 | if (!f) { 316 | return NULL; 317 | } 318 | 319 | auto ret = cmalloc(struct file_logger); 320 | ret->tgt.ops = &ret->ops; 321 | ret->f = f; 322 | ret->ops = file_logger_ops; 323 | 324 | if (isatty(fd)) { 325 | ret->ops.colorize_begin = terminal_colorize_begin; 326 | ret->ops.colorize_end = terminal_colorize_end; 327 | } 328 | return &ret->tgt; 329 | } 330 | 331 | #ifdef CONFIG_OPENGL 332 | /// An opengl logger that can be used for logging into opengl debugging tools, 333 | /// such as apitrace 334 | struct gl_string_marker_logger { 335 | struct log_target tgt; 336 | PFNGLSTRINGMARKERGREMEDYPROC gl_string_marker; 337 | }; 338 | 339 | static void 340 | gl_string_marker_logger_write(struct log_target *tgt, const char *str, size_t len) { 341 | auto g = (struct gl_string_marker_logger *)tgt; 342 | // strip newlines at the end of the string 343 | while (len > 0 && str[len - 1] == '\n') { 344 | len--; 345 | } 346 | g->gl_string_marker((GLsizei)len, str); 347 | } 348 | 349 | static const struct log_ops gl_string_marker_logger_ops = { 350 | .write = gl_string_marker_logger_write, 351 | .writev = log_default_writev, 352 | .destroy = logger_trivial_destroy, 353 | }; 354 | 355 | struct log_target *gl_string_marker_logger_new(void) { 356 | if (!gl_has_extension("GL_GREMEDY_string_marker")) { 357 | return NULL; 358 | } 359 | 360 | void *fnptr = glXGetProcAddress((GLubyte *)"glStringMarkerGREMEDY"); 361 | if (!fnptr) 362 | return NULL; 363 | 364 | auto ret = cmalloc(struct gl_string_marker_logger); 365 | ret->tgt.ops = &gl_string_marker_logger_ops; 366 | ret->gl_string_marker = fnptr; 367 | return &ret->tgt; 368 | } 369 | 370 | #else 371 | struct log_target *gl_string_marker_logger_new(void) { 372 | return NULL; 373 | } 374 | #endif 375 | 376 | // vim: set noet sw=8 ts=8: 377 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2018 Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | #include 7 | 8 | #include "compiler.h" 9 | 10 | enum log_level { 11 | LOG_LEVEL_INVALID = -1, 12 | LOG_LEVEL_TRACE = 0, 13 | LOG_LEVEL_DEBUG, 14 | LOG_LEVEL_INFO, 15 | LOG_LEVEL_WARN, 16 | LOG_LEVEL_ERROR, 17 | LOG_LEVEL_FATAL, 18 | }; 19 | 20 | #define LOG_UNLIKELY(level, x, ...) \ 21 | do { \ 22 | if (unlikely(LOG_LEVEL_##level >= log_get_level_tls())) { \ 23 | log_printf(tls_logger, LOG_LEVEL_##level, __func__, x, ##__VA_ARGS__); \ 24 | } \ 25 | } while (0) 26 | 27 | #define LOG(level, x, ...) \ 28 | do { \ 29 | if (LOG_LEVEL_##level >= log_get_level_tls()) { \ 30 | log_printf(tls_logger, LOG_LEVEL_##level, __func__, x, ##__VA_ARGS__); \ 31 | } \ 32 | } while (0) 33 | #define log_trace(x, ...) LOG_UNLIKELY(TRACE, x, ##__VA_ARGS__) 34 | #define log_debug(x, ...) LOG_UNLIKELY(DEBUG, x, ##__VA_ARGS__) 35 | #define log_info(x, ...) LOG(INFO, x, ##__VA_ARGS__) 36 | #define log_warn(x, ...) LOG(WARN, x, ##__VA_ARGS__) 37 | #define log_error(x, ...) LOG(ERROR, x, ##__VA_ARGS__) 38 | #define log_fatal(x, ...) LOG(FATAL, x, ##__VA_ARGS__) 39 | 40 | #define log_error_errno(x, ...) LOG(ERROR, x ": %s", ##__VA_ARGS__, strerror(errno)) 41 | 42 | struct log; 43 | struct log_target; 44 | 45 | attr_printf(4, 5) void log_printf(struct log *, int level, const char *func, 46 | const char *fmt, ...); 47 | 48 | attr_malloc struct log *log_new(void); 49 | /// Destroy a log struct and every log target added to it 50 | attr_nonnull_all void log_destroy(struct log *); 51 | attr_nonnull(1) void log_set_level(struct log *l, int level); 52 | attr_pure enum log_level log_get_level(const struct log *l); 53 | attr_nonnull_all void log_add_target(struct log *, struct log_target *); 54 | attr_pure enum log_level string_to_log_level(const char *); 55 | /// Remove a previously added log target for a log struct, and destroy it. If the log 56 | /// target was never added, nothing happens. 57 | void log_remove_target(struct log *l, struct log_target *tgt); 58 | 59 | extern thread_local struct log *tls_logger; 60 | 61 | /// Create a thread local logger 62 | static inline void log_init_tls(void) { 63 | tls_logger = log_new(); 64 | } 65 | /// Set thread local logger log level 66 | static inline void log_set_level_tls(int level) { 67 | assert(tls_logger); 68 | log_set_level(tls_logger, level); 69 | } 70 | static inline attr_nonnull_all void log_add_target_tls(struct log_target *tgt) { 71 | assert(tls_logger); 72 | log_add_target(tls_logger, tgt); 73 | } 74 | 75 | static inline attr_nonnull_all void log_remove_target_tls(struct log_target *tgt) { 76 | assert(tls_logger); 77 | log_remove_target(tls_logger, tgt); 78 | } 79 | 80 | static inline attr_pure enum log_level log_get_level_tls(void) { 81 | assert(tls_logger); 82 | return log_get_level(tls_logger); 83 | } 84 | 85 | static inline void log_deinit_tls(void) { 86 | assert(tls_logger); 87 | log_destroy(tls_logger); 88 | tls_logger = NULL; 89 | } 90 | 91 | attr_malloc struct log_target *stderr_logger_new(void); 92 | attr_malloc struct log_target *file_logger_new(const char *file); 93 | attr_malloc struct log_target *null_logger_new(void); 94 | attr_malloc struct log_target *gl_string_marker_logger_new(void); 95 | 96 | // vim: set noet sw=8 ts=8: 97 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | libev = dependency('libev', required: false) 2 | if not libev.found() 3 | libev = cc.find_library('ev') 4 | endif 5 | base_deps = [ 6 | cc.find_library('m'), 7 | libev 8 | ] 9 | 10 | srcs = [ files('compfy.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', 'utils.c', 11 | 'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c', 'log.c', 12 | 'options.c', 'event.c', 'cache.c', 'atom.c', 'file_watch.c', 'update.c') ] 13 | compfy_inc = include_directories('.') 14 | 15 | cflags = [] 16 | 17 | required_xcb_packages = [ 18 | 'xcb-render', 'xcb-damage', 'xcb-randr', 'xcb-sync', 'xcb-composite', 19 | 'xcb-shape', 'xcb-xinerama', 'xcb-xfixes', 'xcb-present', 'xcb-glx', 'xcb' 20 | ] 21 | 22 | required_packages = [ 23 | 'x11', 'x11-xcb', 'xcb-renderutil', 'xcb-image', 'xext', 'pixman-1' 24 | ] 25 | 26 | foreach i : required_packages 27 | base_deps += [dependency(i, required: true)] 28 | endforeach 29 | 30 | foreach i : required_xcb_packages 31 | base_deps += [dependency(i, version: '>=1.12.0', required: true)] 32 | endforeach 33 | 34 | if not cc.has_header('uthash.h') 35 | error('Dependency uthash not found') 36 | endif 37 | 38 | deps = [] 39 | 40 | if get_option('update_checks') 41 | cflags += ['-DCONFIG_UPDATES'] 42 | deps += [dependency('json-c', required: true), dependency('libcurl', required: true)] 43 | endif 44 | 45 | if get_option('config_file') 46 | deps += [dependency('libconfig', version: '>=1.4', required: true)] 47 | 48 | cflags += ['-DCONFIG_LIBCONFIG'] 49 | srcs += [ 'config_libconfig.c' ] 50 | endif 51 | if get_option('regex') 52 | pcre = dependency('libpcre2-8', required: true) 53 | cflags += ['-DCONFIG_REGEX_PCRE'] 54 | deps += [pcre] 55 | endif 56 | 57 | if get_option('vsync_drm') 58 | cflags += ['-DCONFIG_VSYNC_DRM'] 59 | deps += [dependency('libdrm', required: true)] 60 | endif 61 | 62 | if get_option('opengl') 63 | cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES'] 64 | deps += [dependency('gl', required: true), dependency('egl', required: true)] 65 | srcs += [ 'opengl.c' ] 66 | endif 67 | 68 | if get_option('dbus') 69 | cflags += ['-DCONFIG_DBUS'] 70 | deps += [dependency('dbus-1', required: true)] 71 | srcs += [ 'dbus.c' ] 72 | endif 73 | 74 | if get_option('xrescheck') 75 | cflags += ['-DDEBUG_XRC'] 76 | srcs += [ 'xrescheck.c' ] 77 | endif 78 | 79 | if get_option('unittest') 80 | cflags += ['-DUNIT_TEST'] 81 | endif 82 | 83 | host_system = host_machine.system() 84 | if host_system == 'linux' 85 | cflags += ['-DHAS_INOTIFY'] 86 | elif (host_system == 'freebsd' or host_system == 'netbsd' or 87 | host_system == 'dragonfly' or host_system == 'openbsd') 88 | cflags += ['-DHAS_KQUEUE'] 89 | endif 90 | 91 | subdir('backend') 92 | 93 | compfy = executable('compfy', srcs, c_args: cflags, 94 | dependencies: [ base_deps, deps ], 95 | install: true, include_directories: compfy_inc) 96 | 97 | if get_option('unittest') 98 | test('compfy unittest', compfy, args: [ '--unittest' ]) 99 | endif 100 | -------------------------------------------------------------------------------- /src/meta.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2019, Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | /// Macro metaprogramming 7 | 8 | #define _APPLY1(a, ...) a(__VA_ARGS__) 9 | #define _APPLY2(a, ...) a(__VA_ARGS__) 10 | #define _APPLY3(a, ...) a(__VA_ARGS__) 11 | #define _APPLY4(a, ...) a(__VA_ARGS__) 12 | 13 | #define RIOTA1(x) x 14 | #define RIOTA2(x) RIOTA1(x##1), RIOTA1(x##0) 15 | #define RIOTA4(x) RIOTA2(x##1), RIOTA2(x##0) 16 | #define RIOTA8(x) RIOTA4(x##1), RIOTA4(x##0) 17 | #define RIOTA16(x) RIOTA8(x##1), RIOTA8(x##0) 18 | /// Generate a list containing 31, 30, ..., 0, in binary 19 | #define RIOTA32(x) RIOTA16(x##1), RIOTA16(x##0) 20 | 21 | #define CONCAT2(a, b) a##b 22 | #define CONCAT1(a, b) CONCAT2(a, b) 23 | #define CONCAT(a, b) CONCAT1(a, b) 24 | 25 | #define _ARGS_HEAD(head, ...) head 26 | #define _ARGS_SKIP4(_1, _2, _3, _4, ...) __VA_ARGS__ 27 | #define _ARGS_SKIP8(...) _APPLY1(_ARGS_SKIP4, _ARGS_SKIP4(__VA_ARGS__)) 28 | #define _ARGS_SKIP16(...) _APPLY2(_ARGS_SKIP8, _ARGS_SKIP8(__VA_ARGS__)) 29 | #define _ARGS_SKIP32(...) _APPLY3(_ARGS_SKIP16, _ARGS_SKIP16(__VA_ARGS__)) 30 | 31 | /// Return the 33rd argument 32 | #define _ARG33(...) _APPLY4(_ARGS_HEAD, _ARGS_SKIP32(__VA_ARGS__)) 33 | 34 | /// Return the number of arguments passed in binary, handles at most 31 elements 35 | #define VA_ARGS_LENGTH(...) _ARG33(0, ##__VA_ARGS__, RIOTA32(0)) 36 | 37 | #define LIST_APPLY_000000(fn, sep, ...) 38 | #define LIST_APPLY_000001(fn, sep, x, ...) fn(x) 39 | #define LIST_APPLY_000010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000001(fn, sep, __VA_ARGS__) 40 | #define LIST_APPLY_000011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000010(fn, sep, __VA_ARGS__) 41 | #define LIST_APPLY_000100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000011(fn, sep, __VA_ARGS__) 42 | #define LIST_APPLY_000101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000100(fn, sep, __VA_ARGS__) 43 | #define LIST_APPLY_000110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000101(fn, sep, __VA_ARGS__) 44 | #define LIST_APPLY_000111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000110(fn, sep, __VA_ARGS__) 45 | #define LIST_APPLY_001000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000111(fn, sep, __VA_ARGS__) 46 | #define LIST_APPLY_001001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001000(fn, sep, __VA_ARGS__) 47 | #define LIST_APPLY_001010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001001(fn, sep, __VA_ARGS__) 48 | #define LIST_APPLY_001011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001010(fn, sep, __VA_ARGS__) 49 | #define LIST_APPLY_001100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001011(fn, sep, __VA_ARGS__) 50 | #define LIST_APPLY_001101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001100(fn, sep, __VA_ARGS__) 51 | #define LIST_APPLY_001110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001101(fn, sep, __VA_ARGS__) 52 | #define LIST_APPLY_001111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001110(fn, sep, __VA_ARGS__) 53 | #define LIST_APPLY_010000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001111(fn, sep, __VA_ARGS__) 54 | #define LIST_APPLY_010001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010000(fn, sep, __VA_ARGS__) 55 | #define LIST_APPLY_010010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010001(fn, sep, __VA_ARGS__) 56 | #define LIST_APPLY_010011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010010(fn, sep, __VA_ARGS__) 57 | #define LIST_APPLY_010100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010011(fn, sep, __VA_ARGS__) 58 | #define LIST_APPLY_010101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010100(fn, sep, __VA_ARGS__) 59 | #define LIST_APPLY_010110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010101(fn, sep, __VA_ARGS__) 60 | #define LIST_APPLY_010111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010110(fn, sep, __VA_ARGS__) 61 | #define LIST_APPLY_011000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010111(fn, sep, __VA_ARGS__) 62 | #define LIST_APPLY_011001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011000(fn, sep, __VA_ARGS__) 63 | #define LIST_APPLY_011010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011001(fn, sep, __VA_ARGS__) 64 | #define LIST_APPLY_011011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011010(fn, sep, __VA_ARGS__) 65 | #define LIST_APPLY_011100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011011(fn, sep, __VA_ARGS__) 66 | #define LIST_APPLY_011101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011100(fn, sep, __VA_ARGS__) 67 | #define LIST_APPLY_011110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011101(fn, sep, __VA_ARGS__) 68 | #define LIST_APPLY_011111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011110(fn, sep, __VA_ARGS__) 69 | #define LIST_APPLY_(N, fn, sep, ...) CONCAT(LIST_APPLY_, N)(fn, sep, __VA_ARGS__) 70 | #define LIST_APPLY(fn, sep, ...) \ 71 | LIST_APPLY_(VA_ARGS_LENGTH(__VA_ARGS__), fn, sep, __VA_ARGS__) 72 | 73 | #define SEP_COMMA() , 74 | #define SEP_COLON() ; 75 | #define SEP_NONE() 76 | -------------------------------------------------------------------------------- /src/opengl.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #pragma once 4 | 5 | #include "common.h" 6 | #include "compiler.h" 7 | #include "log.h" 8 | #include "region.h" 9 | #include "render.h" 10 | #include "win.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | typedef struct { 22 | /// Fragment shader for blur. 23 | GLuint frag_shader; 24 | /// GLSL program for blur. 25 | GLuint prog; 26 | /// Location of uniform "offset_x" in blur GLSL program. 27 | GLint unifm_offset_x; 28 | /// Location of uniform "offset_y" in blur GLSL program. 29 | GLint unifm_offset_y; 30 | /// Location of uniform "factor_center" in blur GLSL program. 31 | GLint unifm_factor_center; 32 | } glx_blur_pass_t; 33 | 34 | typedef struct { 35 | /// Fragment shader for rounded corners. 36 | GLuint frag_shader; 37 | /// GLSL program for rounded corners. 38 | GLuint prog; 39 | /// Location of uniform "radius" in rounded-corners GLSL program. 40 | GLint unifm_radius; 41 | /// Location of uniform "texcoord" in rounded-corners GLSL program. 42 | GLint unifm_texcoord; 43 | /// Location of uniform "texsize" in rounded-corners GLSL program. 44 | GLint unifm_texsize; 45 | /// Location of uniform "borderw" in rounded-corners GLSL program. 46 | GLint unifm_borderw; 47 | /// Location of uniform "borderc" in rounded-corners GLSL program. 48 | GLint unifm_borderc; 49 | /// Location of uniform "resolution" in rounded-corners GLSL program. 50 | GLint unifm_resolution; 51 | /// Location of uniform "texture_scr" in rounded-corners GLSL program. 52 | GLint unifm_tex_scr; 53 | 54 | } glx_round_pass_t; 55 | 56 | /// Structure containing GLX-dependent data for a session. 57 | typedef struct glx_session { 58 | // === OpenGL related === 59 | /// GLX context. 60 | GLXContext context; 61 | /// Whether we have GL_ARB_texture_non_power_of_two. 62 | bool has_texture_non_power_of_two; 63 | /// Current GLX Z value. 64 | int z; 65 | glx_blur_pass_t *blur_passes; 66 | glx_round_pass_t *round_passes; 67 | } glx_session_t; 68 | 69 | /// @brief Wrapper of a binded GLX texture. 70 | typedef struct _glx_texture { 71 | GLuint texture; 72 | GLXPixmap glpixmap; 73 | xcb_pixmap_t pixmap; 74 | GLenum target; 75 | int width; 76 | int height; 77 | bool y_inverted; 78 | } glx_texture_t; 79 | 80 | #define CGLX_SESSION_INIT \ 81 | { .context = NULL } 82 | 83 | bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z, 84 | GLfloat factor, const region_t *reg_tgt); 85 | 86 | bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, int dy, 87 | int width, int height, int z, double opacity, bool argb, bool neg, 88 | const region_t *reg_tgt, const glx_prog_main_t *pprogram); 89 | 90 | bool glx_init(session_t *ps, bool need_render); 91 | 92 | void glx_destroy(session_t *ps); 93 | 94 | void glx_on_root_change(session_t *ps); 95 | 96 | bool glx_init_blur(session_t *ps); 97 | 98 | bool glx_init_rounded_corners(session_t *ps); 99 | 100 | #ifdef CONFIG_OPENGL 101 | bool glx_load_prog_main(const char *vshader_str, const char *fshader_str, 102 | glx_prog_main_t *pprogram); 103 | #endif 104 | 105 | bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, int width, 106 | int height, bool repeat, const struct glx_fbconfig_info *); 107 | 108 | void glx_release_pixmap(session_t *ps, glx_texture_t *ptex); 109 | 110 | bool glx_bind_texture(session_t *ps, glx_texture_t **pptex, int x, int y, int width, int height); 111 | 112 | void glx_paint_pre(session_t *ps, region_t *preg) attr_nonnull(1, 2); 113 | 114 | /** 115 | * Check if a texture is binded, or is binded to the given pixmap. 116 | */ 117 | static inline bool glx_tex_binded(const glx_texture_t *ptex, xcb_pixmap_t pixmap) { 118 | return ptex && ptex->glpixmap && ptex->texture && (!pixmap || pixmap == ptex->pixmap); 119 | } 120 | 121 | void glx_set_clip(session_t *ps, const region_t *reg); 122 | 123 | bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, 124 | GLfloat factor_center, const region_t *reg_tgt, glx_blur_cache_t *pbc); 125 | 126 | bool glx_round_corners_dst(session_t *ps, struct managed_win *w, 127 | const glx_texture_t *ptex, int dx, int dy, int width, 128 | int height, float z, float cr, const region_t *reg_tgt); 129 | 130 | GLuint glx_create_shader(GLenum shader_type, const char *shader_str); 131 | 132 | GLuint glx_create_program(const GLuint *const shaders, int nshaders); 133 | 134 | GLuint glx_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str); 135 | 136 | unsigned char *glx_take_screenshot(session_t *ps, int *out_length); 137 | 138 | /** 139 | * Check if there's a GLX context. 140 | */ 141 | static inline bool glx_has_context(session_t *ps) { 142 | return ps->psglx && ps->psglx->context; 143 | } 144 | 145 | /** 146 | * Ensure we have a GLX context. 147 | */ 148 | static inline bool ensure_glx_context(session_t *ps) { 149 | // Create GLX context 150 | if (!glx_has_context(ps)) 151 | glx_init(ps, false); 152 | 153 | return ps->psglx->context; 154 | } 155 | 156 | /** 157 | * Free a GLX texture. 158 | */ 159 | static inline void free_texture_r(session_t *ps attr_unused, GLuint *ptexture) { 160 | if (*ptexture) { 161 | assert(glx_has_context(ps)); 162 | glDeleteTextures(1, ptexture); 163 | *ptexture = 0; 164 | } 165 | } 166 | 167 | /** 168 | * Free a GLX Framebuffer object. 169 | */ 170 | static inline void free_glx_fbo(GLuint *pfbo) { 171 | if (*pfbo) { 172 | glDeleteFramebuffers(1, pfbo); 173 | *pfbo = 0; 174 | } 175 | assert(!*pfbo); 176 | } 177 | 178 | /** 179 | * Free data in glx_blur_cache_t on resize. 180 | */ 181 | static inline void free_glx_bc_resize(session_t *ps, glx_blur_cache_t *pbc) { 182 | free_texture_r(ps, &pbc->textures[0]); 183 | free_texture_r(ps, &pbc->textures[1]); 184 | pbc->width = 0; 185 | pbc->height = 0; 186 | } 187 | 188 | /** 189 | * Free a glx_blur_cache_t 190 | */ 191 | static inline void free_glx_bc(session_t *ps, glx_blur_cache_t *pbc) { 192 | free_glx_fbo(&pbc->fbo); 193 | free_glx_bc_resize(ps, pbc); 194 | } 195 | 196 | /** 197 | * Free a glx_texture_t. 198 | */ 199 | static inline void free_texture(session_t *ps, glx_texture_t **pptex) { 200 | glx_texture_t *ptex = *pptex; 201 | 202 | // Quit if there's nothing 203 | if (!ptex) { 204 | return; 205 | } 206 | 207 | glx_release_pixmap(ps, ptex); 208 | 209 | free_texture_r(ps, &ptex->texture); 210 | 211 | // Free structure itself 212 | free(ptex); 213 | *pptex = NULL; 214 | } 215 | 216 | /** 217 | * Free GLX part of paint_t. 218 | */ 219 | static inline void free_paint_glx(session_t *ps, paint_t *ppaint) { 220 | free_texture(ps, &ppaint->ptex); 221 | #ifdef CONFIG_OPENGL 222 | free(ppaint->fbcfg); 223 | #endif 224 | ppaint->fbcfg = NULL; 225 | } 226 | 227 | /** 228 | * Free GLX part of win. 229 | */ 230 | static inline void free_win_res_glx(session_t *ps, struct managed_win *w) { 231 | free_paint_glx(ps, &w->paint); 232 | free_paint_glx(ps, &w->shadow_paint); 233 | #ifdef CONFIG_OPENGL 234 | free_glx_bc(ps, &w->glx_blur_cache); 235 | free_texture(ps, &w->glx_texture_bg); 236 | #endif 237 | } 238 | -------------------------------------------------------------------------------- /src/options.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | #pragma once 4 | 5 | /// Parse command line options 6 | 7 | #include 8 | #include // for xcb_render_fixed_t 9 | 10 | #include "compiler.h" 11 | #include "config.h" 12 | #include "types.h" 13 | #include "win.h" // for wintype_t 14 | 15 | typedef struct session session_t; 16 | 17 | /// Get config options that are needed to parse the rest of the options 18 | /// Return true if we should quit 19 | bool get_early_config(int argc, char *const *argv, char **config_file, bool *all_xerrors, 20 | bool *fork, int *exit_code); 21 | 22 | /** 23 | * Process arguments and configuration files. 24 | * 25 | * Parameters: 26 | * shadow_enable = Carry overs from parse_config 27 | * fading_enable 28 | * conv_kern_hasneg 29 | * winopt_mask 30 | * Returns: 31 | * Whether configuration are processed successfully. 32 | */ 33 | bool must_use get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, 34 | bool fading_enable, bool conv_kern_hasneg, 35 | win_option_mask_t *winopt_mask); 36 | 37 | // vim: set noet sw=8 ts=8: 38 | -------------------------------------------------------------------------------- /src/region.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2018 Yuxuan Shui 3 | #pragma once 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "log.h" 10 | #include "utils.h" 11 | 12 | typedef struct pixman_region32 pixman_region32_t; 13 | typedef struct pixman_box32 pixman_box32_t; 14 | typedef pixman_region32_t region_t; 15 | typedef pixman_box32_t rect_t; 16 | 17 | RC_TYPE(region_t, rc_region, pixman_region32_init, pixman_region32_fini, static inline) 18 | 19 | static inline void dump_region(const region_t *x) { 20 | if (log_get_level_tls() < LOG_LEVEL_TRACE) { 21 | return; 22 | } 23 | int nrects; 24 | const rect_t *rects = pixman_region32_rectangles((region_t *)x, &nrects); 25 | log_trace("nrects: %d", nrects); 26 | for (int i = 0; i < nrects; i++) 27 | log_trace("(%d, %d) - (%d, %d)", rects[i].x1, rects[i].y1, rects[i].x2, 28 | rects[i].y2); 29 | } 30 | 31 | /// Convert one xcb rectangle to our rectangle type 32 | static inline rect_t from_x_rect(const xcb_rectangle_t *rect) { 33 | return (rect_t){ 34 | .x1 = rect->x, 35 | .y1 = rect->y, 36 | .x2 = rect->x + rect->width, 37 | .y2 = rect->y + rect->height, 38 | }; 39 | } 40 | 41 | /// Convert an array of xcb rectangles to our rectangle type 42 | /// Returning an array that needs to be freed 43 | static inline rect_t *from_x_rects(int nrects, const xcb_rectangle_t *rects) { 44 | rect_t *ret = ccalloc(nrects, rect_t); 45 | for (int i = 0; i < nrects; i++) { 46 | ret[i] = from_x_rect(rects + i); 47 | } 48 | return ret; 49 | } 50 | 51 | /** 52 | * Resize a region. 53 | */ 54 | static inline void _resize_region(const region_t *region, region_t *output, int dx, 55 | int dy) { 56 | if (!region || !output) { 57 | return; 58 | } 59 | if (!dx && !dy) { 60 | if (region != output) { 61 | pixman_region32_copy(output, (region_t *)region); 62 | } 63 | return; 64 | } 65 | // Loop through all rectangles 66 | int nrects; 67 | int nnewrects = 0; 68 | const rect_t *rects = pixman_region32_rectangles((region_t *)region, &nrects); 69 | auto newrects = ccalloc(nrects, rect_t); 70 | for (int i = 0; i < nrects; i++) { 71 | int x1 = rects[i].x1 - dx; 72 | int y1 = rects[i].y1 - dy; 73 | int x2 = rects[i].x2 + dx; 74 | int y2 = rects[i].y2 + dy; 75 | int wid = x2 - x1; 76 | int hei = y2 - y1; 77 | if (wid <= 0 || hei <= 0) { 78 | continue; 79 | } 80 | newrects[nnewrects] = 81 | (rect_t){.x1 = x1, .x2 = x2, .y1 = y1, .y2 = y2}; 82 | ++nnewrects; 83 | } 84 | 85 | pixman_region32_fini(output); 86 | pixman_region32_init_rects(output, newrects, nnewrects); 87 | 88 | free(newrects); 89 | } 90 | 91 | static inline region_t resize_region(const region_t *region, int dx, int dy) { 92 | region_t ret; 93 | pixman_region32_init(&ret); 94 | _resize_region(region, &ret, dx, dy); 95 | return ret; 96 | } 97 | 98 | static inline void resize_region_in_place(region_t *region, int dx, int dy) { 99 | return _resize_region(region, region, dx, dy); 100 | } 101 | -------------------------------------------------------------------------------- /src/render.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #ifdef CONFIG_OPENGL 9 | #include "backend/gl/glx.h" 10 | #endif 11 | #include "region.h" 12 | 13 | typedef struct _glx_texture glx_texture_t; 14 | typedef struct glx_prog_main glx_prog_main_t; 15 | typedef struct session session_t; 16 | 17 | struct managed_win; 18 | 19 | typedef struct paint { 20 | xcb_pixmap_t pixmap; 21 | xcb_render_picture_t pict; 22 | glx_texture_t *ptex; 23 | #ifdef CONFIG_OPENGL 24 | struct glx_fbconfig_info *fbcfg; 25 | #endif 26 | } paint_t; 27 | 28 | typedef struct clip { 29 | xcb_render_picture_t pict; 30 | int x; 31 | int y; 32 | } clip_t; 33 | 34 | void render(session_t *ps, int x, int y, int dx, int dy, int w, int h, int fullw, 35 | int fullh, double opacity, bool argb, bool neg, int cr, 36 | xcb_render_picture_t pict, glx_texture_t *ptex, const region_t *reg_paint, 37 | const glx_prog_main_t *pprogram, clip_t *clip); 38 | void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint); 39 | 40 | void paint_all(session_t *ps, struct managed_win *const t, bool ignore_damage); 41 | 42 | void free_picture(xcb_connection_t *c, xcb_render_picture_t *p); 43 | 44 | void free_paint(session_t *ps, paint_t *ppaint); 45 | void free_root_tile(session_t *ps); 46 | 47 | bool init_render(session_t *ps); 48 | void deinit_render(session_t *ps); 49 | 50 | int maximum_buffer_age(session_t *); 51 | -------------------------------------------------------------------------------- /src/string_utils.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #include 5 | 6 | #include "compiler.h" 7 | #include "string_utils.h" 8 | #include "utils.h" 9 | 10 | #pragma GCC diagnostic push 11 | 12 | // gcc warns about legitimate strncpy in mstrjoin and mstrextend 13 | // strncpy(str, src1, len1) intentional truncates the null byte from src1. 14 | // strncpy(str+len1, src2, len2) uses bound depends on the source argument, 15 | // but str is allocated with len1+len2+1, so this strncpy can't overflow 16 | #pragma GCC diagnostic ignored "-Wpragmas" 17 | #pragma GCC diagnostic ignored "-Wstringop-truncation" 18 | #pragma GCC diagnostic ignored "-Wstringop-overflow" 19 | 20 | /** 21 | * Allocate the space and join two strings. 22 | */ 23 | char *mstrjoin(const char *src1, const char *src2) { 24 | auto len1 = strlen(src1); 25 | auto len2 = strlen(src2); 26 | auto len = len1 + len2 + 1; 27 | auto str = ccalloc(len, char); 28 | 29 | strncpy(str, src1, len1); 30 | strncpy(str + len1, src2, len2); 31 | str[len - 1] = '\0'; 32 | 33 | return str; 34 | } 35 | 36 | /** 37 | * Concatenate a string on heap with another string. 38 | */ 39 | void mstrextend(char **psrc1, const char *src2) { 40 | if (!*psrc1) { 41 | *psrc1 = strdup(src2); 42 | return; 43 | } 44 | 45 | auto len1 = strlen(*psrc1); 46 | auto len2 = strlen(src2); 47 | auto len = len1 + len2 + 1; 48 | *psrc1 = crealloc(*psrc1, len); 49 | 50 | strncpy(*psrc1 + len1, src2, len2); 51 | (*psrc1)[len - 1] = '\0'; 52 | } 53 | 54 | #pragma GCC diagnostic pop 55 | 56 | /// Parse a floating point number of form (+|-)?[0-9]*(\.[0-9]*) 57 | double strtod_simple(const char *src, const char **end) { 58 | double neg = 1; 59 | if (*src == '-') { 60 | neg = -1; 61 | src++; 62 | } else if (*src == '+') { 63 | src++; 64 | } 65 | 66 | double ret = 0; 67 | while (*src >= '0' && *src <= '9') { 68 | ret = ret * 10 + (*src - '0'); 69 | src++; 70 | } 71 | 72 | if (*src == '.') { 73 | double frac = 0, mult = 0.1; 74 | src++; 75 | while (*src >= '0' && *src <= '9') { 76 | frac += mult * (*src - '0'); 77 | mult *= 0.1; 78 | src++; 79 | } 80 | ret += frac; 81 | } 82 | 83 | *end = src; 84 | return ret * neg; 85 | } 86 | 87 | const char *trim_both(const char *src, size_t *length) { 88 | size_t i = 0; 89 | while (isspace(src[i])) { 90 | i++; 91 | } 92 | size_t j = strlen(src) - 1; 93 | while (j > i && isspace(src[j])) { 94 | j--; 95 | } 96 | *length = j - i + 1; 97 | return src + i; 98 | } 99 | -------------------------------------------------------------------------------- /src/string_utils.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | #pragma once 4 | #include 5 | #include 6 | 7 | #include "compiler.h" 8 | 9 | #define mstrncmp(s1, s2) strncmp((s1), (s2), strlen(s1)) 10 | 11 | char *mstrjoin(const char *src1, const char *src2); 12 | char *mstrjoin3(const char *src1, const char *src2, const char *src3); 13 | void mstrextend(char **psrc1, const char *src2); 14 | const char *trim_both(const char *src, size_t *length); 15 | 16 | /// Parse a floating point number of form (+|-)?[0-9]*(\.[0-9]*) 17 | double strtod_simple(const char *, const char **); 18 | 19 | static inline int uitostr(unsigned int n, char *buf) { 20 | int ret = 0; 21 | unsigned int tmp = n; 22 | while (tmp > 0) { 23 | tmp /= 10; 24 | ret++; 25 | } 26 | 27 | if (ret == 0) 28 | ret = 1; 29 | 30 | int pos = ret; 31 | while (pos--) { 32 | buf[pos] = (char)(n % 10 + '0'); 33 | n /= 10; 34 | } 35 | return ret; 36 | } 37 | 38 | static inline const char *skip_space_const(const char *src) { 39 | if (!src) 40 | return NULL; 41 | while (*src && isspace((unsigned char)*src)) 42 | src++; 43 | return src; 44 | } 45 | 46 | static inline char *skip_space_mut(char *src) { 47 | if (!src) 48 | return NULL; 49 | while (*src && isspace((unsigned char)*src)) 50 | src++; 51 | return src; 52 | } 53 | 54 | #define skip_space(x) \ 55 | _Generic((x), char * : skip_space_mut, const char * : skip_space_const)(x) 56 | -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2018 Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | /// Some common types 7 | 8 | #include 9 | 10 | /// Enumeration type to represent switches. 11 | typedef enum { 12 | OFF = 0, // false 13 | ON, // true 14 | UNSET 15 | } switch_t; 16 | 17 | /// A structure representing margins around a rectangle. 18 | typedef struct { 19 | int top; 20 | int left; 21 | int bottom; 22 | int right; 23 | } margin_t; 24 | 25 | struct color { 26 | double red, green, blue, alpha; 27 | }; 28 | 29 | typedef uint32_t opacity_t; 30 | 31 | #define MARGIN_INIT \ 32 | { 0, 0, 0, 0 } 33 | -------------------------------------------------------------------------------- /src/update.c: -------------------------------------------------------------------------------- 1 | #ifdef CONFIG_UPDATES 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // Callback function to handle the response data 9 | size_t write_callback_updater(void* contents, size_t size, size_t nmemb, void* user_data) { 10 | size_t real_size = size * nmemb; 11 | ((char*)user_data)[real_size] = '\0'; // Null-terminate the string 12 | strcat(user_data, (const char*)contents); 13 | return real_size; 14 | } 15 | 16 | // Function to fetch the latest release tag from a GitHub repository 17 | char* get_latest_release_tag(const char* owner, const char* repo) { 18 | CURL* curl; 19 | CURLcode res; 20 | 21 | // Initialize libcurl 22 | curl_global_init(CURL_GLOBAL_DEFAULT); 23 | curl = curl_easy_init(); 24 | 25 | if (curl) { 26 | // Set the GitHub API URL with a User-Agent header 27 | char url[100]; 28 | snprintf(url, sizeof(url), "https://api.github.com/repos/%s/%s/releases/latest", owner, repo); 29 | curl_easy_setopt(curl, CURLOPT_URL, url); 30 | 31 | // Add a User-Agent header to the request 32 | curl_easy_setopt(curl, CURLOPT_USERAGENT, "Compfy_Fetch_Version"); 33 | 34 | // Set up a buffer to store the response data 35 | char response_buffer[4096] = {0}; 36 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback_updater); 37 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, response_buffer); 38 | 39 | // Perform the HTTP request 40 | res = curl_easy_perform(curl); 41 | 42 | // Check for errors 43 | if (res != CURLE_OK) { 44 | fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); 45 | } else { 46 | // Print the response for debugging 47 | // printf("Response: %s\n", response_buffer); 48 | 49 | // Parse the JSON response 50 | struct json_object* json = json_tokener_parse(response_buffer); 51 | if (json == NULL) { 52 | fprintf(stderr, "Error parsing JSON response\n"); 53 | } else { 54 | struct json_object* tag_name; 55 | json_object_object_get_ex(json, "tag_name", &tag_name); 56 | 57 | // Clean up libcurl 58 | curl_easy_cleanup(curl); 59 | 60 | // Return the latest release tag 61 | char* tag = strdup(json_object_get_string(tag_name)); 62 | // printf("Tag: %s\n", tag); 63 | return tag; 64 | } 65 | } 66 | } 67 | 68 | // Clean up libcurl in case of failure 69 | curl_easy_cleanup(curl); 70 | return NULL; 71 | } 72 | 73 | int check_for_updates() { 74 | const char* owner = "allusive-dev"; 75 | const char* repo = "compfy"; 76 | 77 | char* latest_tag = get_latest_release_tag(owner, repo); 78 | 79 | if (latest_tag) { 80 | printf("Your current version is: %s\n", COMPFY_VERSION); 81 | printf("The latest version is: %s\n", latest_tag); 82 | free(latest_tag); 83 | } else { 84 | printf("Unable to fetch latest release.\n"); 85 | } 86 | 87 | return 0; 88 | } 89 | #endif -------------------------------------------------------------------------------- /src/uthash_extra.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define HASH_ITER2(head, el) \ 6 | for (__typeof__(head) el = (head), __tmp = el != NULL ? el->hh.next : NULL; \ 7 | el != NULL; el = __tmp, __tmp = el != NULL ? el->hh.next : NULL) 8 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "compiler.h" 6 | #include "string_utils.h" 7 | #include "utils.h" 8 | 9 | /// Report allocation failure without allocating memory 10 | void report_allocation_failure(const char *func, const char *file, unsigned int line) { 11 | // Since memory allocation failed, we try to print this error message without any 12 | // memory allocation. Since logging framework allocates memory (and might even 13 | // have not been initialized yet), so we can't use it. 14 | char buf[11]; 15 | int llen = uitostr(line, buf); 16 | const char msg1[] = " has failed to allocate memory, "; 17 | const char msg2[] = ". Aborting...\n"; 18 | const struct iovec v[] = { 19 | {.iov_base = (void *)func, .iov_len = strlen(func)}, 20 | {.iov_base = "()", .iov_len = 2}, 21 | {.iov_base = (void *)msg1, .iov_len = sizeof(msg1) - 1}, 22 | {.iov_base = "at ", .iov_len = 3}, 23 | {.iov_base = (void *)file, .iov_len = strlen(file)}, 24 | {.iov_base = ":", .iov_len = 1}, 25 | {.iov_base = buf, .iov_len = (size_t)llen}, 26 | {.iov_base = (void *)msg2, .iov_len = sizeof(msg2) - 1}, 27 | }; 28 | 29 | writev(STDERR_FILENO, v, ARR_SIZE(v)); 30 | abort(); 31 | 32 | unreachable; 33 | } 34 | 35 | /// 36 | /// Calculates next closest power of two of 32bit integer n 37 | /// ref: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 38 | /// 39 | int next_power_of_two(int n) { 40 | n--; 41 | n |= n >> 1; 42 | n |= n >> 2; 43 | n |= n >> 4; 44 | n |= n >> 8; 45 | n |= n >> 16; 46 | n++; 47 | return n; 48 | } 49 | 50 | // vim: set noet sw=8 ts=8 : 51 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2018 Yuxuan Shui 3 | #pragma once 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include "compiler.h" 18 | #include "types.h" 19 | 20 | #define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) 21 | 22 | #ifdef __FAST_MATH__ 23 | #warning Use of -ffast-math can cause rendering error or artifacts, \ 24 | therefore it is not recommended. 25 | #endif 26 | 27 | #ifdef __clang__ 28 | __attribute__((optnone)) 29 | #else 30 | __attribute__((optimize("-fno-fast-math"))) 31 | #endif 32 | static inline bool 33 | safe_isnan(double a) { 34 | return __builtin_isnan(a); 35 | } 36 | 37 | /// Same as assert(false), but make sure we abort _even in release builds_. 38 | /// Silence compiler warning caused by release builds making some code paths reachable. 39 | #define BUG() \ 40 | do { \ 41 | assert(false); \ 42 | abort(); \ 43 | } while (0) 44 | #define CHECK_EXPR(...) ((void)0) 45 | /// Same as assert, but evaluates the expression even in release builds 46 | #define CHECK(expr) \ 47 | do { \ 48 | auto _ = (expr); \ 49 | /* make sure the original expression appears in the assertion message */ \ 50 | assert((CHECK_EXPR(expr), _)); \ 51 | (void)_; \ 52 | } while (0) 53 | 54 | /// Asserts that var is within [lower, upper]. Silence compiler warning about expressions 55 | /// being always true or false. 56 | #define ASSERT_IN_RANGE(var, lower, upper) \ 57 | do { \ 58 | auto __tmp attr_unused = (var); \ 59 | _Pragma("GCC diagnostic push"); \ 60 | _Pragma("GCC diagnostic ignored \"-Wtype-limits\""); \ 61 | assert(__tmp >= lower); \ 62 | assert(__tmp <= upper); \ 63 | _Pragma("GCC diagnostic pop"); \ 64 | } while (0) 65 | 66 | /// Asserts that var >= lower. Silence compiler warning about expressions 67 | /// being always true or false. 68 | #define ASSERT_GEQ(var, lower) \ 69 | do { \ 70 | auto __tmp attr_unused = (var); \ 71 | _Pragma("GCC diagnostic push"); \ 72 | _Pragma("GCC diagnostic ignored \"-Wtype-limits\""); \ 73 | assert(__tmp >= lower); \ 74 | _Pragma("GCC diagnostic pop"); \ 75 | } while (0) 76 | 77 | // Some macros for checked cast 78 | // Note these macros are not complete, as in, they won't work for every integer types. But 79 | // they are good enough for our use cases. 80 | 81 | #define to_int_checked(val) \ 82 | ({ \ 83 | int64_t __to_tmp = (val); \ 84 | ASSERT_IN_RANGE(__to_tmp, INT_MIN, INT_MAX); \ 85 | (int)__to_tmp; \ 86 | }) 87 | 88 | #define to_char_checked(val) \ 89 | ({ \ 90 | int64_t __to_tmp = (val); \ 91 | ASSERT_IN_RANGE(__to_tmp, CHAR_MIN, CHAR_MAX); \ 92 | (char)__to_tmp; \ 93 | }) 94 | 95 | #define to_u16_checked(val) \ 96 | ({ \ 97 | auto __to_tmp = (val); \ 98 | ASSERT_IN_RANGE(__to_tmp, 0, UINT16_MAX); \ 99 | (uint16_t) __to_tmp; \ 100 | }) 101 | 102 | #define to_i16_checked(val) \ 103 | ({ \ 104 | int64_t __to_tmp = (val); \ 105 | ASSERT_IN_RANGE(__to_tmp, INT16_MIN, INT16_MAX); \ 106 | (int16_t) __to_tmp; \ 107 | }) 108 | 109 | #define to_u32_checked(val) \ 110 | ({ \ 111 | auto __to_tmp = (val); \ 112 | int64_t max attr_unused = UINT32_MAX; /* silence clang tautological \ 113 | comparison warning*/ \ 114 | ASSERT_IN_RANGE(__to_tmp, 0, max); \ 115 | (uint32_t) __to_tmp; \ 116 | }) 117 | /** 118 | * Normalize an int value to a specific range. 119 | * 120 | * @param i int value to normalize 121 | * @param min minimal value 122 | * @param max maximum value 123 | * @return normalized value 124 | */ 125 | static inline int attr_const normalize_i_range(int i, int min, int max) { 126 | if (i > max) 127 | return max; 128 | if (i < min) 129 | return min; 130 | return i; 131 | } 132 | 133 | #define min2(a, b) ((a) > (b) ? (b) : (a)) 134 | #define max2(a, b) ((a) > (b) ? (a) : (b)) 135 | #define min3(a, b, c) min2(a, min2(b, c)) 136 | 137 | /// clamp `val` into interval [min, max] 138 | #define clamp(val, min, max) max2(min2(val, max), min) 139 | 140 | /** 141 | * Normalize a double value to a specific range. 142 | * 143 | * @param d double value to normalize 144 | * @param min minimal value 145 | * @param max maximum value 146 | * @return normalized value 147 | */ 148 | static inline double attr_const normalize_d_range(double d, double min, double max) { 149 | if (d > max) 150 | return max; 151 | if (d < min) 152 | return min; 153 | return d; 154 | } 155 | 156 | /** 157 | * Normalize a double value to 0.\ 0 - 1.\ 0. 158 | * 159 | * @param d double value to normalize 160 | * @return normalized value 161 | */ 162 | static inline double attr_const normalize_d(double d) { 163 | return normalize_d_range(d, 0.0, 1.0); 164 | } 165 | 166 | /** 167 | * Convert a hex RGB string to RGB 168 | */ 169 | static inline struct color hex_to_rgb(const char *hex) { 170 | struct color rgb; 171 | // Ignore the # in front of the string 172 | const char *sane_hex = hex + 1; 173 | int hex_color = (int)strtol(sane_hex, NULL, 16); 174 | rgb.red = (float)(hex_color >> 16) / 256; 175 | rgb.green = (float)((hex_color & 0x00ff00) >> 8) / 256; 176 | rgb.blue = (float)(hex_color & 0x0000ff) / 256; 177 | 178 | return rgb; 179 | } 180 | 181 | attr_noret void 182 | report_allocation_failure(const char *func, const char *file, unsigned int line); 183 | 184 | /** 185 | * @brief Quit if the passed-in pointer is empty. 186 | */ 187 | static inline void * 188 | allocchk_(const char *func_name, const char *file, unsigned int line, void *ptr) { 189 | if (unlikely(!ptr)) { 190 | report_allocation_failure(func_name, file, line); 191 | } 192 | return ptr; 193 | } 194 | 195 | /// @brief Wrapper of allocchk_(). 196 | #define allocchk(ptr) allocchk_(__func__, __FILE__, __LINE__, ptr) 197 | 198 | /// @brief Wrapper of malloc(). 199 | #define cmalloc(type) ((type *)allocchk(malloc(sizeof(type)))) 200 | 201 | /// @brief Wrapper of malloc() that takes a size 202 | #define cvalloc(size) allocchk(malloc(size)) 203 | 204 | /// @brief Wrapper of calloc(). 205 | #define ccalloc(nmemb, type) \ 206 | ({ \ 207 | auto tmp = (nmemb); \ 208 | ASSERT_GEQ(tmp, 0); \ 209 | ((type *)allocchk(calloc((size_t)tmp, sizeof(type)))); \ 210 | }) 211 | 212 | /// @brief Wrapper of ealloc(). 213 | #define crealloc(ptr, nmemb) \ 214 | ({ \ 215 | auto tmp = (nmemb); \ 216 | ASSERT_GEQ(tmp, 0); \ 217 | ((__typeof__(ptr))allocchk(realloc((ptr), (size_t)tmp * sizeof(*(ptr))))); \ 218 | }) 219 | 220 | /// RC_TYPE generates a reference counted type from `type` 221 | /// 222 | /// parameters: 223 | /// name = the generated type will be called `name`_t. 224 | /// ctor = the constructor of `type`, will be called when 225 | /// a value of `type` is created. should take one 226 | /// argument of `type *`. 227 | /// dtor = the destructor. will be called when all reference 228 | /// is gone. has same signature as ctor 229 | /// Q = function qualifier. this is the qualifier that 230 | /// will be put before generated functions 231 | // 232 | /// functions generated: 233 | /// `name`_new: create a new reference counted object of `type` 234 | /// `name`_ref: increment the reference counter, return a 235 | /// reference to the object 236 | /// `name`_unref: decrement the reference counter. take a `type **` 237 | /// because it needs to nullify the reference. 238 | #define RC_TYPE(type, name, ctor, dtor, Q) \ 239 | typedef struct { \ 240 | type inner; \ 241 | int ref_count; \ 242 | } name##_internal_t; \ 243 | typedef type name##_t; \ 244 | Q type *name##_new(void) { \ 245 | name##_internal_t *ret = cmalloc(name##_internal_t); \ 246 | ctor((type *)ret); \ 247 | ret->ref_count = 1; \ 248 | return (type *)ret; \ 249 | } \ 250 | Q type *name##_ref(type *a) { \ 251 | __auto_type b = (name##_internal_t *)a; \ 252 | b->ref_count++; \ 253 | return a; \ 254 | } \ 255 | Q void name##_unref(type **a) { \ 256 | __auto_type b = (name##_internal_t *)*a; \ 257 | if (!b) \ 258 | return; \ 259 | b->ref_count--; \ 260 | if (!b->ref_count) { \ 261 | dtor((type *)b); \ 262 | free(b); \ 263 | } \ 264 | *a = NULL; \ 265 | } 266 | 267 | /// Generate prototypes for functions generated by RC_TYPE 268 | #define RC_TYPE_PROTO(type, name) \ 269 | typedef type name##_t; \ 270 | type *name##_new(void); \ 271 | void name##_ref(type *a); \ 272 | void name##_unref(type **a); 273 | 274 | static inline void free_charpp(char **str) { 275 | if (str) { 276 | free(*str); 277 | *str = NULL; 278 | } 279 | } 280 | 281 | /// An allocated char* that is automatically freed when it goes out of scope. 282 | #define scoped_charp char *cleanup(free_charpp) 283 | 284 | /// 285 | /// Calculates next closest power of two of 32bit integer n 286 | /// ref: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 287 | /// 288 | int next_power_of_two(int n); 289 | 290 | // Some versions of the Android libc do not have timespec_get(), use 291 | // clock_gettime() instead. 292 | #ifdef __ANDROID__ 293 | 294 | #ifndef TIME_UTC 295 | #define TIME_UTC 1 296 | #endif 297 | 298 | static inline int timespec_get(struct timespec *ts, int base) { 299 | assert(base == TIME_UTC); 300 | return clock_gettime(CLOCK_REALTIME, ts); 301 | } 302 | #endif 303 | 304 | // vim: set noet sw=8 ts=8 : 305 | -------------------------------------------------------------------------------- /src/vsync.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | /// Function pointers to init VSync modes. 5 | 6 | #include "common.h" 7 | #include "log.h" 8 | 9 | #ifdef CONFIG_OPENGL 10 | #include "backend/gl/glx.h" 11 | #include "opengl.h" 12 | #endif 13 | 14 | #ifdef CONFIG_VSYNC_DRM 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #endif 22 | 23 | #include "config.h" 24 | #include "vsync.h" 25 | 26 | #ifdef CONFIG_VSYNC_DRM 27 | /** 28 | * Wait for next VSync, DRM method. 29 | * 30 | * Stolen from: 31 | * https://github.com/MythTV/mythtv/blob/master/mythtv/libs/libmythtv/vsync.cpp 32 | */ 33 | static int vsync_drm_wait(session_t *ps) { 34 | int ret = -1; 35 | drm_wait_vblank_t vbl; 36 | 37 | vbl.request.type = _DRM_VBLANK_RELATIVE, vbl.request.sequence = 1; 38 | 39 | do { 40 | ret = ioctl(ps->drm_fd, DRM_IOCTL_WAIT_VBLANK, &vbl); 41 | vbl.request.type &= ~(uint)_DRM_VBLANK_RELATIVE; 42 | } while (ret && errno == EINTR); 43 | 44 | if (ret) 45 | log_error("VBlank ioctl did not work, unimplemented in this drmver?"); 46 | 47 | return ret; 48 | } 49 | 50 | /** 51 | * Initialize DRM VSync. 52 | * 53 | * @return true for success, false otherwise 54 | */ 55 | static bool vsync_drm_init(session_t *ps) { 56 | // Should we always open card0? 57 | if (ps->drm_fd < 0 && (ps->drm_fd = open("/dev/dri/card0", O_RDWR)) < 0) { 58 | log_error("Failed to open device."); 59 | return false; 60 | } 61 | 62 | if (vsync_drm_wait(ps)) 63 | return false; 64 | 65 | return true; 66 | } 67 | #endif 68 | 69 | #ifdef CONFIG_OPENGL 70 | /** 71 | * Initialize OpenGL VSync. 72 | * 73 | * Stolen from: 74 | * http://git.tuxfamily.org/?p=ccm/cairocompmgr.git;a=commitdiff;h=efa4ceb97da501e8630ca7f12c99b1dce853c73e 75 | * Possible original source: http://www.inb.uni-luebeck.de/~boehme/xvideo_sync.html 76 | * 77 | * @return true for success, false otherwise 78 | */ 79 | static bool vsync_opengl_init(session_t *ps) { 80 | if (!ensure_glx_context(ps)) 81 | return false; 82 | 83 | return glxext.has_GLX_SGI_video_sync; 84 | } 85 | 86 | static bool vsync_opengl_oml_init(session_t *ps) { 87 | if (!ensure_glx_context(ps)) 88 | return false; 89 | 90 | return glxext.has_GLX_OML_sync_control; 91 | } 92 | 93 | static inline bool vsync_opengl_swc_swap_interval(session_t *ps, int interval) { 94 | if (glxext.has_GLX_MESA_swap_control) 95 | return glXSwapIntervalMESA((uint)interval) == 0; 96 | else if (glxext.has_GLX_SGI_swap_control) 97 | return glXSwapIntervalSGI(interval) == 0; 98 | else if (glxext.has_GLX_EXT_swap_control) { 99 | GLXDrawable d = glXGetCurrentDrawable(); 100 | if (d == None) { 101 | // We don't have a context?? 102 | return false; 103 | } 104 | glXSwapIntervalEXT(ps->dpy, glXGetCurrentDrawable(), interval); 105 | return true; 106 | } 107 | return false; 108 | } 109 | 110 | static bool vsync_opengl_swc_init(session_t *ps) { 111 | if (!bkend_use_glx(ps)) { 112 | log_error("OpenGL swap control requires the GLX backend."); 113 | return false; 114 | } 115 | 116 | if (!vsync_opengl_swc_swap_interval(ps, 1)) { 117 | log_error("Failed to load a swap control extension."); 118 | return false; 119 | } 120 | 121 | return true; 122 | } 123 | 124 | /** 125 | * Wait for next VSync, OpenGL method. 126 | */ 127 | static int vsync_opengl_wait(session_t *ps attr_unused) { 128 | unsigned vblank_count = 0; 129 | 130 | glXGetVideoSyncSGI(&vblank_count); 131 | glXWaitVideoSyncSGI(2, (vblank_count + 1) % 2, &vblank_count); 132 | return 0; 133 | } 134 | 135 | /** 136 | * Wait for next VSync, OpenGL OML method. 137 | * 138 | * https://mail.gnome.org/archives/clutter-list/2012-November/msg00031.html 139 | */ 140 | static int vsync_opengl_oml_wait(session_t *ps) { 141 | int64_t ust = 0, msc = 0, sbc = 0; 142 | 143 | glXGetSyncValuesOML(ps->dpy, ps->reg_win, &ust, &msc, &sbc); 144 | glXWaitForMscOML(ps->dpy, ps->reg_win, 0, 2, (msc + 1) % 2, &ust, &msc, &sbc); 145 | return 0; 146 | } 147 | #endif 148 | 149 | /** 150 | * Initialize current VSync method. 151 | */ 152 | bool vsync_init(session_t *ps) { 153 | #ifdef CONFIG_OPENGL 154 | if (bkend_use_glx(ps)) { 155 | // Mesa turns on swap control by default, undo that 156 | vsync_opengl_swc_swap_interval(ps, 0); 157 | } 158 | #endif 159 | #ifdef CONFIG_VSYNC_DRM 160 | log_warn("The DRM vsync method is deprecated, please don't enable it."); 161 | #endif 162 | 163 | if (!ps->o.vsync) { 164 | return true; 165 | } 166 | 167 | #ifdef CONFIG_OPENGL 168 | if (bkend_use_glx(ps)) { 169 | if (!vsync_opengl_swc_init(ps)) { 170 | return false; 171 | } 172 | ps->vsync_wait = NULL; // glXSwapBuffers will automatically wait 173 | // for vsync, we don't need to do anything. 174 | return true; 175 | } 176 | #endif 177 | 178 | // Oh no, we are not using glx backend. 179 | // Throwing things at wall. 180 | #ifdef CONFIG_OPENGL 181 | if (vsync_opengl_oml_init(ps)) { 182 | log_info("Using the opengl-oml vsync method"); 183 | ps->vsync_wait = vsync_opengl_oml_wait; 184 | return true; 185 | } 186 | 187 | if (vsync_opengl_init(ps)) { 188 | log_info("Using the opengl vsync method"); 189 | ps->vsync_wait = vsync_opengl_wait; 190 | return true; 191 | } 192 | #endif 193 | 194 | #ifdef CONFIG_VSYNC_DRM 195 | if (vsync_drm_init(ps)) { 196 | log_info("Using the drm vsync method"); 197 | ps->vsync_wait = vsync_drm_wait; 198 | return true; 199 | } 200 | #endif 201 | 202 | log_error("No supported vsync method found for this backend"); 203 | return false; 204 | } 205 | -------------------------------------------------------------------------------- /src/vsync.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | #include 4 | 5 | typedef struct session session_t; 6 | 7 | bool vsync_init(session_t *ps); 8 | -------------------------------------------------------------------------------- /src/win_defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | typedef enum { 5 | WINTYPE_UNKNOWN, 6 | WINTYPE_DESKTOP, 7 | WINTYPE_DOCK, 8 | WINTYPE_TOOLBAR, 9 | WINTYPE_MENU, 10 | WINTYPE_UTILITY, 11 | WINTYPE_SPLASH, 12 | WINTYPE_DIALOG, 13 | WINTYPE_NORMAL, 14 | WINTYPE_DROPDOWN_MENU, 15 | WINTYPE_POPUP_MENU, 16 | WINTYPE_TOOLTIP, 17 | WINTYPE_NOTIFICATION, 18 | WINTYPE_COMBO, 19 | WINTYPE_DND, 20 | NUM_WINTYPES 21 | } wintype_t; 22 | 23 | /// Enumeration type of window painting mode. 24 | typedef enum { 25 | WMODE_TRANS, // The window body is (potentially) transparent 26 | WMODE_FRAME_TRANS, // The window body is opaque, but the frame is not 27 | WMODE_SOLID, // The window is opaque including the frame 28 | } winmode_t; 29 | 30 | /// Transition table: 31 | /// (DESTROYED is when the win struct is destroyed and freed) 32 | /// ('o' means in all other cases) 33 | /// (Window is created in the UNMAPPED state) 34 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 35 | /// | |UNMAPPING|DESTROYING|MAPPING|FADING |UNMAPPED| MAPPED |DESTROYED| 36 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 37 | /// | UNMAPPING | o | Window |Window | - | Fading | - | - | 38 | /// | | |destroyed |mapped | |finished| | | 39 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 40 | /// | DESTROYING | - | o | - | - | - | - | Fading | 41 | /// | | | | | | | |finished | 42 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 43 | /// | MAPPING | Window | Window | o |Opacity| - | Fading | - | 44 | /// | |unmapped |destroyed | |change | |finished| | 45 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 46 | /// | FADING | Window | Window | - | o | - | Fading | - | 47 | /// | |unmapped |destroyed | | | |finished| | 48 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 49 | /// | UNMAPPED | - | - |Window | - | o | - | Window | 50 | /// | | | |mapped | | | |destroyed| 51 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 52 | /// | MAPPED | Window | Window | - |Opacity| - | o | - | 53 | /// | |unmapped |destroyed | |change | | | | 54 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 55 | typedef enum { 56 | // The window is being faded out because it's unmapped. 57 | WSTATE_UNMAPPING, 58 | // The window is being faded out because it's destroyed, 59 | WSTATE_DESTROYING, 60 | // The window is being faded in 61 | WSTATE_MAPPING, 62 | // Window opacity is not at the target level 63 | WSTATE_FADING, 64 | // The window is mapped, no fading is in progress. 65 | WSTATE_MAPPED, 66 | // The window is unmapped, no fading is in progress. 67 | WSTATE_UNMAPPED, 68 | } winstate_t; 69 | 70 | enum win_flags { 71 | // Note: *_NONE flags are mostly redudant and meant for detecting logical errors 72 | // in the code 73 | 74 | /// pixmap is out of date, will be update in win_process_flags 75 | WIN_FLAGS_PIXMAP_STALE = 1, 76 | /// window does not have pixmap bound 77 | WIN_FLAGS_PIXMAP_NONE = 2, 78 | /// there was an error trying to bind the images 79 | WIN_FLAGS_IMAGE_ERROR = 4, 80 | /// shadow is out of date, will be updated in win_process_flags 81 | WIN_FLAGS_SHADOW_STALE = 8, 82 | /// shadow has not been generated 83 | WIN_FLAGS_SHADOW_NONE = 16, 84 | /// the client window needs to be updated 85 | WIN_FLAGS_CLIENT_STALE = 32, 86 | /// the window is mapped by X, we need to call map_win_start for it 87 | WIN_FLAGS_MAPPED = 64, 88 | /// this window has properties which needs to be updated 89 | WIN_FLAGS_PROPERTY_STALE = 128, 90 | // TODO(yshui) _maybe_ split SIZE_STALE into SIZE_STALE and SHAPE_STALE 91 | /// this window has an unhandled size/shape change 92 | WIN_FLAGS_SIZE_STALE = 256, 93 | /// this window has an unhandled position (i.e. x and y) change 94 | WIN_FLAGS_POSITION_STALE = 512, 95 | /// need better name for this, is set when some aspects of the window changed 96 | WIN_FLAGS_FACTOR_CHANGED = 1024, 97 | }; 98 | 99 | static const uint64_t WIN_FLAGS_IMAGES_STALE = WIN_FLAGS_PIXMAP_STALE | WIN_FLAGS_SHADOW_STALE; 100 | 101 | #define WIN_FLAGS_IMAGES_NONE (WIN_FLAGS_PIXMAP_NONE | WIN_FLAGS_SHADOW_NONE) 102 | -------------------------------------------------------------------------------- /src/x.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2018 Yuxuan Shui 3 | #pragma once 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "compiler.h" 14 | #include "kernel.h" 15 | #include "log.h" 16 | #include "region.h" 17 | 18 | typedef struct session session_t; 19 | struct atom; 20 | 21 | /// Structure representing Window property value. 22 | typedef struct winprop { 23 | union { 24 | void *ptr; 25 | int8_t *p8; 26 | int16_t *p16; 27 | int32_t *p32; 28 | uint32_t *c32; // 32bit cardinal 29 | }; 30 | unsigned long nitems; 31 | xcb_atom_t type; 32 | int format; 33 | 34 | xcb_get_property_reply_t *r; 35 | } winprop_t; 36 | 37 | typedef struct winprop_info { 38 | xcb_atom_t type; 39 | uint8_t format; 40 | uint32_t length; 41 | } winprop_info_t; 42 | 43 | struct xvisual_info { 44 | /// Bit depth of the red component 45 | int red_size; 46 | /// Bit depth of the green component 47 | int green_size; 48 | /// Bit depth of the blue component 49 | int blue_size; 50 | /// Bit depth of the alpha component 51 | int alpha_size; 52 | /// The depth of X visual 53 | int visual_depth; 54 | 55 | xcb_visualid_t visual; 56 | }; 57 | 58 | #define XCB_AWAIT_VOID(func, c, ...) \ 59 | ({ \ 60 | bool __success = true; \ 61 | __auto_type __e = xcb_request_check(c, func##_checked(c, __VA_ARGS__)); \ 62 | if (__e) { \ 63 | x_print_error(__e->sequence, __e->major_code, __e->minor_code, \ 64 | __e->error_code); \ 65 | free(__e); \ 66 | __success = false; \ 67 | } \ 68 | __success; \ 69 | }) 70 | 71 | #define XCB_AWAIT(func, c, ...) \ 72 | ({ \ 73 | xcb_generic_error_t *__e = NULL; \ 74 | __auto_type __r = func##_reply(c, func(c, __VA_ARGS__), &__e); \ 75 | if (__e) { \ 76 | x_print_error(__e->sequence, __e->major_code, __e->minor_code, \ 77 | __e->error_code); \ 78 | free(__e); \ 79 | } \ 80 | __r; \ 81 | }) 82 | 83 | #define log_debug_x_error(e, fmt, ...) \ 84 | LOG(DEBUG, fmt " (%s)", ##__VA_ARGS__, x_strerror(e)) 85 | #define log_error_x_error(e, fmt, ...) \ 86 | LOG(ERROR, fmt " (%s)", ##__VA_ARGS__, x_strerror(e)) 87 | #define log_fatal_x_error(e, fmt, ...) \ 88 | LOG(FATAL, fmt " (%s)", ##__VA_ARGS__, x_strerror(e)) 89 | 90 | // xcb-render specific macros 91 | #define XFIXED_TO_DOUBLE(value) (((double)(value)) / 65536) 92 | #define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536)) 93 | 94 | /// Wraps x_new_id. abort the program if x_new_id returns error 95 | static inline uint32_t x_new_id(xcb_connection_t *c) { 96 | auto ret = xcb_generate_id(c); 97 | if (ret == (uint32_t)-1) { 98 | log_fatal("We seems to have run of XIDs. This is either a bug in the X " 99 | "server, or a resource leakage in the compositor. Please open " 100 | "an issue about this problem. The compositor will die."); 101 | abort(); 102 | } 103 | return ret; 104 | } 105 | 106 | /** 107 | * Send a request to X server and get the reply to make sure all previous 108 | * requests are processed, and their replies received 109 | * 110 | * xcb_get_input_focus is used here because it is the same request used by 111 | * libX11 112 | */ 113 | static inline void x_sync(xcb_connection_t *c) { 114 | free(xcb_get_input_focus_reply(c, xcb_get_input_focus(c), NULL)); 115 | } 116 | 117 | /** 118 | * Get a specific attribute of a window. 119 | * 120 | * Returns a blank structure if the returned type and format does not 121 | * match the requested type and format. 122 | * 123 | * @param ps current session 124 | * @param w window 125 | * @param atom atom of attribute to fetch 126 | * @param length length to read 127 | * @param rtype atom of the requested type 128 | * @param rformat requested format 129 | * @return a winprop_t structure containing the attribute 130 | * and number of items. A blank one on failure. 131 | */ 132 | winprop_t x_get_prop_with_offset(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom, 133 | int offset, int length, xcb_atom_t rtype, int rformat); 134 | 135 | /** 136 | * Wrapper of wid_get_prop_adv(). 137 | */ 138 | static inline winprop_t x_get_prop(xcb_connection_t *c, xcb_window_t wid, xcb_atom_t atom, 139 | int length, xcb_atom_t rtype, int rformat) { 140 | return x_get_prop_with_offset(c, wid, atom, 0L, length, rtype, rformat); 141 | } 142 | 143 | /// Get the type, format and size in bytes of a window's specific attribute. 144 | winprop_info_t x_get_prop_info(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom); 145 | 146 | /// Discard all X events in queue or in flight. Should only be used when the server is 147 | /// grabbed 148 | static inline void x_discard_events(xcb_connection_t *c) { 149 | xcb_generic_event_t *e; 150 | while ((e = xcb_poll_for_event(c))) { 151 | free(e); 152 | } 153 | } 154 | 155 | /** 156 | * Get the value of a type-xcb_window_t property of a window. 157 | * 158 | * @return the value if successful, 0 otherwise 159 | */ 160 | xcb_window_t wid_get_prop_window(xcb_connection_t *c, xcb_window_t wid, xcb_atom_t aprop); 161 | 162 | /** 163 | * Get the value of a text property of a window. 164 | * 165 | * @param[out] pstrlst Out parameter for an array of strings, caller needs to free this 166 | * array 167 | * @param[out] pnstr Number of strings in the array 168 | */ 169 | bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ***pstrlst, 170 | int *pnstr); 171 | 172 | const xcb_render_pictforminfo_t * 173 | x_get_pictform_for_visual(xcb_connection_t *, xcb_visualid_t); 174 | int x_get_visual_depth(xcb_connection_t *, xcb_visualid_t); 175 | 176 | xcb_render_picture_t 177 | x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *, 178 | const xcb_render_pictforminfo_t *pictfmt, 179 | xcb_pixmap_t pixmap, uint32_t valuemask, 180 | const xcb_render_create_picture_value_list_t *attr) 181 | attr_nonnull(1, 2); 182 | 183 | xcb_render_picture_t 184 | x_create_picture_with_visual_and_pixmap(xcb_connection_t *, xcb_visualid_t visual, 185 | xcb_pixmap_t pixmap, uint32_t valuemask, 186 | const xcb_render_create_picture_value_list_t *attr) 187 | attr_nonnull(1); 188 | 189 | xcb_render_picture_t 190 | x_create_picture_with_standard_and_pixmap(xcb_connection_t *, xcb_pict_standard_t standard, 191 | xcb_pixmap_t pixmap, uint32_t valuemask, 192 | const xcb_render_create_picture_value_list_t *attr) 193 | attr_nonnull(1); 194 | 195 | xcb_render_picture_t 196 | x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int h, 197 | xcb_pict_standard_t standard, uint32_t valuemask, 198 | const xcb_render_create_picture_value_list_t *attr) 199 | attr_nonnull(1); 200 | 201 | /** 202 | * Create an picture. 203 | */ 204 | xcb_render_picture_t 205 | x_create_picture_with_pictfmt(xcb_connection_t *, xcb_drawable_t, int w, int h, 206 | const xcb_render_pictforminfo_t *pictfmt, uint32_t valuemask, 207 | const xcb_render_create_picture_value_list_t *attr) 208 | attr_nonnull(1, 5); 209 | 210 | xcb_render_picture_t 211 | x_create_picture_with_visual(xcb_connection_t *, xcb_drawable_t, int w, int h, 212 | xcb_visualid_t visual, uint32_t valuemask, 213 | const xcb_render_create_picture_value_list_t *attr) 214 | attr_nonnull(1); 215 | 216 | /// Fetch a X region and store it in a pixman region 217 | bool x_fetch_region(xcb_connection_t *, xcb_xfixes_region_t r, region_t *res); 218 | 219 | void x_set_picture_clip_region(xcb_connection_t *, xcb_render_picture_t, int16_t clip_x_origin, 220 | int16_t clip_y_origin, const region_t *); 221 | 222 | void x_clear_picture_clip_region(xcb_connection_t *, xcb_render_picture_t pict); 223 | 224 | /** 225 | * Log a X11 error 226 | */ 227 | void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code); 228 | 229 | /* 230 | * Convert a xcb_generic_error_t to a string that describes the error 231 | * 232 | * @return a pointer to a string. this pointer shouldn NOT be freed, same buffer is used 233 | * for multiple calls to this function, 234 | */ 235 | const char *x_strerror(xcb_generic_error_t *e); 236 | 237 | xcb_pixmap_t x_create_pixmap(xcb_connection_t *, uint8_t depth, xcb_drawable_t drawable, 238 | int width, int height); 239 | 240 | bool x_validate_pixmap(xcb_connection_t *, xcb_pixmap_t pxmap); 241 | 242 | /** 243 | * Free a winprop_t. 244 | * 245 | * @param pprop pointer to the winprop_t to free. 246 | */ 247 | static inline void free_winprop(winprop_t *pprop) { 248 | // Empty the whole structure to avoid possible issues 249 | if (pprop->r) 250 | free(pprop->r); 251 | pprop->ptr = NULL; 252 | pprop->r = NULL; 253 | pprop->nitems = 0; 254 | } 255 | 256 | /// Get the back pixmap of the root window 257 | xcb_pixmap_t 258 | x_get_root_back_pixmap(xcb_connection_t *c, xcb_window_t root, struct atom *atoms); 259 | 260 | /// Return true if the atom refers to a property name that is used for the 261 | /// root window background pixmap 262 | bool x_is_root_back_pixmap_atom(struct atom *atoms, xcb_atom_t atom); 263 | 264 | bool x_fence_sync(xcb_connection_t *, xcb_sync_fence_t); 265 | 266 | struct x_convolution_kernel { 267 | int size; 268 | int capacity; 269 | xcb_render_fixed_t kernel[]; 270 | }; 271 | 272 | /** 273 | * Convert a struct conv to a X picture convolution filter, normalizing the kernel 274 | * in the process. Allow the caller to specify the element at the center of the kernel, 275 | * for compatibility with legacy code. 276 | * 277 | * @param[in] kernel the convolution kernel 278 | * @param[in] center the element to put at the center of the matrix 279 | * @param[inout] ret pointer to an array of `size`, if `size` is too small, more space 280 | * will be allocated, and `*ret` will be updated. 281 | * @param[inout] size size of the array pointed to by `ret`. 282 | */ 283 | void attr_nonnull(1, 3) x_create_convolution_kernel(const conv *kernel, double center, 284 | struct x_convolution_kernel **ret); 285 | 286 | /// Generate a search criteria for fbconfig from a X visual. 287 | /// Returns {-1, -1, -1, -1, -1, -1} on failure 288 | struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual); 289 | 290 | xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_t std); 291 | 292 | xcb_render_pictformat_t 293 | x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std); 294 | 295 | xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen); 296 | 297 | uint32_t attr_deprecated xcb_generate_id(xcb_connection_t *c); 298 | -------------------------------------------------------------------------------- /src/xrescheck.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2014 Richard Grenville 3 | 4 | #include "compiler.h" 5 | #include "log.h" 6 | 7 | #include "xrescheck.h" 8 | 9 | static xrc_xid_record_t *gs_xid_records = NULL; 10 | 11 | #define HASH_ADD_XID(head, xidfield, add) HASH_ADD(hh, head, xidfield, sizeof(xid), add) 12 | 13 | #define HASH_FIND_XID(head, findxid, out) HASH_FIND(hh, head, findxid, sizeof(xid), out) 14 | 15 | #define M_CPY_POS_DATA(prec) \ 16 | prec->file = file; \ 17 | prec->func = func; \ 18 | prec->line = line; 19 | 20 | /** 21 | * @brief Add a record of given XID to the allocation table. 22 | */ 23 | void xrc_add_xid_(XID xid, const char *type, M_POS_DATA_PARAMS) { 24 | auto prec = ccalloc(1, xrc_xid_record_t); 25 | prec->xid = xid; 26 | prec->type = type; 27 | M_CPY_POS_DATA(prec); 28 | 29 | HASH_ADD_XID(gs_xid_records, xid, prec); 30 | } 31 | 32 | /** 33 | * @brief Delete a record of given XID in the allocation table. 34 | */ 35 | void xrc_delete_xid_(XID xid, M_POS_DATA_PARAMS) { 36 | xrc_xid_record_t *prec = NULL; 37 | HASH_FIND_XID(gs_xid_records, &xid, prec); 38 | if (!prec) { 39 | log_error("XRC: %s:%d %s(): Can't find XID %#010lx we want to delete.", 40 | file, line, func, xid); 41 | return; 42 | } 43 | HASH_DEL(gs_xid_records, prec); 44 | free(prec); 45 | } 46 | 47 | /** 48 | * @brief Report about issues found in the XID allocation table. 49 | */ 50 | void xrc_report_xid(void) { 51 | for (xrc_xid_record_t *prec = gs_xid_records; prec; prec = prec->hh.next) 52 | log_trace("XRC: %s:%d %s(): %#010lx (%s) not freed.\n", prec->file, 53 | prec->line, prec->func, prec->xid, prec->type); 54 | } 55 | 56 | /** 57 | * @brief Clear the XID allocation table. 58 | */ 59 | void xrc_clear_xid(void) { 60 | xrc_xid_record_t *prec = NULL, *ptmp = NULL; 61 | HASH_ITER(hh, gs_xid_records, prec, ptmp) { 62 | HASH_DEL(gs_xid_records, prec); 63 | free(prec); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/xrescheck.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2014 Richard Grenville 3 | #pragma once 4 | 5 | #include "common.h" 6 | #include "uthash.h" 7 | 8 | typedef struct { 9 | XID xid; 10 | const char *type; 11 | const char *file; 12 | const char *func; 13 | int line; 14 | UT_hash_handle hh; 15 | } xrc_xid_record_t; 16 | 17 | #define M_POS_DATA_PARAMS const char *file, int line, const char *func 18 | #define M_POS_DATA_PASSTHROUGH file, line, func 19 | #define M_POS_DATA __FILE__, __LINE__, __func__ 20 | 21 | void xrc_add_xid_(XID xid, const char *type, M_POS_DATA_PARAMS); 22 | 23 | #define xrc_add_xid(xid, type) xrc_add_xid_(xid, type, M_POS_DATA) 24 | 25 | void xrc_delete_xid_(XID xid, M_POS_DATA_PARAMS); 26 | 27 | #define xrc_delete_xid(xid) xrc_delete_xid_(xid, M_POS_DATA) 28 | 29 | void xrc_report_xid(void); 30 | 31 | void xrc_clear_xid(void); 32 | 33 | // Pixmap 34 | 35 | static inline void xcb_create_pixmap_(xcb_connection_t *c, uint8_t depth, 36 | xcb_pixmap_t pixmap, xcb_drawable_t drawable, 37 | uint16_t width, uint16_t height, M_POS_DATA_PARAMS) { 38 | xcb_create_pixmap(c, depth, pixmap, drawable, width, height); 39 | xrc_add_xid_(pixmap, "Pixmap", M_POS_DATA_PASSTHROUGH); 40 | } 41 | 42 | #define xcb_create_pixmap(c, depth, pixmap, drawable, width, height) \ 43 | xcb_create_pixmap_(c, depth, pixmap, drawable, width, height, M_POS_DATA) 44 | 45 | static inline xcb_void_cookie_t 46 | xcb_composite_name_window_pixmap_(xcb_connection_t *c, xcb_window_t window, 47 | xcb_pixmap_t pixmap, M_POS_DATA_PARAMS) { 48 | xcb_void_cookie_t ret = xcb_composite_name_window_pixmap(c, window, pixmap); 49 | xrc_add_xid_(pixmap, "PixmapC", M_POS_DATA_PASSTHROUGH); 50 | return ret; 51 | } 52 | 53 | #define xcb_composite_name_window_pixmap(dpy, window, pixmap) \ 54 | xcb_composite_name_window_pixmap_(dpy, window, pixmap, M_POS_DATA) 55 | 56 | static inline void 57 | xcb_free_pixmap_(xcb_connection_t *c, xcb_pixmap_t pixmap, M_POS_DATA_PARAMS) { 58 | xcb_free_pixmap(c, pixmap); 59 | xrc_delete_xid_(pixmap, M_POS_DATA_PASSTHROUGH); 60 | } 61 | 62 | #define xcb_free_pixmap(c, pixmap) xcb_free_pixmap_(c, pixmap, M_POS_DATA); 63 | --------------------------------------------------------------------------------