├── .circleci └── config.yml ├── .clang-format ├── .editorconfig ├── .github ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── .gitmodules ├── .vscode ├── c_cpp_properties.json ├── launch.json └── tasks.json ├── CONTRIBUTORS ├── COPYING ├── Doxyfile ├── LICENSE.spdx ├── LICENSES ├── MIT └── MPL-2.0 ├── PKGBUILD ├── README.md ├── README_orig.md ├── bin └── picom-trans ├── compton-default-fshader-win.glsl ├── compton-fake-transparency-fshader-win.glsl ├── compton.desktop ├── dbus-examples ├── cdbus-driver.sh └── inverter.sh ├── demo.gif ├── desc.txt ├── man ├── meson.build ├── picom-trans.1.asciidoc └── picom.1.asciidoc ├── media ├── compton.svg └── icons │ └── 48x48 │ └── compton.png ├── meson.build ├── meson └── install.sh ├── meson_options.txt ├── picom-dbus.desktop ├── picom.desktop ├── picom.sample.conf ├── src ├── atom.c ├── atom.h ├── backend │ ├── backend.c │ ├── backend.h │ ├── backend_common.c │ ├── backend_common.h │ ├── driver.c │ ├── driver.h │ ├── dummy │ │ └── dummy.c │ ├── gl │ │ ├── gl_common.c │ │ ├── gl_common.h │ │ ├── glx.c │ │ └── glx.h │ ├── meson.build │ └── xrender │ │ └── xrender.c ├── c2.c ├── c2.h ├── cache.c ├── cache.h ├── common.h ├── 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 ├── picom.c ├── picom.h ├── picom.modulemap ├── region.h ├── render.c ├── render.h ├── string_utils.c ├── string_utils.h ├── types.h ├── 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 ├── subprojects └── test.h │ ├── meson.build │ └── test.h └── tests ├── configs ├── empty.conf ├── issue239.conf ├── issue239_2.conf └── issue239_3.conf ├── run_one_test.sh ├── run_tests.sh └── testcases ├── basic.py ├── common.py ├── issue239.py ├── issue239_2.py ├── issue239_3.py └── issue239_3_norefresh.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | executors: 2 | e: 3 | docker: 4 | - image: yshui/comptonci 5 | working_directory: "/tmp/workspace" 6 | environment: 7 | UBSAN_OPTIONS: "halt_on_error=1" 8 | 9 | version: 2.1 10 | commands: 11 | build: 12 | parameters: 13 | build-config: 14 | type: string 15 | default: "" 16 | cc: 17 | type: string 18 | default: cc 19 | steps: 20 | - restore_cache: 21 | keys: 22 | - source-v1-{{ .Branch }}-{{ .Revision }} 23 | - source-v1-{{ .Branch }}- 24 | - source-v1- 25 | - checkout 26 | - save_cache: 27 | key: source-v1-{{ .Branch }}-{{ .Revision }} 28 | paths: 29 | - ".git" 30 | - run: 31 | name: config 32 | command: CC=<< parameters.cc >> meson << parameters.build-config >> -Dunittest=true --werror . build 33 | - run: 34 | name: build 35 | command: ninja -vC build 36 | 37 | jobs: 38 | basic: 39 | executor: e 40 | steps: 41 | - build: 42 | build-config: -Dbuild_docs=true -Db_coverage=true 43 | - persist_to_workspace: 44 | root: . 45 | paths: 46 | - . 47 | test: 48 | executor: e 49 | steps: 50 | - attach_workspace: 51 | at: /tmp/workspace 52 | - run: 53 | name: unit test 54 | command: ninja -vC build test 55 | - run: 56 | name: test config file parsing 57 | command: xvfb-run -s "-screen 0 640x480x24" build/src/picom --config picom.sample.conf --no-vsync --diagnostics 58 | - run: 59 | name: run testsuite 60 | command: tests/run_tests.sh build/src/picom 61 | - run: 62 | name: generate coverage reports 63 | command: cd build; find -name '*.gcno' -exec gcov -pb {} + 64 | - run: 65 | name: download codecov scripts 66 | command: curl -s https://codecov.io/bash > codecov.sh 67 | - run: 68 | name: upload coverage reports 69 | command: bash ./codecov.sh -X gcov 70 | 71 | minimal: 72 | executor: e 73 | steps: 74 | - build: 75 | build-config: -Dopengl=false -Ddbus=false -Dregex=false -Dconfig_file=false 76 | release: 77 | executor: e 78 | steps: 79 | - build: 80 | build-config: --buildtype=release 81 | release-clang: 82 | executor: e 83 | steps: 84 | - build: 85 | cc: clang 86 | build-config: --buildtype=release 87 | nogl: 88 | executor: e 89 | steps: 90 | - build: 91 | build-config: -Dopengl=false 92 | noregex: 93 | executor: e 94 | steps: 95 | - build: 96 | build-config: -Dregex=false 97 | clang_basic: 98 | executor: e 99 | steps: 100 | - build: 101 | cc: clang 102 | clang_minimal: 103 | executor: e 104 | steps: 105 | - build: 106 | cc: clang 107 | build-config: -Dopengl=false -Ddbus=false -Dregex=false -Dconfig_file=false 108 | clang_nogl: 109 | executor: e 110 | steps: 111 | - build: 112 | cc: clang 113 | build-config: -Dopengl=false 114 | clang_noregex: 115 | executor: e 116 | steps: 117 | - build: 118 | cc: clang 119 | build-config: -Dregex=false 120 | 121 | workflows: 122 | all_builds: 123 | jobs: 124 | - basic 125 | - clang_basic 126 | - minimal 127 | - clang_minimal 128 | - nogl 129 | - clang_nogl 130 | - release 131 | - release-clang 132 | - test: 133 | requires: 134 | - basic 135 | # vim: set sw=2 ts=8 et: 136 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | TabWidth: 8 3 | UseTab: ForIndentation 4 | BreakBeforeBraces: Attach 5 | #BreakStringLiterals: true 6 | IndentWidth: 8 7 | AlignAfterOpenBracket: Align 8 | ColumnLimit: 90 9 | #ExperimentalAutoDetectBinPacking: true 10 | BinPackArguments: true 11 | BinPackParameters: true 12 | #ReflowComments: true 13 | AlignTrailingComments: true 14 | SpacesBeforeTrailingComments: 8 15 | SpaceBeforeAssignmentOperators: true 16 | SpaceBeforeParens: ControlStatements 17 | AllowShortIfStatementsOnASingleLine: false 18 | AllowShortCaseLabelsOnASingleLine: true 19 | AllowShortFunctionsOnASingleLine: false 20 | IndentCaseLabels: false 21 | IndentPPDirectives: None 22 | PenaltyReturnTypeOnItsOwnLine: 0 23 | PenaltyBreakAssignment: 0 24 | PenaltyBreakBeforeFirstCallParameter: 1 25 | PenaltyBreakComment: 1 26 | PenaltyBreakString: 36 27 | PenaltyExcessCharacter: 3 28 | PenaltyBreakFirstLessLess: 0 29 | PenaltyBreakTemplateDeclaration: 0 30 | BreakBeforeBinaryOperators: None 31 | IncludeCategories: 32 | - Regex: '<.*\.h>' 33 | Priority: 1 34 | - Regex: '".*\.h"' 35 | Priority: 2 36 | SortIncludes: true 37 | #ForEachMacros: [ list_for_each_entry, list_for_each_entry_safe, HASH_ITER ] 38 | #AlignConsecutiveAssignments: true 39 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*.{c,h}] 3 | indent_style = tab 4 | indent_size = 8 5 | max_line_length = 90 6 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Platform 4 | 5 | 6 | ### GPU, drivers, and screen setup 7 | 12 | 13 | ### Environment 14 | 15 | 16 | ### picom version 17 | 18 | 19 | 20 | 21 | ### Configuration: 22 | ``` 23 | // Paste your configuration here 24 | ``` 25 | 26 | ### Steps of reproduction 27 | 31 | 32 | 1. 33 | 2. 34 | 35 | ### Expected behavior 36 | 37 | ### Current Behavior 38 | 39 | ### Stack trace 40 | 45 | 46 | 47 | 48 | ### Other details 49 | 50 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build files 2 | .deps 3 | aclocal.m4 4 | autom4te.cache 5 | config.log 6 | config.status 7 | configure 8 | depcomp 9 | install-sh 10 | missing 11 | stamp-h1 12 | compton 13 | *.o 14 | *.d 15 | build/ 16 | compile_commands.json 17 | build.ninja 18 | 19 | # ccls 20 | .ccls-cache 21 | 22 | # CMake files 23 | compton-*.deb 24 | compton-*.rpm 25 | compton-*.tar.bz2 26 | release/ 27 | _CPack_Packages/ 28 | CMakeCache.txt 29 | CMakeFiles/ 30 | cmake_install.cmake 31 | CPackSourceConfig.cmake 32 | install_manifest.txt 33 | 34 | 35 | # Vim files 36 | .sw[a-z] 37 | .*.sw[a-z] 38 | *~ 39 | 40 | # Misc files 41 | core.* 42 | .gdb_history 43 | oprofile_data/ 44 | compton.plist 45 | callgrind.out.* 46 | man/*.html 47 | man/*.1 48 | doxygen/ 49 | .clang_complete 50 | .ycm_extra_conf.py 51 | .ycm_extra_conf.pyc 52 | /src/backtrace-symbols.[ch] 53 | /compton*.trace 54 | *.orig 55 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arian8j2/picom-jonaburg-fix/31d25da22b44f37cbb9be49fe5c239ef8d00df12/.gitmodules -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "/usr/include", 7 | "/usr/include/pixman-1", 8 | "${workspaceFolder}/**" 9 | ], 10 | "defines": [ 11 | "CONFIG_OPENGL", 12 | "_POSIX_C_SOURCE 199309L" 13 | ], 14 | "compilerPath": "/bin/gcc", 15 | "cStandard": "c11", 16 | "cppStandard": "c++17", 17 | "intelliSenseMode": "clang-x64", 18 | "compileCommands": "${workspaceFolder}/build/compile_commands.json" 19 | } 20 | ], 21 | "version": 4 22 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "targetArchitecture": "x64", 5 | "name": "Debug with Meson", 6 | "type": "cppdbg", 7 | "request": "launch", 8 | "cwd": "${workspaceRoot}", 9 | "program": "${workspaceRoot}/build/src/picom", 10 | "preLaunchTask": "build debug meson", 11 | "stopAtEntry": false, 12 | "launchCompleteCommand": "exec-run", 13 | "linux": { 14 | "MIMode": "gdb", 15 | "miDebuggerPath": "/usr/bin/gdb" 16 | }, 17 | "osx": { 18 | "MIMode": "lldb" 19 | }, 20 | "windows": { 21 | "MIMode": "gdb", 22 | "miDebuggerPath": "C:\\MinGw\\bin\\gdb.exe" 23 | } 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "label": "build debug meson", 5 | "type": "shell", 6 | "command": "meson build --buildtype=debug && ninja -C build", 7 | "problemMatcher": [], 8 | "group": { 9 | "kind": "build", 10 | "isDefault": true 11 | } 12 | }, 13 | { 14 | "label": "build release meson", 15 | "type": "shell", 16 | "command": "meson build --buildtype=release && ninja -C build", 17 | "problemMatcher": [], 18 | "group": { 19 | "kind": "build", 20 | "isDefault": true 21 | } 22 | } 23 | ], 24 | "version": "2.0.0" 25 | } -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Sorted in alphabetical order 2 | Open an issue or pull request if you don't want your name listed here. 3 | 4 | Adam Jackson 5 | Alexander Kapshuna 6 | Antonin Décimo 7 | Avi-D-coder 8 | Brottweiler 9 | Carl Worth 10 | Christopher Jeffrey 11 | Corax26 12 | Dan Elkouby 13 | Dana Jansens 14 | Dave Airlie 15 | dolio 16 | Duncan 17 | Einar Lielmanis 18 | Eric Anholt 19 | Greg Flynn 20 | hasufell 21 | James Cloos 22 | Jamey Sharp 23 | Jarrad 24 | Javeed Shaikh 25 | Keith Packard 26 | Kevin Kelley 27 | mæp 28 | Mark Tiefenbruck 29 | Matthew Allum 30 | Michael Reed 31 | Namkhai Bourquin 32 | Nate Hart 33 | notfoss 34 | Patrick Collins 35 | Peter Mattern 36 | Phil Blundell 37 | Que Quotion 38 | Richard Grenville 39 | Scott Leggett 40 | Tasos Sahanidis 41 | The Gitter Badger 42 | Tilman Sauerbeck 43 | Tim van Dalen 44 | Uli Schlachter 45 | Will Dietz 46 | XeCycle 47 | Yuxuan Shui 48 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | picom - a compositor for X11 2 | 3 | Based on xcompmgr, originally written by Keith Packard, with modifications 4 | from several contributors (according to the xcompmgr man page): Matthew Allum, 5 | Eric Anholt, Dan Doel, Thomas Luebking, Matthew Hawn, Ely Levy, Phil Blundell, 6 | and Carl Worth. Menu transparency was implemented by Dana Jansens. 7 | 8 | Numerous contributions to picom from Richard Grenville. 9 | 10 | See the CONTRIBUTORS file for a complete list of contributors 11 | 12 | This source code is provided under: 13 | 14 | SPDX-License-Identifier: MPL-2.0 AND MIT 15 | 16 | And the preferred license for new source files in this project is: 17 | 18 | SPDX-License-Identifier: MPL-2.0 19 | 20 | You can find the text of the licenses in the LICENSES directory. 21 | -------------------------------------------------------------------------------- /LICENSE.spdx: -------------------------------------------------------------------------------- 1 | SPDXVersion: SPDX-2.1 2 | DataLicense: CC0-1.0 3 | PackageName: picom 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 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Julian Mac Kenzie 2 | _gitfolder="picom" 3 | pkgname=picom-jonaburg-fix 4 | pkgver=0.1 5 | pkgrel=4 6 | pkgdesc="jonaburg's fork plus a patch for rounded corners and shadows" 7 | arch=(i686 x86_64) 8 | url="https://github.com/Arian8j2/picom-jonaburg-fix.git" 9 | license=('MIT' 'MPL2') 10 | depends=('libconfig' 'libev' 'libxdg-basedir' 'pcre' 'pixman' 'xcb-util-image' 'xcb-util-renderutil' 'hicolor-icon-theme' 'libglvnd' 'libx11' 'libxcb' 'libxext' 'libdbus') 11 | makedepends=('git' 'meson' 'ninja' 'gcc' 'asciidoc' 'uthash') 12 | optdepends=('dbus: To control picom via D-Bus' 13 | 'xorg-xwininfo: For picom-trans' 14 | 'xorg-xprop: For picom-trans' 15 | 'python: For picom-convgen.py') 16 | provides=('compton' 'compton-git' 'picom' 'picom-git') 17 | conflicts=('compton' 'compton-git' 'picom' 'picom-git') 18 | source=("${_gitfolder}::git+https://github.com/Arian8j2/picom-jonaburg-fix.git") 19 | md5sums=("SKIP") 20 | build() { 21 | cd "${srcdir}/${_gitfolder}" 22 | meson --buildtype=release . build --prefix=/usr -Dwith_docs=true 23 | ninja -C build 24 | } 25 | 26 | package() { 27 | # this is adapted from tryone144's fork PKGBUILD 28 | cd "${srcdir}/${_gitfolder}" 29 | DESTDIR="$pkgdir/" ninja -C build install 30 | 31 | # install license 32 | install -D -m644 "LICENSES/MIT" "${pkgdir}/usr/share/licenses/${pkgname/-git$/}/LICENSE-MIT" 33 | 34 | # example conf 35 | install -D -m644 "picom.sample.conf" "${pkgdir}/etc/xdg/picom.conf.example" 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | picom 2 | ======= 3 | 4 | *new!* : You'll now also find tryone's dual_kawase blur for the new backend, as well as rounded corners from sdhand if they are so desired, merged from ibhagwan. 5 | 6 | ![](demo.gif) 7 | 8 | This is a forked version from Sandmark's picom branch, including Blackcapcoder's animation code inside. The animations here are further smoothed and time deltas reduced from 1ms to 40us for high refresh rates and buttery smooth transitions. 9 | 10 | You'll need to run it with the experimental backend with: 11 | `picom --experimental-backend` 12 | 13 | Your picom config can also now take advantage of some of the options that were previously implemented in blackcapcoder's compton version: 14 | - [x] * `transition-length` length of animation in milliseconds (default: 300) 15 | - [x] * `transition-pow-x` animation easing on the x-axis (default: 0.1) 16 | - [x] * `transition-pow-y` animation easing on the y-axis (default: 0.1) 17 | - [x] * `transition-pow-w` animation easing on the window width (default: 0.1) 18 | - [x] * `transition-pow-h` animation easing on the window height (default: 0.1) 19 | - [x] * `size-transition` whether to animate window size changes (default: true) 20 | - [ ] * `spawn-center-screen` whether to animate new windows from the center of the screen (default: false) 21 | - [ ] * `spawn-center` whether to animate new windows from their own center (default: true) 22 | - [ ] * `no-scale-down` Whether to animate down scaling (some programs handle this poorly) (default: false) 23 | 24 | ---- 25 | ### Installation of this fork 26 | [AUR](https://aur.archlinux.org/packages/picom-jonaburg-git) package 27 | 28 | OR 29 | 30 | ```bash 31 | git clone https://github.com/jonaburg/picom 32 | cd picom 33 | meson --buildtype=release . build 34 | ninja -C build 35 | # To install the binaries in /usr/local/bin (optional) 36 | sudo ninja -C build install 37 | ``` 38 | 39 | ---- 40 | **This is a development branch, bugs to be expected** 41 | 42 | This is forked from the original Compton because it seems to have become unmaintained. 43 | 44 | The current battle plan of this fork is to refactor it to make the code _possible_ to maintain, so potential contributors won't be scared away when they take a look at the code. 45 | 46 | We also try to fix bugs. 47 | 48 | The original README can be found [here](README_orig.md) 49 | 50 | ## Call for testers 51 | 52 | ### `--experimental-backends` 53 | 54 | This flag enables the refactored/partially rewritten backends. 55 | 56 | Currently, new backends feature better vsync with the xrender backend and improved input lag with the glx backend (for non-NVIDIA users). The performance should be on par with the old backends. 57 | 58 | New backend features will only be implemented on the new backends from now on, and the old backends will eventually be phased out after the new backends stabilize. 59 | 60 | To test the new backends, add the `--experimental-backends` flag to the command you use to run picom. This flag is not available from the configuration file. 61 | 62 | To report issues with the new backends, please state explicitly you are using the new backends in your report. 63 | 64 | ## Rename 65 | 66 | ### Rationale 67 | 68 | Since the inception of this fork, the existence of two compton repositories has caused some number of confusions. Mainly, people will report issues of this fork to the original compton, or report issues of the original compton here. Later, when distros started packaging this fork of compton, some wanted to differentiate the newer compton from the older version. They found themselves having no choice but to invent a name for this fork. This is less than ideal since this has the potential to cause more confusions among users. 69 | 70 | Therefore, we decided to move this fork to a new name. Personally, I consider this more than justified since this version of compton has gone through significant changes since it was forked. 71 | 72 | ### The name 73 | 74 | The criteria for a good name were 75 | 76 | 0. Being short, so it's easy to remember. 77 | 1. Pronounceability, again, helps memorability 78 | 2. Searchability, so when people search the name, it's easy for them to find this repository. 79 | 80 | Of course, choosing a name is never easy, and there is no apparent way to objectively evaluate the names. Yet, we have to solve the aforementioned problems as soon as possible. 81 | 82 | In the end, we picked `picom` (a portmanteau of `pico` and `composite`) as our new name. This name might not be perfect, but is what we will move forward with unless there's a compelling reason not to. 83 | 84 | ### Migration 85 | 86 | Following the [deprecation process](https://github.com/yshui/picom/issues/114), migration to the new name will be broken into 3 steps: 87 | 88 | 1. All mentions of `compton` will be updated to `picom` in the code base. `compton` will still be installed, but only as a symlink to `picom`. When `picom` is launched via the symlink, a warning message is printed, alerting the user to migrate. Similarly, the old configuration file names and dbus interface names will still be accepted but warned. 89 | 2. 3 major releases after step 1, the warning messages will be prompted to error messages and `picom` will not start when launched via the symlink. 90 | 3. 3 major releases after step 2, the symlink will be removed. 91 | 92 | The dbus interface and service names are unchanged, so no migration needed for that. 93 | 94 | ## Change Log 95 | 96 | See [Releases](https://github.com/yshui/picom/releases) 97 | 98 | ## Build 99 | 100 | ### Dependencies 101 | 102 | Assuming you already have all the usual building tools installed (e.g. gcc, python, meson, ninja, etc.), you still need: 103 | 104 | * libx11 105 | * libx11-xcb 106 | * libXext 107 | * xproto 108 | * xcb 109 | * xcb-damage 110 | * xcb-xfixes 111 | * xcb-shape 112 | * xcb-renderutil 113 | * xcb-render 114 | * xcb-randr 115 | * xcb-composite 116 | * xcb-image 117 | * xcb-present 118 | * xcb-xinerama 119 | * xcb-glx 120 | * pixman 121 | * libdbus (optional, disable with the `-Ddbus=false` meson configure flag) 122 | * libconfig (optional, disable with the `-Dconfig_file=false` meson configure flag) 123 | * libGL (optional, disable with the `-Dopengl=false` meson configure flag) 124 | * libpcre (optional, disable with the `-Dregex=false` meson configure flag) 125 | * libev 126 | * uthash 127 | 128 | On Debian based distributions (e.g. Ubuntu), the list of needed packages are 129 | 130 | ``` 131 | libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl1-mesa-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev 132 | ``` 133 | 134 | To build the documents, you need `asciidoc` 135 | 136 | ### To build 137 | 138 | ```bash 139 | $ git submodule update --init --recursive 140 | $ meson --buildtype=release . build 141 | $ ninja -C build 142 | ``` 143 | 144 | Built binary can be found in `build/src` 145 | 146 | If you have libraries and/or headers installed at non-default location (e.g. under `/usr/local/`), you might need to tell meson about them, since meson doesn't look for dependencies there by default. 147 | 148 | You can do that by setting the `CPPFLAGS` and `LDFLAGS` environment variables when running `meson`. Like this: 149 | 150 | ```bash 151 | $ LDFLAGS="-L/path/to/libraries" CPPFLAGS="-I/path/to/headers" meson --buildtype=release . build 152 | 153 | ``` 154 | 155 | As an example, on FreeBSD, you might have to run meson with: 156 | ```bash 157 | $ LDFLAGS="-L/usr/local/include" CPPFLAGS="-I/usr/local/include" meson --buildtype=release . build 158 | $ ninja -C build 159 | ``` 160 | 161 | ### To install 162 | 163 | ``` bash 164 | $ ninja -C build install 165 | ``` 166 | 167 | Default install prefix is `/usr/local`, you can change it with `meson configure -Dprefix= build` 168 | 169 | ## How to Contribute 170 | 171 | ### Code 172 | 173 | You can look at the [Projects](https://github.com/yshui/picom/projects) page, and see if there is anything that interests you. Or you can take a look at the [Issues](https://github.com/yshui/picom/issues). 174 | 175 | ### Non-code 176 | 177 | Even if you don't want to contribute code, you can still contribute by compiling and running this branch, and report any issue you can find. 178 | 179 | Contributions to the documents and wiki will also be appreciated. 180 | 181 | ## Contributors 182 | 183 | See [CONTRIBUTORS](CONTRIBUTORS) 184 | -------------------------------------------------------------------------------- /README_orig.md: -------------------------------------------------------------------------------- 1 | # Compton 2 | 3 | [![Join the chat at https://gitter.im/chjj/compton](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/chjj/compton?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | __Compton__ is a compositor for X, and a fork of __xcompmgr-dana__. 6 | 7 | I was frustrated by the low amount of standalone lightweight compositors. 8 | Compton was forked from Dana Jansens' fork of xcompmgr and refactored. I fixed 9 | whatever bug I found, and added features I wanted. Things seem stable, but don't 10 | quote me on it. I will most likely be actively working on this until I get the 11 | features I want. This is also a learning experience for me. That is, I'm 12 | partially doing this out of a desire to learn Xlib. 13 | 14 | ## Changes from xcompmgr: 15 | 16 | * OpenGL backend (`--backend glx`), in addition to the old X Render backend. 17 | * Inactive window transparency (`-i`) / dimming (`--inactive-dim`). 18 | * Titlebar/frame transparency (`-e`). 19 | * Menu transparency (`-m`, thanks to Dana). 20 | * shadows are now enabled for argb windows, e.g. terminals with transparency 21 | * removed serverside shadows (and simple compositing) to clean the code, 22 | the only option that remains is clientside shadows 23 | * configuration files (see the man page for more details) 24 | * colored shadows (`--shadow-[red/green/blue]`) 25 | * a new fade system 26 | * VSync support (not always working) 27 | * Blur of background of transparent windows, window color inversion (bad in performance) 28 | * Some more options... 29 | 30 | ## Fixes from the original xcompmgr: 31 | 32 | * fixed a segfault when opening certain window types 33 | * fixed a memory leak caused by not freeing up shadows (from the freedesktop 34 | repo) 35 | * fixed the conflict with chromium and similar windows 36 | * [many more](https://github.com/chjj/compton/issues) 37 | 38 | ## Building 39 | 40 | ### Dependencies: 41 | 42 | __B__ for build-time 43 | 44 | __R__ for runtime 45 | 46 | * libx11 (B,R) 47 | * libxcomposite (B,R) 48 | * libxdamage (B,R) 49 | * libxfixes (B,R) 50 | * libXext (B,R) 51 | * libxrender (B,R) 52 | * libXrandr (B,R) 53 | * libXinerama (B,R) (Can be disabled with `NO_XINERAMA` at compile time) 54 | * pkg-config (B) 55 | * make (B) 56 | * xproto / x11proto (B) 57 | * sh (R) 58 | * xprop,xwininfo / x11-utils (R) 59 | * libpcre (B,R) (Can be disabled with `NO_REGEX_PCRE` at compile time) 60 | * libconfig (B,R) (Can be disabled with `NO_LIBCONFIG` at compile time) 61 | * libdrm (B) (Can be disabled with `NO_VSYNC_DRM` at compile time) 62 | * libGL (B,R) (Can be disabled with `NO_VSYNC_OPENGL` at compile time) 63 | * libdbus (B,R) (Can be disabled with `NO_DBUS` at compile time) 64 | * asciidoc (B) (and docbook-xml-dtd-4.5, libxml-utils, libxslt, xsltproc, xmlto, etc. if your distro doesn't pull them in) 65 | 66 | ### How to build 67 | 68 | To build, make sure you have the dependencies above: 69 | 70 | ```bash 71 | # Make the main program 72 | $ make 73 | # Make the man pages 74 | $ make docs 75 | # Install 76 | $ make install 77 | ``` 78 | 79 | (Compton does include a `_CMakeLists.txt` in the tree, but we haven't decided whether we should switch to CMake yet. The `Makefile` is fully usable right now.) 80 | 81 | ## Known issues 82 | 83 | * Our [FAQ](https://github.com/chjj/compton/wiki/faq) covers some known issues. 84 | 85 | * VSync does not work too well. You may check the [VSync Guide](https://github.com/chjj/compton/wiki/vsync-guide) for how to get (possibly) better effects. 86 | 87 | * If `--unredir-if-possible` is enabled, when compton redirects/unredirects windows, the screen may flicker. Using `--paint-on-overlay` minimizes the problem from my observation, yet I do not know if there's a cure. 88 | 89 | * compton may not track focus correctly in all situations. The focus tracking code is experimental. `--use-ewmh-active-win` might be helpful. 90 | 91 | * The performance of blur under X Render backend might be pretty bad. OpenGL backend could be faster. 92 | 93 | * With `--blur-background` you may sometimes see weird lines around damaged area. `--resize-damage YOUR_BLUR_RADIUS` might be helpful in the case. 94 | 95 | ## Usage 96 | 97 | Please refer to the Asciidoc man pages (`man/compton.1.asciidoc` & `man/compton-trans.1.asciidoc`) for more details and examples. 98 | 99 | Note a sample configuration file `compton.sample.conf` is included in the repository. 100 | 101 | ## Support 102 | 103 | * Bug reports and feature requests should go to the "Issues" section above. 104 | 105 | * Our (semi?) official IRC channel is #compton on FreeNode. 106 | 107 | * Some information is available on the wiki, including [FAQ](https://github.com/chjj/compton/wiki/faq), [VSync Guide](https://github.com/chjj/compton/wiki/vsync-guide), and [Performance Guide](https://github.com/chjj/compton/wiki/perf-guide). 108 | 109 | ## License 110 | 111 | Although compton has kind of taken on a life of its own, it was originally 112 | an xcompmgr fork. xcompmgr has gotten around. As far as I can tell, the lineage 113 | for this particular tree is something like: 114 | 115 | * Keith Packard (original author) 116 | * Matthew Hawn 117 | * ... 118 | * Dana Jansens 119 | * chjj and richardgv 120 | 121 | Not counting the tens of people who forked it in between. 122 | 123 | Compton is distributed under MIT license, as far as I (richardgv) know. See LICENSE for more info. 124 | -------------------------------------------------------------------------------- /bin/picom-trans: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # picom-trans 5 | # transset in a bash script 6 | # Copyright (c) 2011-2012, Christopher Jeffrey 7 | # 8 | 9 | # Usage: 10 | # $ picom-trans [options] [+|-]opacity 11 | # By window id 12 | # $ picom-trans -w "$WINDOWID" 75 13 | # By name 14 | # $ picom-trans -n "urxvt" 75 15 | # By current window 16 | # $ picom-trans -c 75 17 | # By selection 18 | # $ picom-trans 75 19 | # $ picom-trans -s 75 20 | # Increment current window 5% 21 | # $ picom-trans -c +5 22 | # Delete current window's opacity 23 | # $ picom-trans -c --delete 24 | # Reset all windows 25 | # $ picom-trans --reset 26 | 27 | case "$0" in 28 | *compton-trans*) echo "Warning: compton has been renamed, please use picom-trans instead" >& 2;; 29 | esac 30 | 31 | # "command" is a shell built-in, faster than "which" 32 | if test -z "$(command -v xprop)" -o -z "$(command -v xwininfo)"; then 33 | echo 'The command xwininfo or xprop is not available. They might reside in a package named xwininfo, xprop, x11-utils, xorg-xprop, or xorg-xwininfo.' >& 2 34 | exit 1 35 | fi 36 | 37 | # Variables 38 | active= 39 | wprefix= 40 | window= 41 | opacity= 42 | cur= 43 | action= 44 | treeout= 45 | wid= 46 | topmost= 47 | lineno= 48 | option= 49 | v= 50 | 51 | # Workaround: replace '-5' with '~5' so as not to confuse getopts. 52 | for v in "$@"; do 53 | shift && set -- "$@" "$(echo "$v" | sed 's/^-\([0-9]\+%\?\)$/~\1/')" 54 | done 55 | 56 | # This takes into account the fact that getopts stops on 57 | # any argument it doesn't recognize or errors on. This 58 | # allows for things like `picom-trans -5` as well 59 | # as `picom-trans -c +5 -s` (contrived example). 60 | while test $# -gt 0; do 61 | # Reset option index 62 | OPTIND=1 63 | 64 | # Read options 65 | while getopts 'scrdgn:w:o:-:' option "$@"; do 66 | if test "$option" = '-'; then 67 | case "$OPTARG" in 68 | select | current | reset | delete | get) 69 | v='' 70 | ;; 71 | name | window | opacity) 72 | eval v=\$$OPTIND 73 | OPTIND=$((OPTIND + 1)) 74 | ;; 75 | name=* | window=* | opacity=*) 76 | v=$(echo "$OPTARG" | sed 's/^[^=]\+=//') 77 | ;; 78 | *) 79 | echo "$0: illegal option $OPTARG" >& 2 80 | exit 1 81 | ;; 82 | esac 83 | option=$(echo "$OPTARG" | cut -c 1) 84 | OPTARG=$v 85 | fi 86 | case "$option" in 87 | s) wprefix=''; window='' ;; 88 | c) 89 | active=$(xprop -root -notype _NET_ACTIVE_WINDOW \ 90 | | grep -Eo '0x[[:xdigit:]]+' | head -n 1) 91 | wprefix='-id'; window=$active 92 | ;; 93 | r) action='reset' ;; 94 | d) action='delete' ;; 95 | g) action='get' ;; 96 | n) wprefix='-name'; window=$OPTARG ;; 97 | w) wprefix='-id'; window=$OPTARG ;; 98 | o) opacity=$OPTARG ;; 99 | \?) exit 1 ;; 100 | esac 101 | done 102 | 103 | # Read positional arguments 104 | shift $((OPTIND - 1)) 105 | test -n "$1" && opacity=$1 && shift 106 | done 107 | 108 | # clean up opacity. xargs == a poor man's trim. 109 | opacity=$(echo "$opacity" | xargs | sed 's/%//g' | sed 's/^~\([0-9]\+\)$/-\1/') 110 | 111 | # Validate opacity value 112 | if test -z "$action" && ! echo "$opacity" | grep -q '^[+-]\?[0-9]\+$'; then 113 | echo "Invalid opacity specified: $opacity." 114 | exit 1 115 | fi 116 | 117 | # Reset opacity for all windows 118 | if test x"$action" = x'reset'; then 119 | xwininfo -root -tree \ 120 | | sed -n 's/^ \(0x[[:xdigit:]]*\).*/\1/p' \ 121 | | while IFS=$(printf '\n') read wid; do 122 | xprop -id "$wid" -remove _NET_WM_WINDOW_OPACITY 123 | done 124 | exit 0 125 | fi 126 | 127 | # Get ID of the target window 128 | if test -z "$wprefix"; then 129 | treeout=$(xwininfo -children -frame) 130 | else 131 | test "$wprefix" = '-id' \ 132 | && ! echo "$window" | grep -Eiq '^[[:space:]]*(0x[[:xdigit:]]+|[[:digit:]]+)[[:space:]]*$' \ 133 | && echo 'Bad window ID.' && exit 1 134 | treeout=$(xwininfo -children $wprefix "$window") 135 | fi 136 | 137 | wid=$(echo "$treeout" | sed -n 's/^xwininfo:.*: \(0x[[:xdigit:]]*\).*$/\1/p') 138 | 139 | if test -z "$wid"; then 140 | echo 'Failed to find window.' 141 | exit 1 142 | fi 143 | 144 | # Make sure it's not root window 145 | if echo "$treeout" | fgrep -q 'Parent window id: 0x0'; then 146 | echo 'Cannot set opacity on root window.' 147 | exit 1 148 | fi 149 | 150 | # If it's already the topmost window 151 | if echo "$treeout" | grep -q 'Parent window id: 0x[[:xdigit:]]* (the root window)'; then 152 | topmost=$wid 153 | else 154 | # Get the whole window tree 155 | treeout=$(xwininfo -root -tree) 156 | 157 | if test -z "$treeout"; then 158 | echo 'Failed to get root window tree.' 159 | exit 1 160 | fi 161 | 162 | # Find the line number of the target window in the window tree 163 | lineno=$(echo -n "$treeout" | grep -nw "$wid" | head -n1 | cut -d ':' -f 1) 164 | 165 | if test -z "$lineno"; then 166 | echo 'Failed to find window in window tree.' 167 | exit 1 168 | fi 169 | 170 | # Find the highest ancestor of the target window below 171 | topmost=$(echo -n "$treeout" \ 172 | | head -n $((lineno + 1)) \ 173 | | sed -n 's/^ \(0x[[:xdigit:]]*\).*/\1/p' \ 174 | | tail -n 1) 175 | fi 176 | 177 | if test -z "$topmost"; then 178 | echo 'Failed to find the highest parent window below root of the' \ 179 | 'selected window.' 180 | exit 1 181 | fi 182 | 183 | # Remove the opacity property. 184 | if test x"$action" = x'delete'; then 185 | xprop -id "$topmost" -remove _NET_WM_WINDOW_OPACITY 186 | exit 0 187 | fi 188 | 189 | # Get current opacity. 190 | cur=$(xprop -id "$topmost" -notype _NET_WM_WINDOW_OPACITY \ 191 | | sed 's/^.*\b\([0-9]\+\).*$\|^.*$/\1/') 192 | test -z "$cur" && cur=0xffffffff 193 | cur=$((cur * 100 / 0xffffffff)) 194 | 195 | # Output current opacity. 196 | if test x"$action" = x'get'; then 197 | echo "$cur" 198 | exit 0 199 | fi 200 | 201 | # Calculate the desired opacity 202 | if echo "$opacity" | grep -q '^[+-]'; then 203 | opacity=$((cur + opacity)) 204 | fi 205 | 206 | test $opacity -lt 0 && opacity=0 207 | test $opacity -gt 100 && opacity=100 208 | 209 | # Set opacity 210 | opacity=$((opacity * 0xffffffff / 100)) 211 | xprop -id "$topmost" -f _NET_WM_WINDOW_OPACITY 32c \ 212 | -set _NET_WM_WINDOW_OPACITY "$opacity" 213 | -------------------------------------------------------------------------------- /compton-default-fshader-win.glsl: -------------------------------------------------------------------------------- 1 | uniform float opacity; 2 | uniform bool invert_color; 3 | uniform sampler2D tex; 4 | 5 | void main() { 6 | vec4 c = texture2D(tex, gl_TexCoord[0]); 7 | if (invert_color) 8 | c = vec4(vec3(c.a, c.a, c.a) - vec3(c), c.a); 9 | c *= opacity; 10 | gl_FragColor = c; 11 | } 12 | -------------------------------------------------------------------------------- /compton-fake-transparency-fshader-win.glsl: -------------------------------------------------------------------------------- 1 | uniform float opacity; 2 | uniform bool invert_color; 3 | uniform sampler2D tex; 4 | 5 | void main() { 6 | vec4 c = texture2D(tex, gl_TexCoord[0]); 7 | { 8 | // Change vec4(1.0, 1.0, 1.0, 1.0) to your desired color 9 | vec4 vdiff = abs(vec4(1.0, 1.0, 1.0, 1.0) - c); 10 | float diff = max(max(max(vdiff.r, vdiff.g), vdiff.b), vdiff.a); 11 | // Change 0.8 to your desired opacity 12 | if (diff < 0.001) 13 | c *= 0.8; 14 | } 15 | if (invert_color) 16 | c = vec4(vec3(c.a, c.a, c.a) - vec3(c), c.a); 17 | c *= opacity; 18 | gl_FragColor = c; 19 | } 20 | -------------------------------------------------------------------------------- /compton.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | Name=compton 5 | GenericName=X compositor 6 | Comment=A X compositor 7 | Categories=Utility; 8 | Keywords=compositor;composite manager;window effects;transparency;opacity; 9 | TryExec=compton 10 | Exec=compton 11 | Icon=compton 12 | # Thanks to quequotion for providing this file! 13 | -------------------------------------------------------------------------------- /dbus-examples/cdbus-driver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "$SED" ]; then 4 | SED="sed" 5 | command -v gsed > /dev/null && SED="gsed" 6 | fi 7 | 8 | # === Get connection parameters === 9 | 10 | dpy=$(echo -n "$DISPLAY" | tr -c '[:alnum:]' _) 11 | 12 | if [ -z "$dpy" ]; then 13 | echo "Cannot find display." 14 | exit 1 15 | fi 16 | 17 | service="com.github.chjj.compton.${dpy}" 18 | interface='com.github.chjj.compton' 19 | object='/com/github/chjj/compton' 20 | type_win='uint32' 21 | type_enum='uint32' 22 | 23 | # === DBus methods === 24 | 25 | # List all window ID compton manages (except destroyed ones) 26 | dbus-send --print-reply --dest="$service" "$object" "${interface}.list_win" 27 | 28 | # Get window ID of currently focused window 29 | focused=$(dbus-send --print-reply --dest="$service" "$object" "${interface}.find_win" string:focused | $SED -n 's/^[[:space:]]*'${type_win}'[[:space:]]*\([[:digit:]]*\).*/\1/p') 30 | 31 | if [ -n "$focused" ]; then 32 | # Get invert_color_force property of the window 33 | dbus-send --print-reply --dest="$service" "$object" "${interface}.win_get" "${type_win}:${focused}" string:invert_color_force 34 | 35 | # Set the window to have inverted color 36 | dbus-send --print-reply --dest="$service" "$object" "${interface}.win_set" "${type_win}:${focused}" string:invert_color_force "${type_enum}:1" 37 | else 38 | echo "Cannot find focused window." 39 | fi 40 | 41 | # Reset compton 42 | sleep 3 43 | dbus-send --print-reply --dest="$service" "$object" "${interface}.reset" 44 | 45 | # Undirect window 46 | sleep 3 47 | dbus-send --print-reply --dest="$service" "$object" "${interface}.opts_set" string:redirected_force "${type_enum}:0" 48 | 49 | # Revert back to auto 50 | sleep 3 51 | dbus-send --print-reply --dest="$service" "$object" "${interface}.opts_set" string:redirected_force "${type_enum}:2" 52 | 53 | # Force repaint 54 | dbus-send --print-reply --dest="$service" "$object" "${interface}.repaint" 55 | -------------------------------------------------------------------------------- /dbus-examples/inverter.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # === Verify `compton --dbus` status === 4 | 5 | if [ -z "`dbus-send --session --dest=org.freedesktop.DBus --type=method_call --print-reply /org/freedesktop/DBus org.freedesktop.DBus.ListNames | grep compton`" ]; then 6 | echo "compton DBus interface unavailable" 7 | if [ -n "`pgrep picom`" ]; then 8 | echo "compton running without dbus interface" 9 | #killall compton & # Causes all windows to flicker away and come back ugly. 10 | #compton --dbus & # Causes all windows to flicker away and come back beautiful 11 | else 12 | echo "compton not running" 13 | fi 14 | exit 1; 15 | fi 16 | 17 | # === Setup sed === 18 | 19 | if [ -z "$SED" ]; then 20 | SED="sed" 21 | command -v gsed > /dev/null && SED="gsed" 22 | fi 23 | 24 | # === Get connection parameters === 25 | 26 | dpy=$(echo -n "$DISPLAY" | tr -c '[:alnum:]' _) 27 | 28 | if [ -z "$dpy" ]; then 29 | echo "Cannot find display." 30 | exit 1; 31 | fi 32 | 33 | service="com.github.chjj.compton.${dpy}" 34 | interface="com.github.chjj.compton" 35 | compton_dbus="dbus-send --print-reply --dest="${service}" / "${interface}"." 36 | type_win='uint32' 37 | type_enum='uint32' 38 | 39 | # === Color Inversion === 40 | 41 | # Get window ID of window to invert 42 | if [ -z "$1" -o "$1" = "selected" ]; then 43 | window=$(xwininfo -frame | sed -n 's/^xwininfo: Window id: \(0x[[:xdigit:]][[:xdigit:]]*\).*/\1/p') # Select window by mouse 44 | elif [ "$1" = "focused" ]; then 45 | # Ensure we are tracking focus 46 | window=$(${compton_dbus}find_win string:focused | $SED -n 's/^[[:space:]]*'${type_win}'[[:space:]]*\([[:digit:]]*\).*/\1/p') # Query compton for the active window 47 | elif echo "$1" | grep -Eiq '^([[:digit:]][[:digit:]]*|0x[[:xdigit:]][[:xdigit:]]*)$'; then 48 | window="$1" # Accept user-specified window-id if the format is correct 49 | else 50 | echo "$0" "[ selected | focused | window-id ]" 51 | fi 52 | 53 | # Color invert the selected or focused window 54 | if [ -n "$window" ]; then 55 | invert_status="$(${compton_dbus}win_get "${type_win}:${window}" string:invert_color | $SED -n 's/^[[:space:]]*boolean[[:space:]]*\([[:alpha:]]*\).*/\1/p')" 56 | if [ "$invert_status" = true ]; then 57 | invert=0 # Set the window to have normal color 58 | else 59 | invert=1 # Set the window to have inverted color 60 | fi 61 | ${compton_dbus}win_set "${type_win}:${window}" string:invert_color_force "${type_enum}:${invert}" & 62 | else 63 | echo "Cannot find $1 window." 64 | exit 1; 65 | fi 66 | exit 0; 67 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arian8j2/picom-jonaburg-fix/31d25da22b44f37cbb9be49fe5c239ef8d00df12/demo.gif -------------------------------------------------------------------------------- /desc.txt: -------------------------------------------------------------------------------- 1 | Compton is a X compositing window manager, fork of xcompmgr-dana. 2 | -------------------------------------------------------------------------------- /man/meson.build: -------------------------------------------------------------------------------- 1 | mans = ['picom.1', 'picom-trans.1'] 2 | if get_option('with_docs') 3 | a2x = find_program('a2x') 4 | foreach m : mans 5 | custom_target(m, output: [m], input: [m+'.asciidoc'], 6 | command: [a2x, '-a', 7 | 'picom-version='+version, 8 | '--format', 'manpage', '@INPUT@', '-D', 9 | meson.current_build_dir()], 10 | install: true, install_dir: 'share/man/man1/') 11 | endforeach 12 | endif 13 | -------------------------------------------------------------------------------- /man/picom-trans.1.asciidoc: -------------------------------------------------------------------------------- 1 | picom-trans(1) 2 | ================ 3 | :doctype: manpage 4 | :man source: picom 5 | :man version: {picom-version} 6 | :man manual: User Commands 7 | 8 | NAME 9 | ---- 10 | picom-trans - an opacity setter tool 11 | 12 | SYNOPSIS 13 | -------- 14 | 15 | *picom-trans* [-w 'WINDOW_ID'] [-n 'WINDOW_NAME'] [-c] [-s] 'OPACITY' 16 | 17 | DESCRIPTION 18 | ----------- 19 | 20 | *picom-trans* is a bash script that sets '_NET_WM_WINDOW_OPACITY' attribute of a window using standard X11 command-line utilities, including *xprop*(1) and *xwininfo*(1). It is similar to *transset*(1) or *transset-df*(1). 21 | 22 | OPTIONS 23 | ------- 24 | *-w* 'WINDOW_ID':: 25 | Specify the window id of the target window. 26 | 27 | *-n* 'WINDOW_NAME':: 28 | Specify and try to match a window name. 29 | 30 | *-c*:: 31 | Specify the currently active window as target. Only works if EWMH '_NET_ACTIVE_WINDOW' property exists on root window. 32 | 33 | *-s*:: 34 | Select target window with mouse cursor. This is the default if no window has been specified. 35 | 36 | *-o* 'OPACITY':: 37 | Specify the new opacity value for the window. This value can be anywhere from 1-100. If it is prefixed with a plus or minus (+/-), this will increment or decrement from the target window's current opacity instead. 38 | 39 | EXAMPLES 40 | -------- 41 | 42 | * Set the opacity of the window with specific window ID to 75%: 43 | + 44 | ------------ 45 | picom-trans -w "$WINDOWID" 75 46 | ------------ 47 | 48 | * Set the opacity of the window with the name "urxvt" to 75%: 49 | + 50 | ------------ 51 | picom-trans -n "urxvt" 75 52 | ------------ 53 | 54 | * Set current window to opacity of 75%: 55 | + 56 | ------------ 57 | picom-trans -c 75 58 | ------------ 59 | 60 | * Select target window and set opacity to 75%: 61 | + 62 | ------------ 63 | picom-trans -s 75 64 | ------------ 65 | 66 | * Increment opacity of current active window by 5%: 67 | + 68 | ------------ 69 | picom-trans -c +5 70 | ------------ 71 | 72 | * Decrement opacity of current active window by 5%: 73 | + 74 | ------------ 75 | picom-trans -c -- -5 76 | ------------ 77 | 78 | BUGS 79 | ---- 80 | Please submit bug reports to . 81 | 82 | SEE ALSO 83 | -------- 84 | link:picom.1.html[*picom*(1)], *xprop*(1), *xwininfo*(1) 85 | -------------------------------------------------------------------------------- /media/compton.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 57 | 58 | 59 | 60 | 61 | 65 | 66 | 67 | 68 | 69 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /media/icons/48x48/compton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arian8j2/picom-jonaburg-fix/31d25da22b44f37cbb9be49fe5c239ef8d00df12/media/icons/48x48/compton.png -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('picom', 'c', version: '7', 2 | default_options: ['c_std=c11']) 3 | 4 | cc = meson.get_compiler('c') 5 | 6 | # use project version by default 7 | version = 'v'+meson.project_version() 8 | 9 | # use git describe if that's available 10 | git = find_program('git', required: false) 11 | if git.found() 12 | gitv = run_command('git', 'rev-parse', '--short=5', 'HEAD') 13 | if gitv.returncode() == 0 14 | version = 'vgit-'+gitv.stdout().strip() 15 | endif 16 | endif 17 | 18 | add_global_arguments('-DCOMPTON_VERSION="'+version+'"', language: 'c') 19 | 20 | if get_option('buildtype') == 'release' 21 | add_global_arguments('-DNDEBUG', language: 'c') 22 | endif 23 | 24 | if get_option('sanitize') 25 | sanitizers = ['address', 'undefined'] 26 | if cc.has_argument('-fsanitize=integer') 27 | sanitizers += ['integer'] 28 | endif 29 | if cc.has_argument('-fsanitize=nullability') 30 | sanitizers += ['nullability'] 31 | endif 32 | add_global_arguments('-fsanitize='+','.join(sanitizers), language: 'c') 33 | add_global_link_arguments('-fsanitize='+','.join(sanitizers), language: 'c') 34 | if cc.has_argument('-fno-sanitize=unsigned-integer-overflow') 35 | add_global_arguments('-fno-sanitize=unsigned-integer-overflow', language: 'c') 36 | endif 37 | endif 38 | 39 | if get_option('modularize') 40 | if not cc.has_argument('-fmodules') 41 | error('option \'modularize\' requires clang') 42 | endif 43 | add_global_arguments(['-fmodules', 44 | '-fmodule-map-file='+ 45 | meson.current_source_dir()+ 46 | '/src/picom.modulemap'], 47 | language: 'c') 48 | endif 49 | 50 | add_global_arguments('-D_GNU_SOURCE', language: 'c') 51 | 52 | if cc.has_header('stdc-predef.h') 53 | add_global_arguments('-DHAS_STDC_PREDEF_H', language: 'c') 54 | endif 55 | 56 | warns = [ 'all', 'cast-function-type', 'ignored-qualifiers', 'missing-parameter-type', 57 | 'nonnull', 'shadow', 'no-type-limits', 'old-style-declaration', 'override-init', 58 | 'sign-compare', 'type-limits', 'uninitialized', 'shift-negative-value', 59 | 'unused-but-set-parameter', 'unused-parameter', 'implicit-fallthrough', 60 | 'no-unknown-warning-option', 'no-missing-braces', 'conversion', 'empty-body' ] 61 | foreach w : warns 62 | if cc.has_argument('-W'+w) 63 | add_global_arguments('-W'+w, language: 'c') 64 | endif 65 | endforeach 66 | 67 | test_h_dep = subproject('test.h').get_variable('test_h_dep') 68 | 69 | subdir('src') 70 | subdir('man') 71 | 72 | install_data('bin/picom-trans', install_dir: get_option('bindir')) 73 | install_data('compton.desktop', install_dir: 'share/applications') 74 | install_data('picom.desktop', install_dir: 'share/applications') 75 | install_data('media/icons/48x48/compton.png', 76 | install_dir: 'share/icons/hicolor/48x48/apps') 77 | install_data('media/compton.svg', 78 | install_dir: 'share/icons/hicolor/scalable/apps') 79 | 80 | meson.add_install_script('meson/install.sh') 81 | -------------------------------------------------------------------------------- /meson/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ ! -e "${MESON_INSTALL_DESTDIR_PREFIX}/bin/compton" ]; then 4 | echo "Linking picom to ${MESON_INSTALL_DESTDIR_PREFIX}/bin/compton" 5 | ln -s picom "${MESON_INSTALL_DESTDIR_PREFIX}/bin/compton" 6 | fi 7 | 8 | if [ ! -e "${MESON_INSTALL_DESTDIR_PREFIX}/bin/compton-trans" ]; then 9 | echo "Linking picom-trans to ${MESON_INSTALL_DESTDIR_PREFIX}/bin/compton-trans" 10 | ln -s picom-trans "${MESON_INSTALL_DESTDIR_PREFIX}/bin/compton-trans" 11 | fi 12 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('sanitize', type: 'boolean', value: false, description: 'Build with sanitizers enabled (deprecated)') 2 | option('config_file', type: 'boolean', value: true, description: 'Enable config file support') 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 | option('dbus', type: 'boolean', value: true, description: 'Enable support for D-Bus remote control') 9 | 10 | option('xrescheck', type: 'boolean', value: false, description: 'Enable X resource leak checker (for debug only)') 11 | 12 | option('with_docs', type: 'boolean', value: false, description: 'Build documentation and man pages') 13 | 14 | option('modularize', type: 'boolean', value: false, description: 'Build with clang\'s module system') 15 | 16 | option('unittest', type: 'boolean', value: false, description: 'Enable unittests in the code') 17 | -------------------------------------------------------------------------------- /picom-dbus.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | Name=picom (dbus) 5 | GenericName=X compositor (dbus) 6 | Comment=A X compositor with dbus backend enabled 7 | Categories=Utility; 8 | Keywords=compositor;composite manager;window effects;transparency;opacity; 9 | TryExec=picom 10 | Exec=picom --dbus 11 | -------------------------------------------------------------------------------- /picom.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | Name=picom 5 | GenericName=X compositor 6 | Comment=A X compositor 7 | Categories=Utility; 8 | Keywords=compositor;composite manager;window effects;transparency;opacity; 9 | TryExec=picom 10 | Exec=picom 11 | # Thanks to quequotion for providing this file! 12 | -------------------------------------------------------------------------------- /src/atom.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "atom.h" 5 | #include "common.h" 6 | #include "utils.h" 7 | #include "log.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_LIST); 34 | #undef ATOM_GET 35 | return atoms; 36 | } 37 | -------------------------------------------------------------------------------- /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_LIST \ 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_TRANSIENT_FOR, \ 20 | WM_WINDOW_ROLE, \ 21 | WM_CLIENT_LEADER, \ 22 | _NET_ACTIVE_WINDOW, \ 23 | _COMPTON_SHADOW, \ 24 | _NET_WM_WINDOW_TYPE, \ 25 | _NET_WM_WINDOW_TYPE_DESKTOP, \ 26 | _NET_WM_WINDOW_TYPE_DOCK, \ 27 | _NET_WM_WINDOW_TYPE_TOOLBAR, \ 28 | _NET_WM_WINDOW_TYPE_MENU, \ 29 | _NET_WM_WINDOW_TYPE_UTILITY, \ 30 | _NET_WM_WINDOW_TYPE_SPLASH, \ 31 | _NET_WM_WINDOW_TYPE_DIALOG, \ 32 | _NET_WM_WINDOW_TYPE_NORMAL, \ 33 | _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, \ 34 | _NET_WM_WINDOW_TYPE_POPUP_MENU, \ 35 | _NET_WM_WINDOW_TYPE_TOOLTIP, \ 36 | _NET_WM_WINDOW_TYPE_NOTIFICATION, \ 37 | _NET_WM_WINDOW_TYPE_COMBO, \ 38 | _NET_WM_WINDOW_TYPE_DND, \ 39 | _NET_WM_STATE, \ 40 | _NET_WM_STATE_FULLSCREEN, \ 41 | _NET_WM_BYPASS_COMPOSITOR 42 | // clang-format on 43 | 44 | #define ATOM_DEF(x) xcb_atom_t a##x 45 | 46 | struct atom { 47 | struct cache *c; 48 | LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST); 49 | }; 50 | 51 | struct atom *init_atoms(xcb_connection_t *); 52 | 53 | static inline xcb_atom_t get_atom(struct atom *a, const char *key) { 54 | return (xcb_atom_t)(intptr_t)cache_get(a->c, key, NULL); 55 | } 56 | 57 | static inline void destroy_atoms(struct atom *a) { 58 | cache_free(a->c); 59 | free(a); 60 | } 61 | -------------------------------------------------------------------------------- /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 "config.h" 9 | #include "compiler.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 ev_loop; 20 | struct backend_operations; 21 | 22 | typedef struct backend_base { 23 | struct backend_operations *ops; 24 | xcb_connection_t *c; 25 | xcb_window_t root; 26 | struct ev_loop *loop; 27 | 28 | /// Whether the backend can accept new render request at the moment 29 | bool busy; 30 | // ... 31 | 32 | // Session data 33 | session_t *ps; 34 | } backend_t; 35 | 36 | typedef void (*backend_ready_callback_t)(void *); 37 | 38 | enum image_operations { 39 | // Invert the color of the entire image, `reg_op` ignored 40 | IMAGE_OP_INVERT_COLOR_ALL, 41 | // Dim the entire image, argument is the percentage. `reg_op` ignored 42 | IMAGE_OP_DIM_ALL, 43 | // Multiply the alpha channel by the argument 44 | IMAGE_OP_APPLY_ALPHA, 45 | // Same as APPLY_ALPHA, but `reg_op` is ignored and the operation applies to the 46 | // full image 47 | IMAGE_OP_APPLY_ALPHA_ALL, 48 | // Change the effective size of the image, without touching the backing image 49 | // itself. When the image is used, the backing image should be tiled to fill its 50 | // effective size. `reg_op` and `reg_visible` is ignored. `arg` is two integers, 51 | // width and height, in that order. 52 | IMAGE_OP_RESIZE_TILE, 53 | // Limit how bright image can be 54 | IMAGE_OP_MAX_BRIGHTNESS, 55 | }; 56 | 57 | struct gaussian_blur_args { 58 | int size; 59 | double deviation; 60 | }; 61 | 62 | struct dual_kawase_blur_args { 63 | int size; 64 | blur_strength_t strength; 65 | }; 66 | 67 | struct box_blur_args { 68 | int size; 69 | }; 70 | 71 | struct kernel_blur_args { 72 | struct conv **kernels; 73 | int kernel_count; 74 | }; 75 | 76 | struct round_corners_args { 77 | int corner_radius; 78 | bool round_borders; 79 | }; 80 | 81 | struct backend_operations { 82 | // =========== Initialization =========== 83 | 84 | /// Initialize the backend, prepare for rendering to the target window. 85 | /// Here is how you should choose target window: 86 | /// 1) if ps->overlay is not XCB_NONE, use that 87 | /// 2) use ps->root otherwise 88 | /// TODO make the target window a parameter 89 | backend_t *(*init)(session_t *)attr_nonnull(1); 90 | void (*deinit)(backend_t *backend_data) attr_nonnull(1); 91 | 92 | /// Called when rendering will be stopped for an unknown amount of 93 | /// time (e.g. when screen is unredirected). Free some resources. 94 | /// 95 | /// Optional, not yet used 96 | void (*pause)(backend_t *backend_data, session_t *ps); 97 | 98 | /// Called before rendering is resumed 99 | /// 100 | /// Optional, not yet used 101 | void (*resume)(backend_t *backend_data, session_t *ps); 102 | 103 | /// Called when root property changed, returns the new 104 | /// backend_data. Even if the backend_data changed, all 105 | /// the existing image data returned by this backend should 106 | /// remain valid. 107 | /// 108 | /// Optional 109 | void *(*root_change)(backend_t *backend_data, session_t *ps); 110 | 111 | // =========== Rendering ============ 112 | 113 | // NOTE: general idea about reg_paint/reg_op vs reg_visible is that reg_visible is 114 | // merely a hint. Ignoring reg_visible entirely don't affect the correctness of 115 | // the operation performed. OTOH reg_paint/reg_op is part of the parameters of the 116 | // operation, and must be honored in order to complete the operation correctly. 117 | 118 | // NOTE: due to complications introduced by use-damage and blur, the rendering API 119 | // is a bit weird. The idea is, `compose` and `blur` have to update a temporary 120 | // buffer, because `blur` requires data from an area slightly larger than the area 121 | // that will be visible. So the area outside the visible area has to be rendered, 122 | // but we have to discard the result (because the result of blurring that area 123 | // will be wrong). That's why we cannot render into the back buffer directly. 124 | // After rendering is done, `present` is called to update a portion of the actual 125 | // back buffer, then present it to the target (or update the target directly, 126 | // if not back buffered). 127 | 128 | /// Called before when a new frame starts. 129 | /// 130 | /// Optional 131 | void (*prepare)(backend_t *backend_data, const region_t *reg_damage); 132 | 133 | /** 134 | * Paint the content of an image onto the rendering buffer 135 | * 136 | * @param backend_data the backend data 137 | * @param image_data the image to paint 138 | * @param dst_x, dst_y the top left corner of the image in the target 139 | * @param reg_paint the clip region, in target coordinates 140 | * @param reg_visible the visible region, in target coordinates 141 | */ 142 | void (*compose)(backend_t *backend_data, struct managed_win *const w, void *image_data, int dst_x, int dst_y, 143 | const region_t *reg_paint, const region_t *reg_visible); 144 | 145 | /// Fill rectangle of the rendering buffer, mostly for debug purposes, optional. 146 | void (*fill)(backend_t *backend_data, struct color, const region_t *clip); 147 | 148 | /// Blur a given region of the rendering buffer. 149 | bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx, 150 | const region_t *reg_blur, const region_t *reg_visible) 151 | attr_nonnull(1, 3, 4, 5); 152 | 153 | /// Round a given region of the rendering buffer. 154 | bool (*round)(backend_t *backend_data, struct managed_win *w, void *round_ctx, 155 | void *image_data, const region_t *reg_round, const region_t *reg_visible) 156 | attr_nonnull(1, 2, 3, 5, 6); 157 | 158 | /// Update part of the back buffer with the rendering buffer, then present the 159 | /// back buffer onto the target window (if not back buffered, update part of the 160 | /// target window directly). 161 | /// 162 | /// Optional, if NULL, indicates the backend doesn't have render output 163 | /// 164 | /// @param region part of the target that should be updated 165 | void (*present)(backend_t *backend_data, const region_t *region) attr_nonnull(1, 2); 166 | 167 | /** 168 | * Bind a X pixmap to the backend's internal image data structure. 169 | * 170 | * @param backend_data backend data 171 | * @param pixmap X pixmap to bind 172 | * @param fmt information of the pixmap's visual 173 | * @param owned whether the ownership of the pixmap is transfered to the backend 174 | * @return backend internal data structure bound with this pixmap 175 | */ 176 | void *(*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap, 177 | struct xvisual_info fmt, bool owned); 178 | 179 | /// Create a shadow image based on the parameters 180 | /// Default implementation: default_backend_render_shadow 181 | void *(*render_shadow)(backend_t *backend_data, int width, int height, 182 | const conv *kernel, double r, double g, double b, double a); 183 | 184 | // ============ Resource management =========== 185 | 186 | /// Free resources associated with an image data structure 187 | void (*release_image)(backend_t *backend_data, void *img_data) attr_nonnull(1, 2); 188 | 189 | // =========== Query =========== 190 | 191 | /// Return if image is not completely opaque. 192 | /// 193 | /// This function is needed because some backend might change the content of the 194 | /// window (e.g. when using a custom shader with the glx backend), so only the 195 | /// backend knows if an image is transparent. 196 | bool (*is_image_transparent)(backend_t *backend_data, void *image_data) 197 | attr_nonnull(1, 2); 198 | 199 | /// Get the age of the buffer content we are currently rendering ontop 200 | /// of. The buffer that has just been `present`ed has a buffer age of 1. 201 | /// Everytime `present` is called, buffers get older. Return -1 if the 202 | /// buffer is empty. 203 | /// 204 | /// Optional 205 | int (*buffer_age)(backend_t *backend_data); 206 | 207 | /// The maximum number buffer_age might return. 208 | int max_buffer_age; 209 | 210 | // =========== Post-processing ============ 211 | /** 212 | * Manipulate an image 213 | * 214 | * @param backend_data backend data 215 | * @param op the operation to perform 216 | * @param image_data an image data structure returned by the backend 217 | * @param reg_op the clip region, define the part of the image to be 218 | * operated on. 219 | * @param reg_visible define the part of the image that will eventually 220 | * be visible on target. this is a hint to the backend 221 | * for optimization purposes. 222 | * @param args extra arguments, operation specific 223 | * @return a new image data structure containing the result 224 | */ 225 | bool (*image_op)(backend_t *backend_data, enum image_operations op, void *image_data, 226 | const region_t *reg_op, const region_t *reg_visible, void *args); 227 | 228 | /// Create another instance of the `image_data`. All `image_op` calls on the 229 | /// returned image should not affect the original image 230 | void *(*copy)(backend_t *base, const void *image_data, const region_t *reg_visible); 231 | 232 | /// Create a blur context that can be used to call `blur` 233 | void *(*create_blur_context)(backend_t *base, enum blur_method, void *args); 234 | /// Destroy a blur context 235 | void (*destroy_blur_context)(backend_t *base, void *ctx); 236 | /// Get how many pixels outside of the blur area is needed for blur 237 | void (*get_blur_size)(void *blur_context, int *width, int *height); 238 | 239 | /// Backup our current window background so we can use it for "erasing" corners 240 | bool (*store_back_texture)(backend_t *base, struct managed_win *w, void *ctx_, 241 | const region_t *reg_tgt, int x, int y, int width, int height); 242 | 243 | /// Create a rounded corners context 244 | void *(*create_round_context)(backend_t *base, void *args); 245 | /// Destroy a rounded corners context 246 | void (*destroy_round_context)(backend_t *base, void *ctx); 247 | 248 | // =========== Hooks ============ 249 | /// Let the backend hook into the event handling queue 250 | /// Not implemented yet 251 | void (*set_ready_callback)(backend_t *, backend_ready_callback_t cb); 252 | /// Called right after the core has handled its events. 253 | /// Not implemented yet 254 | void (*handle_events)(backend_t *); 255 | // =========== Misc ============ 256 | /// Return the driver that is been used by the backend 257 | enum driver (*detect_driver)(backend_t *backend_data); 258 | }; 259 | 260 | extern struct backend_operations *backend_list[]; 261 | 262 | void paint_all_new(session_t *ps, struct managed_win *const t, bool ignore_damage) 263 | attr_nonnull(1); 264 | -------------------------------------------------------------------------------- /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 "config.h" 11 | #include "region.h" 12 | 13 | typedef struct session session_t; 14 | typedef struct win win; 15 | typedef struct conv conv; 16 | typedef struct backend_base backend_t; 17 | struct backend_operations; 18 | 19 | typedef struct dual_kawase_params { 20 | /// Number of downsample passes 21 | int iterations; 22 | /// Pixel offset for down- and upsample 23 | float offset; 24 | /// Save area around blur target (@ref resize_width, @ref resize_height) 25 | int expand; 26 | } dual_kawase_params_t; 27 | 28 | bool build_shadow(xcb_connection_t *, xcb_drawable_t, double opacity, int width, 29 | int height, const conv *kernel, xcb_render_picture_t shadow_pixel, 30 | xcb_pixmap_t *pixmap, xcb_render_picture_t *pict); 31 | 32 | xcb_render_picture_t solid_picture(xcb_connection_t *, xcb_drawable_t, bool argb, 33 | double a, double r, double g, double b); 34 | 35 | xcb_image_t * 36 | make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, int height); 37 | 38 | /// The default implementation of `is_win_transparent`, it simply looks at win::mode. So 39 | /// this is not suitable for backends that alter the content of windows 40 | bool default_is_win_transparent(void *, win *, void *); 41 | 42 | /// The default implementation of `is_frame_transparent`, it uses win::frame_opacity. Same 43 | /// caveat as `default_is_win_transparent` applies. 44 | bool default_is_frame_transparent(void *, win *, void *); 45 | 46 | void * 47 | default_backend_render_shadow(backend_t *backend_data, int width, int height, 48 | const conv *kernel, double r, double g, double b, double a); 49 | 50 | void init_backend_base(struct backend_base *base, session_t *ps); 51 | 52 | struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count); 53 | 54 | struct dual_kawase_params *generate_dual_kawase_params(void *args); 55 | -------------------------------------------------------------------------------- /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 | enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) { 16 | enum driver ret = 0; 17 | // First we try doing backend agnostic detection using RANDR 18 | // There's no way to query the X server about what driver is loaded, so RANDR is 19 | // our best shot. 20 | auto randr_version = xcb_randr_query_version_reply( 21 | c, xcb_randr_query_version(c, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), 22 | NULL); 23 | if (randr_version && 24 | (randr_version->major_version > 1 || randr_version->minor_version >= 4)) { 25 | auto r = xcb_randr_get_providers_reply( 26 | c, xcb_randr_get_providers(c, window), NULL); 27 | if (r == NULL) { 28 | log_warn("Failed to get RANDR providers"); 29 | free(randr_version); 30 | return 0; 31 | } 32 | 33 | auto providers = xcb_randr_get_providers_providers(r); 34 | for (auto i = 0; i < xcb_randr_get_providers_providers_length(r); i++) { 35 | auto r2 = xcb_randr_get_provider_info_reply( 36 | c, xcb_randr_get_provider_info(c, providers[i], r->timestamp), NULL); 37 | if (r2 == NULL) { 38 | continue; 39 | } 40 | if (r2->num_outputs == 0) { 41 | free(r2); 42 | continue; 43 | } 44 | 45 | auto name_len = xcb_randr_get_provider_info_name_length(r2); 46 | assert(name_len >= 0); 47 | auto name = 48 | strndup(xcb_randr_get_provider_info_name(r2), (size_t)name_len); 49 | if (strcasestr(name, "modesetting") != NULL) { 50 | ret |= DRIVER_MODESETTING; 51 | } else if (strcasestr(name, "Radeon") != NULL) { 52 | // Be conservative, add both radeon drivers 53 | ret |= DRIVER_AMDGPU | DRIVER_RADEON; 54 | } else if (strcasestr(name, "NVIDIA") != NULL) { 55 | ret |= DRIVER_NVIDIA; 56 | } else if (strcasestr(name, "nouveau") != NULL) { 57 | ret |= DRIVER_NOUVEAU; 58 | } else if (strcasestr(name, "Intel") != NULL) { 59 | ret |= DRIVER_INTEL; 60 | } 61 | free(name); 62 | free(r2); 63 | } 64 | free(r); 65 | } 66 | free(randr_version); 67 | 68 | // If the backend supports driver detection, use that as well 69 | if (backend_data && backend_data->ops->detect_driver) { 70 | ret |= backend_data->ops->detect_driver(backend_data); 71 | } 72 | return ret; 73 | } 74 | -------------------------------------------------------------------------------- /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 | 9 | struct session; 10 | struct backend_base; 11 | 12 | // A list of known driver quirks: 13 | // * NVIDIA driver doesn't like seeing the same pixmap under different 14 | // ids, so avoid naming the pixmap again when it didn't actually change. 15 | 16 | /// A list of possible drivers. 17 | /// The driver situation is a bit complicated. There are two drivers we care about: the 18 | /// DDX, and the OpenGL driver. They are usually paired, but not always, since there is 19 | /// also the generic modesetting driver. 20 | /// This enum represents _both_ drivers. 21 | enum driver { 22 | DRIVER_AMDGPU = 1, // AMDGPU for DDX, radeonsi for OpenGL 23 | DRIVER_RADEON = 2, // ATI for DDX, mesa r600 for OpenGL 24 | DRIVER_FGLRX = 4, 25 | DRIVER_NVIDIA = 8, 26 | DRIVER_NOUVEAU = 16, 27 | DRIVER_INTEL = 32, 28 | DRIVER_MODESETTING = 64, 29 | }; 30 | 31 | /// Return a list of all drivers currently in use by the X server. 32 | /// Note, this is a best-effort test, so no guarantee all drivers will be detected. 33 | enum driver detect_driver(xcb_connection_t *, struct backend_base *, xcb_window_t); 34 | 35 | // Print driver names to stdout, for diagnostics 36 | static inline void print_drivers(enum driver drivers) { 37 | if (drivers & DRIVER_AMDGPU) { 38 | printf("AMDGPU, "); 39 | } 40 | if (drivers & DRIVER_RADEON) { 41 | printf("Radeon, "); 42 | } 43 | if (drivers & DRIVER_FGLRX) { 44 | printf("fglrx, "); 45 | } 46 | if (drivers & DRIVER_NVIDIA) { 47 | printf("NVIDIA, "); 48 | } 49 | if (drivers & DRIVER_NOUVEAU) { 50 | printf("nouveau, "); 51 | } 52 | if (drivers & DRIVER_INTEL) { 53 | printf("Intel, "); 54 | } 55 | if (drivers & DRIVER_MODESETTING) { 56 | printf("modesetting, "); 57 | } 58 | printf("\b\b \n"); 59 | } 60 | -------------------------------------------------------------------------------- /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 | 28 | struct backend_base *dummy_init(struct session *ps attr_unused) { 29 | auto ret = (struct backend_base *)ccalloc(1, struct dummy_data); 30 | ret->c = ps->c; 31 | ret->loop = ps->loop; 32 | ret->root = ps->root; 33 | ret->busy = false; 34 | return ret; 35 | } 36 | 37 | void dummy_deinit(struct backend_base *data) { 38 | auto dummy = (struct dummy_data *)data; 39 | HASH_ITER2(dummy->images, img) { 40 | log_warn("Backend image for pixmap %#010x is not freed", img->pixmap); 41 | HASH_DEL(dummy->images, img); 42 | free(img->refcount); 43 | free(img); 44 | } 45 | free(dummy); 46 | } 47 | 48 | static void dummy_check_image(struct backend_base *base, const struct dummy_image *img) { 49 | auto dummy = (struct dummy_data *)base; 50 | struct dummy_image *tmp = NULL; 51 | HASH_FIND_INT(dummy->images, &img->pixmap, tmp); 52 | if (!tmp) { 53 | log_warn("Using an invalid (possibly freed) image"); 54 | assert(false); 55 | } 56 | assert(*tmp->refcount > 0); 57 | } 58 | 59 | void dummy_compose(struct backend_base *base, struct managed_win *w attr_unused, void *image, int dst_x attr_unused, 60 | int dst_y attr_unused, const region_t *reg_paint attr_unused, 61 | const region_t *reg_visible attr_unused) { 62 | dummy_check_image(base, image); 63 | } 64 | 65 | void dummy_fill(struct backend_base *backend_data attr_unused, struct color c attr_unused, 66 | const region_t *clip attr_unused) { 67 | } 68 | 69 | bool dummy_blur(struct backend_base *backend_data attr_unused, double opacity attr_unused, 70 | void *blur_ctx attr_unused, const region_t *reg_blur attr_unused, 71 | const region_t *reg_visible attr_unused) { 72 | return true; 73 | } 74 | 75 | bool dummy_round(struct backend_base *backend_data attr_unused, struct managed_win *w attr_unused, 76 | void *ctx_ attr_unused, void *image_data attr_unused, const region_t *reg_round attr_unused, 77 | const region_t *reg_visible attr_unused) { 78 | return true; 79 | } 80 | 81 | void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap, 82 | struct xvisual_info fmt, bool owned attr_unused) { 83 | auto dummy = (struct dummy_data *)base; 84 | struct dummy_image *img = NULL; 85 | HASH_FIND_INT(dummy->images, &pixmap, img); 86 | if (img) { 87 | (*img->refcount)++; 88 | return img; 89 | } 90 | 91 | img = ccalloc(1, struct dummy_image); 92 | img->pixmap = pixmap; 93 | img->transparent = fmt.alpha_size != 0; 94 | img->refcount = ccalloc(1, int); 95 | *img->refcount = 1; 96 | 97 | HASH_ADD_INT(dummy->images, pixmap, img); 98 | return (void *)img; 99 | } 100 | 101 | void dummy_release_image(backend_t *base, void *image) { 102 | auto dummy = (struct dummy_data *)base; 103 | auto img = (struct dummy_image *)image; 104 | assert(*img->refcount > 0); 105 | (*img->refcount)--; 106 | if (*img->refcount == 0) { 107 | HASH_DEL(dummy->images, img); 108 | free(img->refcount); 109 | free(img); 110 | } 111 | } 112 | 113 | bool dummy_is_image_transparent(struct backend_base *base, void *image) { 114 | auto img = (struct dummy_image *)image; 115 | dummy_check_image(base, img); 116 | return img->transparent; 117 | } 118 | 119 | int dummy_buffer_age(struct backend_base *base attr_unused) { 120 | return 2; 121 | } 122 | 123 | bool dummy_image_op(struct backend_base *base, enum image_operations op attr_unused, 124 | void *image, const region_t *reg_op attr_unused, 125 | const region_t *reg_visible attr_unused, void *args attr_unused) { 126 | dummy_check_image(base, image); 127 | return true; 128 | } 129 | 130 | void *dummy_image_copy(struct backend_base *base, const void *image, 131 | const region_t *reg_visible attr_unused) { 132 | auto img = (const struct dummy_image *)image; 133 | dummy_check_image(base, img); 134 | (*img->refcount)++; 135 | return (void *)img; 136 | } 137 | 138 | void *dummy_create_blur_context(struct backend_base *base attr_unused, 139 | enum blur_method method attr_unused, void *args attr_unused) { 140 | static int dummy_context; 141 | return &dummy_context; 142 | } 143 | 144 | void dummy_destroy_blur_context(struct backend_base *base attr_unused, void *ctx attr_unused) { 145 | } 146 | 147 | void *dummy_create_round_context(struct backend_base *base attr_unused, void *args attr_unused) { 148 | static int dummy_context; 149 | return &dummy_context; 150 | } 151 | 152 | void dummy_destroy_round_context(struct backend_base *base attr_unused, void *ctx attr_unused) { 153 | } 154 | 155 | void dummy_get_blur_size(void *ctx attr_unused, int *width, int *height) { 156 | // These numbers are arbitrary, to make sure the reisze_region code path is 157 | // covered. 158 | *width = 5; 159 | *height = 5; 160 | } 161 | 162 | bool dummy_store_back_texture(backend_t *backend_data attr_unused, struct managed_win *w attr_unused, void *ctx_ attr_unused, 163 | const region_t *reg_tgt attr_unused, int x attr_unused, int y attr_unused, int width attr_unused, int height attr_unused) { 164 | return true; 165 | } 166 | 167 | struct backend_operations dummy_ops = { 168 | .init = dummy_init, 169 | .deinit = dummy_deinit, 170 | .compose = dummy_compose, 171 | .fill = dummy_fill, 172 | .blur = dummy_blur, 173 | .round = dummy_round, 174 | .bind_pixmap = dummy_bind_pixmap, 175 | .render_shadow = default_backend_render_shadow, 176 | .release_image = dummy_release_image, 177 | .is_image_transparent = dummy_is_image_transparent, 178 | .buffer_age = dummy_buffer_age, 179 | .max_buffer_age = 5, 180 | 181 | .image_op = dummy_image_op, 182 | .copy = dummy_image_copy, 183 | .create_blur_context = dummy_create_blur_context, 184 | .destroy_blur_context = dummy_destroy_blur_context, 185 | .create_round_context = dummy_create_round_context, 186 | .destroy_round_context = dummy_destroy_round_context, 187 | .get_blur_size = dummy_get_blur_size, 188 | .store_back_texture = dummy_store_back_texture 189 | 190 | }; 191 | -------------------------------------------------------------------------------- /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 | 16 | // Program and uniforms for window shader 17 | typedef struct { 18 | GLuint prog; 19 | GLint unifm_opacity; 20 | GLint unifm_invert_color; 21 | GLint unifm_tex; 22 | GLint unifm_dim; 23 | GLint unifm_brightness; 24 | GLint unifm_max_brightness; 25 | } gl_win_shader_t; 26 | 27 | // Program and uniforms for brightness shader 28 | typedef struct { 29 | GLuint prog; 30 | } gl_brightness_shader_t; 31 | 32 | // Program and uniforms for blur shader 33 | typedef struct { 34 | GLuint prog; 35 | GLint unifm_opacity; 36 | GLint unifm_texture_size; 37 | GLint unifm_halfpixel; 38 | GLint orig_loc; 39 | GLint texorig_loc; 40 | GLint projection_loc; 41 | } gl_blur_shader_t; 42 | 43 | typedef struct { 44 | GLuint prog; 45 | GLint projection_loc; 46 | GLint unifm_radius; 47 | GLint unifm_texcoord; 48 | GLint unifm_texsize; 49 | GLint unifm_borderw; 50 | GLint unifm_resolution; 51 | GLint unifm_tex_bg; 52 | GLint unifm_tex_wnd; 53 | } gl_round_shader_t; 54 | 55 | typedef struct { 56 | GLuint prog; 57 | GLint color_loc; 58 | } gl_fill_shader_t; 59 | 60 | struct gl_texture { 61 | int refcount; 62 | GLuint texture; 63 | int width, height; 64 | bool y_inverted; 65 | 66 | // Textures for auxiliary uses. 67 | GLuint auxiliary_texture[2]; 68 | void *user_data; 69 | }; 70 | 71 | /// @brief Wrapper of a binded GLX texture. 72 | typedef struct gl_image { 73 | struct gl_texture *inner; 74 | double opacity; 75 | double dim; 76 | double max_brightness; 77 | int ewidth, eheight; 78 | bool has_alpha; 79 | bool color_inverted; 80 | } gl_image_t; 81 | 82 | struct gl_data { 83 | backend_t base; 84 | // If we are using proprietary NVIDIA driver 85 | bool is_nvidia; 86 | // Height and width of the viewport 87 | int height, width; 88 | gl_win_shader_t win_shader; 89 | gl_brightness_shader_t brightness_shader; 90 | gl_fill_shader_t fill_shader; 91 | GLuint back_texture, back_fbo; 92 | GLuint present_prog; 93 | 94 | /// Called when an gl_texture is decoupled from the texture it refers. Returns 95 | /// the decoupled user_data 96 | void *(*decouple_texture_user_data)(backend_t *base, void *user_data); 97 | 98 | /// Release the user data attached to a gl_texture 99 | void (*release_user_data)(backend_t *base, struct gl_texture *); 100 | 101 | struct log_target *logger; 102 | }; 103 | 104 | typedef struct session session_t; 105 | 106 | #define GL_PROG_MAIN_INIT \ 107 | { .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, .unifm_tex = -1, } 108 | 109 | GLuint gl_create_shader(GLenum shader_type, const char *shader_str); 110 | GLuint gl_create_program(const GLuint *const shaders, int nshaders); 111 | GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str); 112 | 113 | /** 114 | * @brief Render a region with texture data. 115 | */ 116 | void gl_compose(backend_t *, struct managed_win *, void *ptex, int dst_x, int dst_y, const region_t *reg_tgt, 117 | const region_t *reg_visible); 118 | 119 | void gl_resize(struct gl_data *, int width, int height); 120 | 121 | bool gl_init(struct gl_data *gd, session_t *); 122 | void gl_deinit(struct gl_data *gd); 123 | 124 | GLuint gl_new_texture(GLenum target); 125 | 126 | bool gl_image_op(backend_t *base, enum image_operations op, void *image_data, 127 | const region_t *reg_op, const region_t *reg_visible, void *arg); 128 | 129 | void gl_release_image(backend_t *base, void *image_data); 130 | 131 | void *gl_copy(backend_t *base, const void *image_data, const region_t *reg_visible); 132 | 133 | bool gl_blur(backend_t *base, double opacity, void *, const region_t *reg_blur, 134 | const region_t *reg_visible); 135 | void *gl_create_blur_context(backend_t *base, enum blur_method, void *args); 136 | void gl_destroy_blur_context(backend_t *base, void *ctx); 137 | void gl_get_blur_size(void *blur_context, int *width, int *height); 138 | 139 | 140 | bool gl_round(backend_t *backend_data, struct managed_win *w, void *ctx_, 141 | void *image_data, const region_t *reg_round, const region_t *reg_visible); 142 | void *gl_create_round_context(backend_t *base, void *args); 143 | void gl_destroy_round_context(backend_t *base, void *ctx); 144 | bool gl_store_back_texture(backend_t *backend_data, struct managed_win *w, 145 | void *ctx_, const region_t *reg_tgt, int x, int y, int width, int height); 146 | 147 | bool gl_is_image_transparent(backend_t *base, void *image_data); 148 | void gl_fill(backend_t *base, struct color, const region_t *clip); 149 | 150 | void gl_present(backend_t *base, const region_t *); 151 | 152 | static inline void gl_delete_texture(GLuint texture) { 153 | glDeleteTextures(1, &texture); 154 | } 155 | 156 | /** 157 | * Get a textual representation of an OpenGL error. 158 | */ 159 | static inline const char *gl_get_err_str(GLenum err) { 160 | switch (err) { 161 | CASESTRRET(GL_NO_ERROR); 162 | CASESTRRET(GL_INVALID_ENUM); 163 | CASESTRRET(GL_INVALID_VALUE); 164 | CASESTRRET(GL_INVALID_OPERATION); 165 | CASESTRRET(GL_INVALID_FRAMEBUFFER_OPERATION); 166 | CASESTRRET(GL_OUT_OF_MEMORY); 167 | CASESTRRET(GL_STACK_UNDERFLOW); 168 | CASESTRRET(GL_STACK_OVERFLOW); 169 | } 170 | return NULL; 171 | } 172 | 173 | /** 174 | * Check for GLX error. 175 | * 176 | * http://blog.nobel-joergensen.com/2013/01/29/debugging-opengl-using-glgeterror/ 177 | */ 178 | static inline void gl_check_err_(const char *func, int line) { 179 | GLenum err = GL_NO_ERROR; 180 | 181 | while (GL_NO_ERROR != (err = glGetError())) { 182 | const char *errtext = gl_get_err_str(err); 183 | if (errtext) { 184 | log_printf(tls_logger, LOG_LEVEL_ERROR, func, 185 | "GLX error at line %d: %s", line, errtext); 186 | } else { 187 | log_printf(tls_logger, LOG_LEVEL_ERROR, func, 188 | "GLX error at line %d: %d", line, err); 189 | } 190 | } 191 | } 192 | 193 | static inline void gl_clear_err(void) { 194 | while (glGetError() != GL_NO_ERROR); 195 | } 196 | 197 | #define gl_check_err() gl_check_err_(__func__, __LINE__) 198 | 199 | /** 200 | * Check if a GLX extension exists. 201 | */ 202 | static inline bool gl_has_extension(const char *ext) { 203 | int nexts = 0; 204 | glGetIntegerv(GL_NUM_EXTENSIONS, &nexts); 205 | for (int i = 0; i < nexts || !nexts; i++) { 206 | const char *exti = (const char *)glGetStringi(GL_EXTENSIONS, (GLuint)i); 207 | if (exti == NULL) { 208 | break; 209 | } 210 | if (strcmp(ext, exti) == 0) { 211 | return true; 212 | } 213 | } 214 | gl_clear_err(); 215 | log_info("Missing GL extension %s.", ext); 216 | return false; 217 | } 218 | -------------------------------------------------------------------------------- /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 | }; 58 | 59 | extern struct glxext_info glxext; 60 | 61 | extern PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI; 62 | extern PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI; 63 | extern PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML; 64 | extern PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML; 65 | extern PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT; 66 | extern PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI; 67 | extern PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA; 68 | extern PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT; 69 | extern PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT; 70 | extern PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB; 71 | 72 | void glxext_init(Display *, int screen); 73 | -------------------------------------------------------------------------------- /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') ] 7 | endif 8 | -------------------------------------------------------------------------------- /src/c2.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Compton - a compositor for X11 4 | * 5 | * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard 6 | * 7 | * Copyright (c) 2011-2013, Christopher Jeffrey 8 | * See LICENSE-mit for more information. 9 | * 10 | */ 11 | 12 | #pragma once 13 | 14 | #include 15 | 16 | typedef struct _c2_lptr c2_lptr_t; 17 | typedef struct session session_t; 18 | struct managed_win; 19 | 20 | c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data); 21 | 22 | c2_lptr_t *c2_free_lptr(c2_lptr_t *lp); 23 | 24 | bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst, void **pdata); 25 | 26 | bool c2_list_postprocess(session_t *ps, c2_lptr_t *list); 27 | -------------------------------------------------------------------------------- /src/cache.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "compiler.h" 4 | #include "utils.h" 5 | #include "cache.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_get(struct cache *c, const char *key, int *err) { 21 | struct cache_entry *e; 22 | HASH_FIND_STR(c->entries, key, e); 23 | if (e) { 24 | return e->value; 25 | } 26 | 27 | int tmperr; 28 | if (!err) { 29 | err = &tmperr; 30 | } 31 | 32 | *err = 0; 33 | e = ccalloc(1, struct cache_entry); 34 | e->key = strdup(key); 35 | e->value = c->getter(c->user_data, key, err); 36 | if (*err) { 37 | free(e->key); 38 | free(e); 39 | return NULL; 40 | } 41 | 42 | HASH_ADD_STR(c->entries, key, e); 43 | return e->value; 44 | } 45 | 46 | static inline void _cache_invalidate(struct cache *c, struct cache_entry *e) { 47 | if (c->free) { 48 | c->free(c->user_data, e->value); 49 | } 50 | free(e->key); 51 | HASH_DEL(c->entries, e); 52 | free(e); 53 | } 54 | 55 | void cache_invalidate(struct cache *c, const char *key) { 56 | struct cache_entry *e; 57 | HASH_FIND_STR(c->entries, key, e); 58 | 59 | if (e) { 60 | _cache_invalidate(c, e); 61 | } 62 | } 63 | 64 | void cache_invalidate_all(struct cache *c) { 65 | struct cache_entry *e, *tmpe; 66 | HASH_ITER(hh, c->entries, e, tmpe) { 67 | _cache_invalidate(c, e); 68 | } 69 | } 70 | 71 | void *cache_free(struct cache *c) { 72 | void *ret = c->user_data; 73 | cache_invalidate_all(c); 74 | free(c); 75 | return ret; 76 | } 77 | 78 | struct cache *new_cache(void *ud, cache_getter_t getter, cache_free_t f) { 79 | auto c = ccalloc(1, struct cache); 80 | c->user_data = ud; 81 | c->getter = getter; 82 | c->free = f; 83 | return c; 84 | } 85 | -------------------------------------------------------------------------------- /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 | struct cache *new_cache(void *user_data, cache_getter_t getter, cache_free_t f); 8 | 9 | void *cache_get(struct cache *, const char *key, int *err); 10 | void cache_invalidate(struct cache *, const char *key); 11 | void cache_invalidate_all(struct cache *); 12 | 13 | /// Returns the user data passed to `new_cache` 14 | void *cache_free(struct cache *); 15 | -------------------------------------------------------------------------------- /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 | #define auto __auto_type 10 | #define likely(x) __builtin_expect(!!(x), 1) 11 | #define unlikely(x) __builtin_expect(!!(x), 0) 12 | #define likely_if(x) if (likely(x)) 13 | #define unlikely_if(x) if (unlikely(x)) 14 | 15 | #ifndef __has_attribute 16 | # if __GNUC__ >= 4 17 | # define __has_attribute(x) 1 18 | # else 19 | # define __has_attribute(x) 0 20 | # endif 21 | #endif 22 | 23 | #if __has_attribute(const) 24 | # define attr_const __attribute__((const)) 25 | #else 26 | # define attr_const 27 | #endif 28 | 29 | #if __has_attribute(format) 30 | # define attr_printf(a, b) __attribute__((format(printf, a, b))) 31 | #else 32 | # define attr_printf(a, b) 33 | #endif 34 | 35 | #if __has_attribute(pure) 36 | # define attr_pure __attribute__((pure)) 37 | #else 38 | # define attr_pure 39 | #endif 40 | 41 | #if __has_attribute(unused) 42 | # define attr_unused __attribute__((unused)) 43 | #else 44 | # define attr_unused 45 | #endif 46 | 47 | #if __has_attribute(warn_unused_result) 48 | # define attr_warn_unused_result __attribute__((warn_unused_result)) 49 | #else 50 | # define attr_warn_unused_result 51 | #endif 52 | // An alias for conveninence 53 | #define must_use attr_warn_unused_result 54 | 55 | #if __has_attribute(nonnull) 56 | # define attr_nonnull(...) __attribute__((nonnull(__VA_ARGS__))) 57 | # define attr_nonnull_all __attribute__((nonnull)) 58 | #else 59 | # define attr_nonnull(...) 60 | # define attr_nonnull_all 61 | #endif 62 | 63 | #if __has_attribute(returns_nonnull) 64 | # define attr_ret_nonnull __attribute__((returns_nonnull)) 65 | #else 66 | # define attr_ret_nonnull 67 | #endif 68 | 69 | #if __has_attribute(deprecated) 70 | # define attr_deprecated __attribute__((deprecated)) 71 | #else 72 | # define attr_deprecated 73 | #endif 74 | 75 | #if __has_attribute(malloc) 76 | # define attr_malloc __attribute__((malloc)) 77 | #else 78 | # define attr_malloc 79 | #endif 80 | 81 | #if __STDC_VERSION__ >= 201112L 82 | # define attr_noret _Noreturn 83 | #else 84 | # if __has_attribute(noreturn) 85 | # define attr_noret __attribute__((noreturn)) 86 | # else 87 | # define attr_noret 88 | # endif 89 | #endif 90 | 91 | #if defined(__GNUC__) || defined(__clang__) 92 | # define unreachable __builtin_unreachable() 93 | #else 94 | # define unreachable do {} while(0) 95 | #endif 96 | 97 | #ifndef __has_include 98 | # define __has_include(x) 0 99 | #endif 100 | 101 | #if !defined(__STDC_NO_THREADS__) && __has_include() 102 | # include 103 | #elif __STDC_VERSION__ >= 201112L 104 | # define thread_local _Thread_local 105 | #elif defined(__GNUC__) || defined(__clang__) 106 | # define thread_local __thread 107 | #else 108 | # define thread_local _Pragma("GCC error \"No thread local storage support\"") __error__ 109 | #endif 110 | 111 | typedef unsigned long ulong; 112 | typedef unsigned int uint; 113 | 114 | static inline int popcount(uint x) { 115 | return __builtin_popcount(x); 116 | } 117 | -------------------------------------------------------------------------------- /src/dbus.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Compton - a compositor for X11 4 | * 5 | * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard 6 | * 7 | * Copyright (c) 2011-2013, Christopher Jeffrey 8 | * See LICENSE-mit for more information. 9 | * 10 | */ 11 | 12 | #include 13 | 14 | #include 15 | 16 | typedef struct session session_t; 17 | struct win; 18 | 19 | /** 20 | * Return a string representation of a D-Bus message type. 21 | */ 22 | static inline const char *cdbus_repr_msgtype(DBusMessage *msg) { 23 | return dbus_message_type_to_string(dbus_message_get_type(msg)); 24 | } 25 | 26 | /** 27 | * Initialize D-Bus connection. 28 | */ 29 | bool cdbus_init(session_t *ps, const char *uniq_name); 30 | 31 | /** 32 | * Destroy D-Bus connection. 33 | */ 34 | void cdbus_destroy(session_t *ps); 35 | 36 | /// Generate dbus win_added signal 37 | void cdbus_ev_win_added(session_t *ps, struct win *w); 38 | 39 | /// Generate dbus win_destroyed signal 40 | void cdbus_ev_win_destroyed(session_t *ps, struct win *w); 41 | 42 | /// Generate dbus win_mapped signal 43 | void cdbus_ev_win_mapped(session_t *ps, struct win *w); 44 | 45 | /// Generate dbus win_unmapped signal 46 | void cdbus_ev_win_unmapped(session_t *ps, struct win *w); 47 | 48 | /// Generate dbus win_focusout signal 49 | void cdbus_ev_win_focusout(session_t *ps, struct win *w); 50 | 51 | /// Generate dbus win_focusin signal 52 | void cdbus_ev_win_focusin(session_t *ps, struct win *w); 53 | 54 | // vim: set noet sw=8 ts=8 : 55 | -------------------------------------------------------------------------------- /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 "diagnostic.h" 10 | #include "config.h" 11 | #include "picom.h" 12 | #include "common.h" 13 | 14 | void print_diagnostics(session_t *ps, const char *config_file, bool compositor_running) { 15 | printf("**Version:** " COMPTON_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", 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 | 40 | // vim: set noet sw=8 ts=8 : 41 | -------------------------------------------------------------------------------- /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 | double factor = erf(size / r / sqrt(2)); 94 | double a = exp(-0.5 * size * size / (r * r)) / sqrt(2 * M_PI) / r; 95 | return a / factor; 96 | } 97 | 98 | /// Pick a suitable gaussian kernel radius for a given kernel size. The returned radius 99 | /// is the maximum possible radius (<= size*2) that satisfies no sum of the rows in 100 | /// the kernel are less than `row_limit` (up to certain precision). 101 | static inline double gaussian_kernel_std_for_size(int size, double row_limit) { 102 | assert(size > 0); 103 | if (row_limit >= 1.0 / 2.0 / size) { 104 | return size * 2; 105 | } 106 | double l = 0, r = size * 2; 107 | while (r - l > 1e-2) { 108 | double mid = (l + r) / 2.0; 109 | double vmid = estimate_first_row_sum(size, mid); 110 | if (vmid > row_limit) { 111 | r = mid; 112 | } else { 113 | l = mid; 114 | } 115 | } 116 | return (l + r) / 2.0; 117 | } 118 | 119 | /// Create a gaussian kernel with auto detected standard deviation. The choosen standard 120 | /// deviation tries to make sure the outer most pixels of the shadow are completely 121 | /// transparent, so the transition from shadow to the background is smooth. 122 | /// 123 | /// @param[in] shadow_radius the radius of the shadow 124 | conv *gaussian_kernel_autodetect_deviation(int shadow_radius) { 125 | assert(shadow_radius >= 0); 126 | int size = shadow_radius * 2 + 1; 127 | 128 | if (shadow_radius == 0) { 129 | return gaussian_kernel(0, size); 130 | } 131 | double std = gaussian_kernel_std_for_size(shadow_radius, 1.0 / 256.0); 132 | return gaussian_kernel(std, size); 133 | } 134 | 135 | /// preprocess kernels to make shadow generation faster 136 | /// shadow_sum[x*d+y] is the sum of the kernel from (0, 0) to (x, y), inclusive 137 | void sum_kernel_preprocess(conv *map) { 138 | if (map->rsum) { 139 | free(map->rsum); 140 | } 141 | 142 | auto sum = map->rsum = ccalloc(map->w * map->h, double); 143 | sum[0] = map->data[0]; 144 | 145 | for (int x = 1; x < map->w; x++) { 146 | sum[x] = sum[x - 1] + map->data[x]; 147 | } 148 | 149 | const int d = map->w; 150 | for (int y = 1; y < map->h; y++) { 151 | sum[y * d] = sum[(y - 1) * d] + map->data[y * d]; 152 | for (int x = 1; x < map->w; x++) { 153 | double tmp = sum[(y - 1) * d + x] + sum[y * d + x - 1] - 154 | sum[(y - 1) * d + x - 1]; 155 | sum[y * d + x] = tmp + map->data[y * d + x]; 156 | } 157 | } 158 | } 159 | 160 | // vim: set noet sw=8 ts=8 : 161 | -------------------------------------------------------------------------------- /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 | /// Create a gaussian kernel with auto detected standard deviation. The choosen standard 26 | /// deviation tries to make sure the outer most pixels of the shadow are completely 27 | /// transparent. 28 | /// 29 | /// @param[in] shadow_radius the radius of the shadow 30 | conv *gaussian_kernel_autodetect_deviation(int shadow_radius); 31 | 32 | /// preprocess kernels to make shadow generation faster 33 | /// shadow_sum[x*d+y] is the sum of the kernel from (0, 0) to (x, y), inclusive 34 | void sum_kernel_preprocess(conv *map); 35 | 36 | static inline void free_conv(conv *k) { 37 | free(k->rsum); 38 | free(k); 39 | } 40 | -------------------------------------------------------------------------------- /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/glx.h" 14 | #include "backend/gl/gl_common.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 | auto tm = localtime(&ts.tv_sec); 164 | char time_buf[100]; 165 | strftime(time_buf, sizeof time_buf, "%x %T", tm); 166 | 167 | char *time = NULL; 168 | int tlen = asprintf(&time, "%s.%03ld", time_buf, ts.tv_nsec / 1000000); 169 | if (tlen < 0 || !time) { 170 | free(buf); 171 | free(time); 172 | return; 173 | } 174 | 175 | const char *log_level_str = log_level_to_string(level); 176 | size_t llen = strlen(log_level_str); 177 | size_t flen = strlen(func); 178 | 179 | struct log_target *head = l->head; 180 | while (head) { 181 | const char *p = "", *s = ""; 182 | size_t plen = 0, slen = 0; 183 | 184 | if (head->ops->colorize_begin) { 185 | // construct target specific prefix 186 | p = head->ops->colorize_begin(level); 187 | plen = strlen(p); 188 | if (head->ops->colorize_end) { 189 | s = head->ops->colorize_end(level); 190 | slen = strlen(s); 191 | } 192 | } 193 | head->ops->writev( 194 | head, 195 | (struct iovec[]){{.iov_base = "[ ", .iov_len = 2}, 196 | {.iov_base = time, .iov_len = (size_t)tlen}, 197 | {.iov_base = " ", .iov_len = 1}, 198 | {.iov_base = (void *)func, .iov_len = flen}, 199 | {.iov_base = " ", .iov_len = 1}, 200 | {.iov_base = (void *)p, .iov_len = plen}, 201 | {.iov_base = (void *)log_level_str, .iov_len = llen}, 202 | {.iov_base = (void *)s, .iov_len = slen}, 203 | {.iov_base = " ] ", .iov_len = 3}, 204 | {.iov_base = buf, .iov_len = (size_t)blen}, 205 | {.iov_base = "\n", .iov_len = 1}}, 206 | 11); 207 | head = head->next; 208 | } 209 | free(time); 210 | free(buf); 211 | } 212 | 213 | /// A trivial deinitializer that simply frees the memory 214 | static attr_unused void logger_trivial_destroy(struct log_target *tgt) { 215 | free(tgt); 216 | } 217 | 218 | /// A null log target that does nothing 219 | static const struct log_ops null_logger_ops; 220 | static struct log_target null_logger_target = { 221 | .ops = &null_logger_ops, 222 | }; 223 | 224 | struct log_target *null_logger_new(void) { 225 | return &null_logger_target; 226 | } 227 | 228 | static void null_logger_write(struct log_target *tgt attr_unused, 229 | const char *str attr_unused, size_t len attr_unused) { 230 | return; 231 | } 232 | 233 | static void null_logger_writev(struct log_target *tgt attr_unused, 234 | const struct iovec *vec attr_unused, int vcnt attr_unused) { 235 | return; 236 | } 237 | 238 | static const struct log_ops null_logger_ops = { 239 | .write = null_logger_write, 240 | .writev = null_logger_writev, 241 | }; 242 | 243 | /// A file based logger that writes to file (or stdout/stderr) 244 | struct file_logger { 245 | struct log_target tgt; 246 | FILE *f; 247 | struct log_ops ops; 248 | }; 249 | 250 | static void file_logger_write(struct log_target *tgt, const char *str, size_t len) { 251 | auto f = (struct file_logger *)tgt; 252 | fwrite(str, 1, len, f->f); 253 | } 254 | 255 | static void file_logger_writev(struct log_target *tgt, const struct iovec *vec, int vcnt) { 256 | auto f = (struct file_logger *)tgt; 257 | fflush(f->f); 258 | writev(fileno(f->f), vec, vcnt); 259 | } 260 | 261 | static void file_logger_destroy(struct log_target *tgt) { 262 | auto f = (struct file_logger *)tgt; 263 | fclose(f->f); 264 | free(tgt); 265 | } 266 | 267 | #define ANSI(x) "\033[" x "m" 268 | static const char *terminal_colorize_begin(enum log_level level) { 269 | switch (level) { 270 | case LOG_LEVEL_TRACE: return ANSI("30;2"); 271 | case LOG_LEVEL_DEBUG: return ANSI("37;2"); 272 | case LOG_LEVEL_INFO: return ANSI("92"); 273 | case LOG_LEVEL_WARN: return ANSI("33"); 274 | case LOG_LEVEL_ERROR: return ANSI("31;1"); 275 | case LOG_LEVEL_FATAL: return ANSI("30;103;1"); 276 | default: return ""; 277 | } 278 | } 279 | 280 | static const char *terminal_colorize_end(enum log_level level attr_unused) { 281 | return ANSI("0"); 282 | } 283 | #undef PREFIX 284 | 285 | static const struct log_ops file_logger_ops = { 286 | .write = file_logger_write, 287 | .writev = file_logger_writev, 288 | .destroy = file_logger_destroy, 289 | }; 290 | 291 | struct log_target *file_logger_new(const char *filename) { 292 | FILE *f = fopen(filename, "a"); 293 | if (!f) { 294 | return NULL; 295 | } 296 | 297 | auto ret = cmalloc(struct file_logger); 298 | ret->tgt.ops = &ret->ops; 299 | ret->f = f; 300 | 301 | // Always assume a file is not a terminal 302 | ret->ops = file_logger_ops; 303 | 304 | return &ret->tgt; 305 | } 306 | 307 | struct log_target *stderr_logger_new(void) { 308 | int fd = dup(STDERR_FILENO); 309 | if (fd < 0) { 310 | return NULL; 311 | } 312 | 313 | FILE *f = fdopen(fd, "w"); 314 | if (!f) { 315 | return NULL; 316 | } 317 | 318 | auto ret = cmalloc(struct file_logger); 319 | ret->tgt.ops = &ret->ops; 320 | ret->f = f; 321 | ret->ops = file_logger_ops; 322 | 323 | if (isatty(fd)) { 324 | ret->ops.colorize_begin = terminal_colorize_begin; 325 | ret->ops.colorize_end = terminal_colorize_end; 326 | } 327 | return &ret->tgt; 328 | } 329 | 330 | #ifdef CONFIG_OPENGL 331 | /// An opengl logger that can be used for logging into opengl debugging tools, 332 | /// such as apitrace 333 | struct gl_string_marker_logger { 334 | struct log_target tgt; 335 | PFNGLSTRINGMARKERGREMEDYPROC gl_string_marker; 336 | }; 337 | 338 | static void gl_string_marker_logger_write(struct log_target *tgt, const char *str, size_t len) { 339 | auto g = (struct gl_string_marker_logger *)tgt; 340 | g->gl_string_marker((GLsizei)len, str); 341 | } 342 | 343 | static const struct log_ops gl_string_marker_logger_ops = { 344 | .write = gl_string_marker_logger_write, 345 | .writev = log_default_writev, 346 | .destroy = logger_trivial_destroy, 347 | }; 348 | 349 | struct log_target *gl_string_marker_logger_new(void) { 350 | if (!gl_has_extension("GL_GREMEDY_string_marker")) { 351 | return NULL; 352 | } 353 | 354 | void *fnptr = glXGetProcAddress((GLubyte *)"glStringMarkerGREMEDY"); 355 | if (!fnptr) 356 | return NULL; 357 | 358 | auto ret = cmalloc(struct gl_string_marker_logger); 359 | ret->tgt.ops = &gl_string_marker_logger_ops; 360 | ret->gl_string_marker = fnptr; 361 | return &ret->tgt; 362 | } 363 | 364 | #else 365 | struct log_target *gl_string_marker_logger_new(void) { 366 | return NULL; 367 | } 368 | #endif 369 | 370 | // vim: set noet sw=8 ts=8: 371 | -------------------------------------------------------------------------------- /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('picom.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') ] 13 | picom_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' 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('config_file') 41 | deps += [dependency('libconfig', version: '>=1.4', required: true)] 42 | 43 | cflags += ['-DCONFIG_LIBCONFIG'] 44 | srcs += [ 'config_libconfig.c' ] 45 | endif 46 | if get_option('regex') 47 | pcre = dependency('libpcre', required: true) 48 | cflags += ['-DCONFIG_REGEX_PCRE'] 49 | if pcre.version().version_compare('>=8.20') 50 | cflags += ['-DCONFIG_REGEX_PCRE_JIT'] 51 | endif 52 | deps += [pcre] 53 | endif 54 | 55 | if get_option('vsync_drm') 56 | cflags += ['-DCONFIG_VSYNC_DRM'] 57 | deps += [dependency('libdrm', required: true)] 58 | endif 59 | 60 | if get_option('opengl') 61 | cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES'] 62 | deps += [dependency('gl', required: true)] 63 | srcs += [ 'opengl.c' ] 64 | endif 65 | 66 | if get_option('dbus') 67 | cflags += ['-DCONFIG_DBUS'] 68 | deps += [dependency('dbus-1', required: true)] 69 | srcs += [ 'dbus.c' ] 70 | endif 71 | 72 | if get_option('xrescheck') 73 | cflags += ['-DDEBUG_XRC'] 74 | srcs += [ 'xrescheck.c' ] 75 | endif 76 | 77 | if get_option('unittest') 78 | cflags += ['-DUNIT_TEST'] 79 | endif 80 | 81 | host_system = host_machine.system() 82 | if host_system == 'linux' 83 | cflags += ['-DHAS_INOTIFY'] 84 | elif host_system == 'freebsd' or host_system == 'netbsd' or 85 | host_system == 'dragonfly' or host_system == 'openbsd' 86 | cflags += ['-DHAS_KQUEUE'] 87 | endif 88 | 89 | subdir('backend') 90 | 91 | picom = executable('picom', srcs, c_args: cflags, 92 | dependencies: [ base_deps, deps, test_h_dep ], 93 | install: true, include_directories: picom_inc) 94 | 95 | if get_option('unittest') 96 | test('picom unittest', picom, args: [ '--unittest' ]) 97 | endif 98 | -------------------------------------------------------------------------------- /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 | * Compton - a compositor for X11 4 | * 5 | * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard 6 | * 7 | * Copyright (c) 2011-2013, Christopher Jeffrey 8 | * See LICENSE-mit for more information. 9 | * 10 | */ 11 | 12 | #pragma once 13 | 14 | #include "common.h" 15 | #include "compiler.h" 16 | #include "log.h" 17 | #include "region.h" 18 | #include "render.h" 19 | #include "win.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | typedef struct { 31 | /// Fragment shader for blur. 32 | GLuint frag_shader; 33 | /// GLSL program for blur. 34 | GLuint prog; 35 | /// Location of uniform "offset_x" in blur GLSL program. 36 | GLint unifm_offset_x; 37 | /// Location of uniform "offset_y" in blur GLSL program. 38 | GLint unifm_offset_y; 39 | /// Location of uniform "opacity" in conv-blur and (dual filter) kawase-blur GLSL program. 40 | GLint unifm_opacity; 41 | /// Location of uniform "offset" in kawase-blur GLSL program. 42 | GLint unifm_offset; 43 | /// Location of uniform "halfpixel" in kawase-blur GLSL program. 44 | GLint unifm_halfpixel; 45 | /// Location of uniform "fulltex" in kawase-blur GLSL program. 46 | GLint unifm_fulltex; 47 | } glx_blur_pass_t; 48 | 49 | typedef struct { 50 | /// Fragment shader for rounded corners. 51 | GLuint frag_shader; 52 | /// GLSL program for rounded corners. 53 | GLuint prog; 54 | /// Location of uniform "radius" in rounded-corners GLSL program. 55 | GLint unifm_radius; 56 | /// Location of uniform "texcoord" in rounded-corners GLSL program. 57 | GLint unifm_texcoord; 58 | /// Location of uniform "texsize" in rounded-corners GLSL program. 59 | GLint unifm_texsize; 60 | /// Location of uniform "borderw" in rounded-corners GLSL program. 61 | GLint unifm_borderw; 62 | /// Location of uniform "borderc" in rounded-corners GLSL program. 63 | GLint unifm_borderc; 64 | /// Location of uniform "resolution" in rounded-corners GLSL program. 65 | GLint unifm_resolution; 66 | /// Location of uniform "texture_scr" in rounded-corners GLSL program. 67 | GLint unifm_tex_scr; 68 | /// Location of uniform "texture_wnd" in rounded-corners GLSL program. 69 | GLint unifm_tex_wnd; 70 | 71 | } glx_round_pass_t; 72 | 73 | /// Structure containing GLX-dependent data for a session. 74 | typedef struct glx_session { 75 | // === OpenGL related === 76 | /// GLX context. 77 | GLXContext context; 78 | /// Whether we have GL_ARB_texture_non_power_of_two. 79 | bool has_texture_non_power_of_two; 80 | /// Current GLX Z value. 81 | int z; 82 | /// Cached blur textures for every pass 83 | glx_blur_cache_t blur_cache; 84 | glx_blur_pass_t *blur_passes; 85 | glx_round_pass_t *round_passes; 86 | } glx_session_t; 87 | 88 | /// @brief Wrapper of a binded GLX texture. 89 | typedef struct _glx_texture { 90 | GLuint texture; 91 | GLXPixmap glpixmap; 92 | xcb_pixmap_t pixmap; 93 | GLenum target; 94 | int width; 95 | int height; 96 | bool y_inverted; 97 | } glx_texture_t; 98 | 99 | #define CGLX_SESSION_INIT \ 100 | { .context = NULL } 101 | 102 | bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z, 103 | GLfloat factor, const region_t *reg_tgt); 104 | 105 | bool glx_render(session_t *ps, struct managed_win *w, const glx_texture_t *ptex, int x, int y, int dx, int dy, 106 | int width, int height, int z, double opacity, bool argb, bool neg, int cr, 107 | const region_t *reg_tgt, const glx_prog_main_t *pprogram); 108 | 109 | bool glx_init(session_t *ps, bool need_render); 110 | 111 | void glx_destroy(session_t *ps); 112 | 113 | void glx_on_root_change(session_t *ps); 114 | 115 | bool glx_init_blur(session_t *ps); 116 | 117 | bool glx_init_rounded_corners(session_t *ps); 118 | 119 | #ifdef CONFIG_OPENGL 120 | bool glx_load_prog_main(const char *vshader_str, const char *fshader_str, 121 | glx_prog_main_t *pprogram); 122 | #endif 123 | 124 | bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, int width, 125 | int height, bool repeat, const struct glx_fbconfig_info *); 126 | 127 | void glx_release_pixmap(session_t *ps, glx_texture_t *ptex); 128 | 129 | bool glx_bind_texture(session_t *ps, glx_texture_t **pptex, 130 | int x, int y, int width, int height, bool repeat); 131 | 132 | void glx_release_texture(session_t *ps, glx_texture_t **ptex); 133 | 134 | void glx_paint_pre(session_t *ps, region_t *preg) attr_nonnull(1, 2); 135 | 136 | /** 137 | * Check if a texture is binded, or is binded to the given pixmap. 138 | */ 139 | static inline bool glx_tex_binded(const glx_texture_t *ptex, xcb_pixmap_t pixmap) { 140 | return ptex && ptex->glpixmap && ptex->texture && (!pixmap || pixmap == ptex->pixmap); 141 | } 142 | 143 | void glx_set_clip(session_t *ps, const region_t *reg); 144 | 145 | bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, 146 | double opacity, const region_t *reg_tgt, glx_blur_cache_t *pbc); 147 | 148 | bool glx_round_corners_dst0(session_t *ps, struct managed_win *w, const glx_texture_t *ptex, int shader_idx, 149 | int dx, int dy, int width, int height, float z, float cr, 150 | const region_t *reg_tgt, glx_blur_cache_t *pbc); 151 | 152 | bool glx_round_corners_dst1(session_t *ps, struct managed_win *w, const glx_texture_t *ptex, int shader_idx, 153 | int dx, int dy, int width, int height, float z, float cr, 154 | const region_t *reg_tgt, glx_blur_cache_t *pbc); 155 | 156 | GLuint glx_create_shader(GLenum shader_type, const char *shader_str); 157 | 158 | GLuint glx_create_program(const GLuint *const shaders, int nshaders); 159 | 160 | GLuint glx_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str); 161 | 162 | unsigned char *glx_take_screenshot(session_t *ps, int *out_length); 163 | 164 | /** 165 | * Check if there's a GLX context. 166 | */ 167 | static inline bool glx_has_context(session_t *ps) { 168 | return ps->psglx && ps->psglx->context; 169 | } 170 | 171 | /** 172 | * Ensure we have a GLX context. 173 | */ 174 | static inline bool ensure_glx_context(session_t *ps) { 175 | // Create GLX context 176 | if (!glx_has_context(ps)) 177 | glx_init(ps, false); 178 | 179 | return ps->psglx->context; 180 | } 181 | 182 | /** 183 | * Free a GLX texture. 184 | */ 185 | static inline void free_texture_r(session_t *ps attr_unused, GLuint *ptexture) { 186 | if (*ptexture) { 187 | assert(glx_has_context(ps)); 188 | glDeleteTextures(1, ptexture); 189 | *ptexture = 0; 190 | } 191 | } 192 | 193 | /** 194 | * Free a GLX Framebuffer object. 195 | */ 196 | static inline void free_glx_fbo(GLuint *pfbo) { 197 | if (*pfbo) { 198 | glDeleteFramebuffers(1, pfbo); 199 | *pfbo = 0; 200 | } 201 | assert(!*pfbo); 202 | } 203 | 204 | /** 205 | * Free data in glx_blur_cache_t on resize. 206 | */ 207 | static inline void free_glx_bc_resize(session_t *ps, glx_blur_cache_t *pbc) { 208 | for (int i = 0; i < MAX_BLUR_PASS; i++) { 209 | free_texture_r(ps, &pbc->textures[i]); 210 | pbc->width[i] = 0; 211 | pbc->height[i] = 0; 212 | } 213 | } 214 | 215 | /** 216 | * Free a glx_blur_cache_t 217 | */ 218 | static inline void free_glx_bc(session_t *ps, glx_blur_cache_t *pbc) { 219 | for (int i = 0; i < MAX_BLUR_PASS; i++) 220 | free_glx_fbo(&pbc->fbos[i]); 221 | free_glx_bc_resize(ps, pbc); 222 | } 223 | 224 | /** 225 | * Free a glx_texture_t. 226 | */ 227 | static inline void free_texture(session_t *ps, glx_texture_t **pptex) { 228 | glx_texture_t *ptex = *pptex; 229 | 230 | // Quit if there's nothing 231 | if (!ptex) 232 | return; 233 | 234 | glx_release_pixmap(ps, ptex); 235 | 236 | free_texture_r(ps, &ptex->texture); 237 | 238 | // Free structure itself 239 | free(ptex); 240 | *pptex = NULL; 241 | assert(!*pptex); 242 | } 243 | 244 | /** 245 | * Free GLX part of paint_t. 246 | */ 247 | static inline void free_paint_glx(session_t *ps, paint_t *ppaint) { 248 | free_texture(ps, &ppaint->ptex); 249 | } 250 | 251 | /** 252 | * Free GLX part of win. 253 | */ 254 | static inline void free_win_res_glx(session_t *ps, struct managed_win *w) { 255 | free_paint_glx(ps, &w->paint); 256 | free_paint_glx(ps, &w->shadow_paint); 257 | #ifdef CONFIG_OPENGL 258 | free_glx_bc(ps, &w->glx_blur_cache); 259 | free_glx_bc(ps, &w->glx_round_cache); 260 | free_texture(ps, &w->glx_texture_bg); 261 | free(w->paint.fbcfg); 262 | #endif 263 | } 264 | -------------------------------------------------------------------------------- /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/picom.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 { ROOT_FLAGS_SCREEN_CHANGE = 1 }; 29 | 30 | // == Functions == 31 | // TODO move static inline functions that are only used in picom.c, into 32 | // picom.c 33 | 34 | // inline functions must be made static to compile correctly under clang: 35 | // http://clang.llvm.org/compatibility.html#inline 36 | 37 | void add_damage(session_t *ps, const region_t *damage); 38 | 39 | uint32_t determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode); 40 | 41 | xcb_window_t find_client_win(session_t *ps, xcb_window_t w); 42 | 43 | /// Handle configure event of a root window 44 | void configure_root(session_t *ps, int width, int height); 45 | 46 | void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce); 47 | 48 | void update_refresh_rate(session_t *ps); 49 | 50 | void root_damaged(session_t *ps); 51 | 52 | void cxinerama_upd_scrs(session_t *ps); 53 | 54 | void queue_redraw(session_t *ps); 55 | 56 | void discard_ignore(session_t *ps, unsigned long sequence); 57 | 58 | void set_root_flags(session_t *ps, uint64_t flags); 59 | 60 | void quit(session_t *ps); 61 | 62 | xcb_window_t session_get_target_window(session_t *); 63 | 64 | uint8_t session_redirection_mode(session_t *ps); 65 | 66 | /** 67 | * Set a switch_t array of all unset wintypes to true. 68 | */ 69 | static inline void wintype_arr_enable_unset(switch_t arr[]) { 70 | wintype_t i; 71 | 72 | for (i = 0; i < NUM_WINTYPES; ++i) 73 | if (UNSET == arr[i]) 74 | arr[i] = ON; 75 | } 76 | 77 | /** 78 | * Check if a window ID exists in an array of window IDs. 79 | * 80 | * @param arr the array of window IDs 81 | * @param count amount of elements in the array 82 | * @param wid window ID to search for 83 | */ 84 | static inline bool array_wid_exists(const xcb_window_t *arr, int count, xcb_window_t wid) { 85 | while (count--) { 86 | if (arr[count] == wid) { 87 | return true; 88 | } 89 | } 90 | 91 | return false; 92 | } 93 | 94 | /** 95 | * Destroy a condition list. 96 | */ 97 | static inline void free_wincondlst(c2_lptr_t **pcondlst) { 98 | while ((*pcondlst = c2_free_lptr(*pcondlst))) 99 | continue; 100 | } 101 | 102 | #ifndef CONFIG_OPENGL 103 | static inline void free_paint_glx(session_t *ps attr_unused, paint_t *p attr_unused) { 104 | } 105 | static inline void 106 | free_win_res_glx(session_t *ps attr_unused, struct managed_win *w attr_unused) { 107 | } 108 | #endif 109 | 110 | /** 111 | * Create a XTextProperty of a single string. 112 | */ 113 | static inline XTextProperty *make_text_prop(session_t *ps, char *str) { 114 | XTextProperty *pprop = ccalloc(1, XTextProperty); 115 | 116 | if (XmbTextListToTextProperty(ps->dpy, &str, 1, XStringStyle, pprop)) { 117 | XFree(pprop->value); 118 | free(pprop); 119 | pprop = NULL; 120 | } 121 | 122 | return pprop; 123 | } 124 | 125 | /** 126 | * Set a single-string text property on a window. 127 | */ 128 | static inline bool 129 | wid_set_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop_atom, char *str) { 130 | XTextProperty *pprop = make_text_prop(ps, str); 131 | if (!pprop) { 132 | log_error("Failed to make text property: %s.", str); 133 | return false; 134 | } 135 | 136 | XSetTextProperty(ps->dpy, wid, pprop, prop_atom); 137 | XFree(pprop->value); 138 | XFree(pprop); 139 | 140 | return true; 141 | } 142 | 143 | /** 144 | * Dump an drawable's info. 145 | */ 146 | static inline void dump_drawable(session_t *ps, xcb_drawable_t drawable) { 147 | auto r = xcb_get_geometry_reply(ps->c, xcb_get_geometry(ps->c, drawable), NULL); 148 | if (!r) { 149 | log_trace("Drawable %#010x: Failed", drawable); 150 | return; 151 | } 152 | log_trace("Drawable %#010x: x = %u, y = %u, wid = %u, hei = %d, b = %u, d = %u", 153 | drawable, r->x, r->y, r->width, r->height, r->border_width, r->depth); 154 | free(r); 155 | } 156 | -------------------------------------------------------------------------------- /src/picom.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 picom { 24 | header "picom.h" 25 | } 26 | module types { 27 | header "types.h" 28 | } 29 | module c2 { 30 | header "c2.h" 31 | } 32 | module render { 33 | header "render.h" 34 | } 35 | module options { 36 | header "options.h" 37 | } 38 | module opengl { 39 | header "opengl.h" 40 | } 41 | module diagnostic { 42 | header "diagnostic.h" 43 | } 44 | module win_defs { 45 | header "win_defs.h" 46 | } 47 | module win { 48 | header "win.h" 49 | export win_defs 50 | } 51 | module log { 52 | header "log.h" 53 | export compiler 54 | } 55 | module x { 56 | header "x.h" 57 | } 58 | module vsync { 59 | header "vsync.h" 60 | } 61 | module common { 62 | header "common.h" 63 | } 64 | module config { 65 | header "config.h" 66 | } 67 | module xrescheck { 68 | header "xrescheck.h" 69 | } 70 | module cache { 71 | header "cache.h" 72 | } 73 | module backend { 74 | module gl { 75 | module gl_common { 76 | header "backend/gl/gl_common.h" 77 | } 78 | module glx { 79 | header "backend/gl/glx.h" 80 | export GL.glx 81 | } 82 | } 83 | module backend { 84 | header "backend/backend.h" 85 | } 86 | module backend_common { 87 | header "backend/backend_common.h" 88 | } 89 | } 90 | module xcb [system] { 91 | module xcb { 92 | header "/usr/include/xcb/xcb.h" 93 | export * 94 | } 95 | module randr { 96 | header "/usr/include/xcb/randr.h" 97 | export * 98 | } 99 | module render { 100 | header "/usr/include/xcb/render.h" 101 | export * 102 | } 103 | module sync { 104 | header "/usr/include/xcb/sync.h" 105 | export * 106 | } 107 | module composite { 108 | header "/usr/include/xcb/composite.h" 109 | export * 110 | } 111 | module xfixes { 112 | header "/usr/include/xcb/xfixes.h" 113 | export * 114 | } 115 | module damage { 116 | header "/usr/include/xcb/damage.h" 117 | export * 118 | } 119 | module xproto { 120 | header "/usr/include/xcb/xproto.h" 121 | export * 122 | } 123 | module present { 124 | header "/usr/include/xcb/present.h" 125 | } 126 | module util { 127 | module render { 128 | header "/usr/include/xcb/xcb_renderutil.h" 129 | export * 130 | } 131 | } 132 | } 133 | module X11 [system] { 134 | module Xlib { 135 | header "/usr/include/X11/Xlib.h" 136 | export * 137 | } 138 | module Xutil { 139 | header "/usr/include/X11/Xutil.h" 140 | export * 141 | } 142 | } 143 | module GL [system] { 144 | module glx { 145 | header "/usr/include/GL/glx.h" 146 | export * 147 | } 148 | module gl { 149 | header "/usr/include/GL/gl.h" 150 | export * 151 | } 152 | } 153 | module libc [system] { 154 | export * 155 | module assert { 156 | export * 157 | textual header "/usr/include/assert.h" 158 | } 159 | module string { 160 | export * 161 | header "/usr/include/string.h" 162 | } 163 | module ctype { 164 | export * 165 | header "/usr/include/ctype.h" 166 | } 167 | module errno { 168 | export * 169 | header "/usr/include/errno.h" 170 | } 171 | module fenv { 172 | export * 173 | header "/usr/include/fenv.h" 174 | } 175 | module inttypes { 176 | export * 177 | header "/usr/include/inttypes.h" 178 | } 179 | module math { 180 | export * 181 | header "/usr/include/math.h" 182 | } 183 | module setjmp { 184 | export * 185 | header "/usr/include/setjmp.h" 186 | } 187 | module stdio { 188 | export * 189 | header "/usr/include/stdio.h" 190 | } 191 | 192 | module stdlib [system] { 193 | export * 194 | header "/usr/include/stdlib.h" 195 | } 196 | } 197 | 198 | // glib specific header. In it's own module because it 199 | // doesn't exist on some systems with unpatched glib 2.26+ 200 | module "xlocale.h" [system] { 201 | export * 202 | header "/usr/include/xlocale.h" 203 | } 204 | 205 | // System header that we have difficult with merging. 206 | module "sys_types.h" [system] { 207 | export * 208 | header "/usr/include/sys/types.h" 209 | } 210 | 211 | module "signal.h" [system] { 212 | export * 213 | header "/usr/include/signal.h" 214 | } 215 | -------------------------------------------------------------------------------- /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, struct managed_win *, int x, int y, int dx, int dy, int w, int h, int fullw, int fullh, double opacity, 35 | bool argb, bool neg, int cr, xcb_render_picture_t pict, glx_texture_t *ptex, 36 | const region_t *reg_paint, const glx_prog_main_t *pprogram, clip_t *clip); 37 | void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint); 38 | 39 | void paint_all(session_t *ps, struct managed_win *const t, bool ignore_damage); 40 | 41 | void free_picture(xcb_connection_t *c, xcb_render_picture_t *p); 42 | 43 | void free_paint(session_t *ps, paint_t *ppaint); 44 | void free_root_tile(session_t *ps); 45 | 46 | bool init_render(session_t *ps); 47 | void deinit_render(session_t *ps); 48 | 49 | int maximum_buffer_age(session_t *); 50 | -------------------------------------------------------------------------------- /src/string_utils.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "compiler.h" 9 | #include "string_utils.h" 10 | #include "utils.h" 11 | 12 | #pragma GCC diagnostic push 13 | 14 | // gcc warns about legitimate strncpy in mstrjoin and mstrextend 15 | // strncpy(str, src1, len1) intentional truncates the null byte from src1. 16 | // strncpy(str+len1, src2, len2) uses bound depends on the source argument, 17 | // but str is allocated with len1+len2+1, so this strncpy can't overflow 18 | #pragma GCC diagnostic ignored "-Wpragmas" 19 | #pragma GCC diagnostic ignored "-Wstringop-truncation" 20 | #pragma GCC diagnostic ignored "-Wstringop-overflow" 21 | 22 | /** 23 | * Allocate the space and join two strings. 24 | */ 25 | char *mstrjoin(const char *src1, const char *src2) { 26 | auto len1 = strlen(src1); 27 | auto len2 = strlen(src2); 28 | auto len = len1 + len2 + 1; 29 | auto str = ccalloc(len, char); 30 | 31 | strncpy(str, src1, len1); 32 | strncpy(str + len1, src2, len2); 33 | str[len - 1] = '\0'; 34 | 35 | return str; 36 | } 37 | 38 | TEST_CASE(mstrjoin) { 39 | char *str = mstrjoin("asdf", "qwer"); 40 | TEST_STREQUAL(str, "asdfqwer"); 41 | free(str); 42 | 43 | str = mstrjoin("", "qwer"); 44 | TEST_STREQUAL(str, "qwer"); 45 | free(str); 46 | 47 | str = mstrjoin("asdf", ""); 48 | TEST_STREQUAL(str, "asdf"); 49 | free(str); 50 | } 51 | 52 | /** 53 | * Concatenate a string on heap with another string. 54 | */ 55 | void mstrextend(char **psrc1, const char *src2) { 56 | if (!*psrc1) { 57 | *psrc1 = strdup(src2); 58 | return; 59 | } 60 | 61 | auto len1 = strlen(*psrc1); 62 | auto len2 = strlen(src2); 63 | auto len = len1 + len2 + 1; 64 | *psrc1 = crealloc(*psrc1, len); 65 | 66 | strncpy(*psrc1 + len1, src2, len2); 67 | (*psrc1)[len - 1] = '\0'; 68 | } 69 | 70 | TEST_CASE(mstrextend) { 71 | char *str1 = NULL; 72 | mstrextend(&str1, "asdf"); 73 | TEST_STREQUAL(str1, "asdf"); 74 | 75 | mstrextend(&str1, "asd"); 76 | TEST_STREQUAL(str1, "asdfasd"); 77 | 78 | mstrextend(&str1, ""); 79 | TEST_STREQUAL(str1, "asdfasd"); 80 | free(str1); 81 | } 82 | 83 | #pragma GCC diagnostic pop 84 | 85 | /// Parse a floating point number of form (+|-)?[0-9]*(\.[0-9]*) 86 | double strtod_simple(const char *src, const char **end) { 87 | double neg = 1; 88 | if (*src == '-') { 89 | neg = -1; 90 | src++; 91 | } else if (*src == '+') { 92 | src++; 93 | } 94 | 95 | double ret = 0; 96 | while (*src >= '0' && *src <= '9') { 97 | ret = ret * 10 + (*src - '0'); 98 | src++; 99 | } 100 | 101 | if (*src == '.') { 102 | double frac = 0, mult = 0.1; 103 | src++; 104 | while (*src >= '0' && *src <= '9') { 105 | frac += mult * (*src - '0'); 106 | mult *= 0.1; 107 | src++; 108 | } 109 | ret += frac; 110 | } 111 | 112 | *end = src; 113 | return ret * neg; 114 | } 115 | 116 | TEST_CASE(strtod_simple) { 117 | const char *end; 118 | double result = strtod_simple("1.0", &end); 119 | TEST_EQUAL(result, 1); 120 | TEST_EQUAL(*end, '\0'); 121 | 122 | result = strtod_simple("-1.0", &end); 123 | TEST_EQUAL(result, -1); 124 | TEST_EQUAL(*end, '\0'); 125 | 126 | result = strtod_simple("+.5", &end); 127 | TEST_EQUAL(result, 0.5); 128 | TEST_EQUAL(*end, '\0'); 129 | } 130 | -------------------------------------------------------------------------------- /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 | 15 | /// Parse a floating point number of form (+|-)?[0-9]*(\.[0-9]*) 16 | double strtod_simple(const char *, const char **); 17 | 18 | static inline int uitostr(unsigned int n, char *buf) { 19 | int ret = 0; 20 | unsigned int tmp = n; 21 | while (tmp > 0) { 22 | tmp /= 10; 23 | ret++; 24 | } 25 | 26 | if (ret == 0) 27 | ret = 1; 28 | 29 | int pos = ret; 30 | while (pos--) { 31 | buf[pos] = (char)(n % 10 + '0'); 32 | n /= 10; 33 | } 34 | return ret; 35 | } 36 | 37 | static inline const char *skip_space_const(const char *src) { 38 | if (!src) 39 | return NULL; 40 | while (*src && isspace(*src)) 41 | src++; 42 | return src; 43 | } 44 | 45 | static inline char *skip_space_mut(char *src) { 46 | if (!src) 47 | return NULL; 48 | while (*src && isspace(*src)) 49 | src++; 50 | return src; 51 | } 52 | 53 | #define skip_space(x) \ 54 | _Generic((x), char * : skip_space_mut, const char * : skip_space_const)(x) 55 | -------------------------------------------------------------------------------- /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/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 | { 41 | n--; 42 | n |= n >> 1; 43 | n |= n >> 2; 44 | n |= n >> 4; 45 | n |= n >> 8; 46 | n |= n >> 16; 47 | n++; 48 | return n; 49 | } 50 | 51 | // vim: set noet sw=8 ts=8 : 52 | -------------------------------------------------------------------------------- /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 | /// Pending window updates 31 | enum win_update { 32 | WIN_UPDATE_MAP = 1, 33 | }; 34 | 35 | /// Transition table: 36 | /// (DESTROYED is when the win struct is destroyed and freed) 37 | /// ('o' means in all other cases) 38 | /// (Window is created in the UNMAPPED state) 39 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 40 | /// | |UNMAPPING|DESTROYING|MAPPING|FADING |UNMAPPED| MAPPED |DESTROYED| 41 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 42 | /// | UNMAPPING | o | Window |Window | - | Fading | - | - | 43 | /// | | |destroyed |mapped | |finished| | | 44 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 45 | /// | DESTROYING | - | o | - | - | - | - | Fading | 46 | /// | | | | | | | |finished | 47 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 48 | /// | MAPPING | Window | Window | o | - | - | Fading | - | 49 | /// | |unmapped |destroyed | | | |finished| | 50 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 51 | /// | FADING | Window | Window | - | o | - | Fading | - | 52 | /// | |unmapped |destroyed | | | |finished| | 53 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 54 | /// | UNMAPPED | - | - |Window | - | o | - | Window | 55 | /// | | | |mapped | | | |destroyed| 56 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 57 | /// | MAPPED | Window | Window | - |Opacity| - | o | - | 58 | /// | |unmapped |destroyed | |change | | | | 59 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 60 | typedef enum { 61 | // The window is being faded out because it's unmapped. 62 | WSTATE_UNMAPPING, 63 | // The window is being faded out because it's destroyed, 64 | WSTATE_DESTROYING, 65 | // The window is being faded in 66 | WSTATE_MAPPING, 67 | // Window opacity is not at the target level 68 | WSTATE_FADING, 69 | // The window is mapped, no fading is in progress. 70 | WSTATE_MAPPED, 71 | // The window is unmapped, no fading is in progress. 72 | WSTATE_UNMAPPED, 73 | } winstate_t; 74 | 75 | enum win_flags { 76 | // Note: *_NONE flags are mostly redudant and meant for detecting logical errors 77 | // in the code 78 | 79 | /// pixmap is out of date, will be update in win_process_flags 80 | WIN_FLAGS_PIXMAP_STALE = 1, 81 | /// window does not have pixmap bound 82 | WIN_FLAGS_PIXMAP_NONE = 2, 83 | /// there was an error trying to bind the images 84 | WIN_FLAGS_IMAGE_ERROR = 4, 85 | /// shadow is out of date, will be updated in win_process_flags 86 | WIN_FLAGS_SHADOW_STALE = 8, 87 | /// shadow has not been generated 88 | WIN_FLAGS_SHADOW_NONE = 16, 89 | }; 90 | 91 | static const int_fast16_t WIN_FLAGS_IMAGES_STALE = 92 | WIN_FLAGS_PIXMAP_STALE | WIN_FLAGS_SHADOW_STALE; 93 | 94 | #define WIN_FLAGS_IMAGES_NONE (WIN_FLAGS_PIXMAP_NONE | WIN_FLAGS_SHADOW_NONE) 95 | -------------------------------------------------------------------------------- /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 | 20 | /// Structure representing Window property value. 21 | typedef struct winprop { 22 | union { 23 | void *ptr; 24 | int8_t *p8; 25 | int16_t *p16; 26 | int32_t *p32; 27 | uint32_t *c32; // 32bit cardinal 28 | }; 29 | unsigned long nitems; 30 | xcb_atom_t type; 31 | int format; 32 | 33 | xcb_get_property_reply_t *r; 34 | } winprop_t; 35 | 36 | struct xvisual_info { 37 | /// Bit depth of the red component 38 | int red_size; 39 | /// Bit depth of the green component 40 | int green_size; 41 | /// Bit depth of the blue component 42 | int blue_size; 43 | /// Bit depth of the alpha component 44 | int alpha_size; 45 | /// The depth of X visual 46 | int visual_depth; 47 | 48 | xcb_visualid_t visual; 49 | }; 50 | 51 | #define XCB_AWAIT_VOID(func, c, ...) \ 52 | ({ \ 53 | bool __success = true; \ 54 | __auto_type __e = xcb_request_check(c, func##_checked(c, __VA_ARGS__)); \ 55 | if (__e) { \ 56 | x_print_error(__e->sequence, __e->major_code, __e->minor_code, \ 57 | __e->error_code); \ 58 | free(__e); \ 59 | __success = false; \ 60 | } \ 61 | __success; \ 62 | }) 63 | 64 | #define XCB_AWAIT(func, c, ...) \ 65 | ({ \ 66 | xcb_generic_error_t *__e = NULL; \ 67 | __auto_type __r = func##_reply(c, func(c, __VA_ARGS__), &__e); \ 68 | if (__e) { \ 69 | x_print_error(__e->sequence, __e->major_code, __e->minor_code, \ 70 | __e->error_code); \ 71 | free(__e); \ 72 | } \ 73 | __r; \ 74 | }) 75 | 76 | #define log_error_x_error(e, fmt, ...) \ 77 | LOG(ERROR, fmt " (%s)", ##__VA_ARGS__, x_strerror(e)) 78 | #define log_fatal_x_error(e, fmt, ...) \ 79 | LOG(FATAL, fmt " (%s)", ##__VA_ARGS__, x_strerror(e)) 80 | 81 | /// Wraps x_new_id. abort the program if x_new_id returns error 82 | static inline uint32_t x_new_id(xcb_connection_t *c) { 83 | auto ret = xcb_generate_id(c); 84 | if (ret == (uint32_t)-1) { 85 | log_fatal("We seems to have run of XIDs. This is either a bug in the X " 86 | "server, or a resource leakage in the compositor. Please open " 87 | "an issue about this problem. The compositor will die."); 88 | abort(); 89 | } 90 | return ret; 91 | } 92 | 93 | /** 94 | * Send a request to X server and get the reply to make sure all previous 95 | * requests are processed, and their replies received 96 | * 97 | * xcb_get_input_focus is used here because it is the same request used by 98 | * libX11 99 | */ 100 | static inline void x_sync(xcb_connection_t *c) { 101 | free(xcb_get_input_focus_reply(c, xcb_get_input_focus(c), NULL)); 102 | } 103 | 104 | /** 105 | * Get a specific attribute of a window. 106 | * 107 | * Returns a blank structure if the returned type and format does not 108 | * match the requested type and format. 109 | * 110 | * @param ps current session 111 | * @param w window 112 | * @param atom atom of attribute to fetch 113 | * @param length length to read 114 | * @param rtype atom of the requested type 115 | * @param rformat requested format 116 | * @return a winprop_t structure containing the attribute 117 | * and number of items. A blank one on failure. 118 | */ 119 | winprop_t x_get_prop_with_offset(const session_t *ps, xcb_window_t w, xcb_atom_t atom, 120 | int offset, int length, xcb_atom_t rtype, int rformat); 121 | 122 | /** 123 | * Wrapper of wid_get_prop_adv(). 124 | */ 125 | static inline winprop_t x_get_prop(const session_t *ps, xcb_window_t wid, xcb_atom_t atom, 126 | int length, xcb_atom_t rtype, int rformat) { 127 | return x_get_prop_with_offset(ps, wid, atom, 0L, length, rtype, rformat); 128 | } 129 | 130 | /// Discard all X events in queue or in flight. Should only be used when the server is 131 | /// grabbed 132 | static inline void x_discard_events(xcb_connection_t *c) { 133 | xcb_generic_event_t *e; 134 | while ((e = xcb_poll_for_event(c))) { 135 | free(e); 136 | } 137 | } 138 | 139 | /** 140 | * Get the value of a type-xcb_window_t property of a window. 141 | * 142 | * @return the value if successful, 0 otherwise 143 | */ 144 | xcb_window_t wid_get_prop_window(session_t *ps, xcb_window_t wid, xcb_atom_t aprop); 145 | 146 | /** 147 | * Get the value of a text property of a window. 148 | */ 149 | bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ***pstrlst, 150 | int *pnstr); 151 | 152 | const xcb_render_pictforminfo_t * 153 | x_get_pictform_for_visual(xcb_connection_t *, xcb_visualid_t); 154 | int x_get_visual_depth(xcb_connection_t *, xcb_visualid_t); 155 | 156 | xcb_render_picture_t 157 | x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *, 158 | const xcb_render_pictforminfo_t *pictfmt, 159 | xcb_pixmap_t pixmap, uint32_t valuemask, 160 | const xcb_render_create_picture_value_list_t *attr) 161 | attr_nonnull(1, 2); 162 | 163 | xcb_render_picture_t 164 | x_create_picture_with_visual_and_pixmap(xcb_connection_t *, xcb_visualid_t visual, 165 | xcb_pixmap_t pixmap, uint32_t valuemask, 166 | const xcb_render_create_picture_value_list_t *attr) 167 | attr_nonnull(1); 168 | 169 | xcb_render_picture_t 170 | x_create_picture_with_standard_and_pixmap(xcb_connection_t *, xcb_pict_standard_t standard, 171 | xcb_pixmap_t pixmap, uint32_t valuemask, 172 | const xcb_render_create_picture_value_list_t *attr) 173 | attr_nonnull(1); 174 | 175 | xcb_render_picture_t 176 | x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int h, 177 | xcb_pict_standard_t standard, uint32_t valuemask, 178 | const xcb_render_create_picture_value_list_t *attr) 179 | attr_nonnull(1); 180 | 181 | /** 182 | * Create an picture. 183 | */ 184 | xcb_render_picture_t 185 | x_create_picture_with_pictfmt(xcb_connection_t *, xcb_drawable_t, int w, int h, 186 | const xcb_render_pictforminfo_t *pictfmt, uint32_t valuemask, 187 | const xcb_render_create_picture_value_list_t *attr) 188 | attr_nonnull(1, 5); 189 | 190 | xcb_render_picture_t 191 | x_create_picture_with_visual(xcb_connection_t *, xcb_drawable_t, int w, int h, 192 | xcb_visualid_t visual, uint32_t valuemask, 193 | const xcb_render_create_picture_value_list_t *attr) 194 | attr_nonnull(1); 195 | 196 | /// Fetch a X region and store it in a pixman region 197 | bool x_fetch_region(xcb_connection_t *, xcb_xfixes_region_t r, region_t *res); 198 | 199 | void x_set_picture_clip_region(xcb_connection_t *, xcb_render_picture_t, int16_t clip_x_origin, 200 | int16_t clip_y_origin, const region_t *); 201 | 202 | void x_clear_picture_clip_region(xcb_connection_t *, xcb_render_picture_t pict); 203 | 204 | /** 205 | * Log a X11 error 206 | */ 207 | void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code); 208 | 209 | /* 210 | * Convert a xcb_generic_error_t to a string that describes the error 211 | * 212 | * @return a pointer to a string. this pointer shouldn NOT be freed, same buffer is used 213 | * for multiple calls to this function, 214 | */ 215 | const char *x_strerror(xcb_generic_error_t *e); 216 | 217 | xcb_pixmap_t x_create_pixmap(xcb_connection_t *, uint8_t depth, xcb_drawable_t drawable, 218 | int width, int height); 219 | 220 | bool x_validate_pixmap(xcb_connection_t *, xcb_pixmap_t pxmap); 221 | 222 | /** 223 | * Free a winprop_t. 224 | * 225 | * @param pprop pointer to the winprop_t to free. 226 | */ 227 | static inline void free_winprop(winprop_t *pprop) { 228 | // Empty the whole structure to avoid possible issues 229 | if (pprop->r) 230 | free(pprop->r); 231 | pprop->ptr = NULL; 232 | pprop->r = NULL; 233 | pprop->nitems = 0; 234 | } 235 | /// Get the back pixmap of the root window 236 | xcb_pixmap_t x_get_root_back_pixmap(session_t *ps); 237 | 238 | /// Return true if the atom refers to a property name that is used for the 239 | /// root window background pixmap 240 | bool x_is_root_back_pixmap_atom(session_t *ps, xcb_atom_t atom); 241 | 242 | bool x_fence_sync(xcb_connection_t *, xcb_sync_fence_t); 243 | 244 | struct x_convolution_kernel { 245 | int size; 246 | int capacity; 247 | xcb_render_fixed_t kernel[]; 248 | }; 249 | 250 | /** 251 | * Convert a struct conv to a X picture convolution filter, normalizing the kernel 252 | * in the process. Allow the caller to specify the element at the center of the kernel, 253 | * for compatibility with legacy code. 254 | * 255 | * @param[in] kernel the convolution kernel 256 | * @param[in] center the element to put at the center of the matrix 257 | * @param[inout] ret pointer to an array of `size`, if `size` is too small, more space 258 | * will be allocated, and `*ret` will be updated. 259 | * @param[inout] size size of the array pointed to by `ret`. 260 | */ 261 | void attr_nonnull(1, 3) x_create_convolution_kernel(const conv *kernel, double center, 262 | struct x_convolution_kernel **ret); 263 | 264 | /// Generate a search criteria for fbconfig from a X visual. 265 | /// Returns {-1, -1, -1, -1, -1, -1} on failure 266 | struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual); 267 | 268 | xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_t std); 269 | 270 | xcb_render_pictformat_t x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std); 271 | 272 | xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen); 273 | 274 | uint32_t attr_deprecated xcb_generate_id(xcb_connection_t *c); 275 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /subprojects/test.h/meson.build: -------------------------------------------------------------------------------- 1 | project('test.h', 'c') 2 | test_h_dep = declare_dependency(include_directories: include_directories('.')) 3 | -------------------------------------------------------------------------------- /subprojects/test.h/test.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | #pragma once 3 | 4 | #ifdef UNIT_TEST 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \ 12 | defined(__NetBSD__) || defined(__OpenBSD__) 13 | #define USE_SYSCTL_FOR_ARGS 1 14 | // clang-format off 15 | #include 16 | #include 17 | // clang-format on 18 | #include // getpid 19 | #endif 20 | 21 | struct test_file_metadata; 22 | 23 | struct test_failure { 24 | bool present; 25 | const char *message; 26 | const char *file; 27 | int line; 28 | }; 29 | 30 | struct test_case_metadata { 31 | void (*fn)(struct test_case_metadata *, struct test_file_metadata *); 32 | struct test_failure failure; 33 | const char *name; 34 | struct test_case_metadata *next; 35 | }; 36 | 37 | struct test_file_metadata { 38 | bool registered; 39 | const char *name; 40 | struct test_file_metadata *next; 41 | struct test_case_metadata *tests; 42 | }; 43 | 44 | struct test_file_metadata __attribute__((weak)) * test_file_head; 45 | 46 | #define SET_FAILURE(_message) \ 47 | metadata->failure = (struct test_failure) { \ 48 | .message = _message, .file = __FILE__, .line = __LINE__, .present = true, \ 49 | } 50 | 51 | #define TEST_EQUAL(a, b) \ 52 | do { \ 53 | if ((a) != (b)) { \ 54 | SET_FAILURE(#a " != " #b); \ 55 | return; \ 56 | } \ 57 | } while (0) 58 | 59 | #define TEST_TRUE(a) \ 60 | do { \ 61 | if (!(a)) { \ 62 | SET_FAILURE(#a " is not true"); \ 63 | return; \ 64 | } \ 65 | } while (0) 66 | 67 | #define TEST_STREQUAL(a, b) \ 68 | do { \ 69 | if (strcmp(a, b) != 0) { \ 70 | SET_FAILURE(#a " != " #b); \ 71 | return; \ 72 | } \ 73 | } while (0) 74 | 75 | #define TEST_CASE(_name) \ 76 | static void __test_h_##_name(struct test_case_metadata *, \ 77 | struct test_file_metadata *); \ 78 | static struct test_file_metadata __test_h_file; \ 79 | static struct test_case_metadata __test_h_meta_##_name = { \ 80 | .name = #_name, \ 81 | .fn = __test_h_##_name, \ 82 | }; \ 83 | static void __attribute__((constructor(101))) __test_h_##_name##_register(void) { \ 84 | __test_h_meta_##_name.next = __test_h_file.tests; \ 85 | __test_h_file.tests = &__test_h_meta_##_name; \ 86 | if (!__test_h_file.registered) { \ 87 | __test_h_file.name = __FILE__; \ 88 | __test_h_file.next = test_file_head; \ 89 | test_file_head = &__test_h_file; \ 90 | __test_h_file.registered = true; \ 91 | } \ 92 | } \ 93 | static void __test_h_##_name( \ 94 | struct test_case_metadata *metadata __attribute__((unused)), \ 95 | struct test_file_metadata *file_metadata __attribute__((unused))) 96 | 97 | extern void __attribute__((weak)) (*test_h_unittest_setup)(void); 98 | /// Run defined tests, return true if all tests succeeds 99 | /// @param[out] tests_run if not NULL, set to whether tests were run 100 | static inline void __attribute__((constructor(102))) run_tests(void) { 101 | bool should_run = false; 102 | #ifdef USE_SYSCTL_FOR_ARGS 103 | int mib[] = { 104 | CTL_KERN, 105 | #if defined(__NetBSD__) || defined(__OpenBSD__) 106 | KERN_PROC_ARGS, 107 | getpid(), 108 | KERN_PROC_ARGV, 109 | #else 110 | KERN_PROC, 111 | KERN_PROC_ARGS, 112 | getpid(), 113 | #endif 114 | }; 115 | char *arg = NULL; 116 | size_t arglen; 117 | sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &arglen, NULL, 0); 118 | arg = malloc(arglen); 119 | sysctl(mib, sizeof(mib) / sizeof(mib[0]), arg, &arglen, NULL, 0); 120 | #else 121 | FILE *cmdlinef = fopen("/proc/self/cmdline", "r"); 122 | char *arg = NULL; 123 | int arglen; 124 | fscanf(cmdlinef, "%ms%n", &arg, &arglen); 125 | fclose(cmdlinef); 126 | #endif 127 | for (char *pos = arg; pos < arg + arglen; pos += strlen(pos) + 1) { 128 | if (strcmp(pos, "--unittest") == 0) { 129 | should_run = true; 130 | break; 131 | } 132 | } 133 | free(arg); 134 | 135 | if (!should_run) { 136 | return; 137 | } 138 | 139 | if (&test_h_unittest_setup) { 140 | test_h_unittest_setup(); 141 | } 142 | 143 | struct test_file_metadata *i = test_file_head; 144 | int failed = 0, success = 0; 145 | while (i) { 146 | fprintf(stderr, "Running tests from %s:\n", i->name); 147 | struct test_case_metadata *j = i->tests; 148 | while (j) { 149 | fprintf(stderr, "\t%s ... ", j->name); 150 | j->failure.present = false; 151 | j->fn(j, i); 152 | if (j->failure.present) { 153 | fprintf(stderr, "failed (%s at %s:%d)\n", j->failure.message, 154 | j->failure.file, j->failure.line); 155 | failed++; 156 | } else { 157 | fprintf(stderr, "passed\n"); 158 | success++; 159 | } 160 | j = j->next; 161 | } 162 | fprintf(stderr, "\n"); 163 | i = i->next; 164 | } 165 | int total = failed + success; 166 | fprintf(stderr, "Test results: passed %d/%d, failed %d/%d\n", success, total, 167 | failed, total); 168 | exit(failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE); 169 | } 170 | 171 | #else 172 | 173 | #include 174 | 175 | #define TEST_CASE(name) static void __attribute__((unused)) __test_h_##name(void) 176 | 177 | #define TEST_EQUAL(a, b) \ 178 | (void)(a); \ 179 | (void)(b) 180 | #define TEST_TRUE(a) (void)(a) 181 | #define TEST_STREQUAL(a, b) \ 182 | (void)(a); \ 183 | (void)(b) 184 | 185 | #endif 186 | -------------------------------------------------------------------------------- /tests/configs/empty.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arian8j2/picom-jonaburg-fix/31d25da22b44f37cbb9be49fe5c239ef8d00df12/tests/configs/empty.conf -------------------------------------------------------------------------------- /tests/configs/issue239.conf: -------------------------------------------------------------------------------- 1 | fading = true; 2 | fade-in-step = 1; 3 | fade-out-step = 0.01; 4 | shadow = true; 5 | shadow-exclude = [ 6 | "name = 'NoShadow'" 7 | ] 8 | -------------------------------------------------------------------------------- /tests/configs/issue239_2.conf: -------------------------------------------------------------------------------- 1 | fading = true; 2 | fade-in-step = 1; 3 | fade-out-step = 0.01; 4 | shadow = true; 5 | shadow-exclude = [ 6 | "name = 'NoShadow'" 7 | ] 8 | unredir-if-possible = true; 9 | -------------------------------------------------------------------------------- /tests/configs/issue239_3.conf: -------------------------------------------------------------------------------- 1 | shadow = true; 2 | shadow-exclude = [ 3 | "name = 'NoShadow'" 4 | ] 5 | -------------------------------------------------------------------------------- /tests/run_one_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -xe 3 | if [ -z $DISPLAY ]; then 4 | exec xvfb-run -s "+extension composite" -a $0 $1 $2 $3 5 | fi 6 | 7 | echo "Running test $2" 8 | 9 | # TODO keep the log file, and parse it to see if test is successful 10 | ($1 --experimental-backends --backend dummy --log-level=debug --log-file=$PWD/log --config=$2) & 11 | main_pid=$! 12 | $3 13 | 14 | kill -INT $main_pid || true 15 | cat log 16 | rm log 17 | wait $main_pid 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | exe=$(realpath $1) 4 | cd $(dirname $0) 5 | 6 | ./run_one_test.sh $exe configs/empty.conf testcases/basic.py 7 | ./run_one_test.sh $exe configs/issue239.conf testcases/issue239.py 8 | ./run_one_test.sh $exe configs/issue239_2.conf testcases/issue239_2.py 9 | ./run_one_test.sh $exe configs/issue239_3.conf testcases/issue239_3.py 10 | ./run_one_test.sh $exe configs/issue239_3.conf testcases/issue239_3_norefresh.py 11 | -------------------------------------------------------------------------------- /tests/testcases/basic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import xcffib.xproto as xproto 4 | import xcffib 5 | 6 | conn = xcffib.connect() 7 | setup = conn.get_setup() 8 | root = setup.roots[0].root 9 | visual = setup.roots[0].root_visual 10 | depth = setup.roots[0].root_depth 11 | 12 | wid = conn.generate_id() 13 | conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() 14 | conn.core.MapWindowChecked(wid).check() 15 | conn.core.UnmapWindowChecked(wid).check() 16 | conn.core.DestroyWindowChecked(wid).check() 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/testcases/common.py: -------------------------------------------------------------------------------- 1 | import xcffib.xproto as xproto 2 | import xcffib.randr as randr 3 | import time 4 | import random 5 | import string 6 | def set_window_name(conn, wid, name): 7 | prop_name = "_NET_WM_NAME" 8 | prop_name = conn.core.InternAtom(True, len(prop_name), prop_name).reply().atom 9 | str_type = "STRING" 10 | str_type = conn.core.InternAtom(True, len(str_type), str_type).reply().atom 11 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, prop_name, str_type, 8, len(name), name).check() 12 | 13 | def find_picom_window(conn): 14 | prop_name = "WM_NAME" 15 | prop_name = conn.core.InternAtom(True, len(prop_name), prop_name).reply().atom 16 | setup = conn.get_setup() 17 | root = setup.roots[0].root 18 | windows = conn.core.QueryTree(root).reply() 19 | 20 | ext = xproto.xprotoExtension(conn) 21 | for w in windows.children: 22 | name = ext.GetProperty(False, w, prop_name, xproto.GetPropertyType.Any, 0, (2 ** 32) - 1).reply() 23 | if name.value.buf() == b"picom": 24 | return w 25 | 26 | def trigger_root_configure(conn): 27 | setup = conn.get_setup() 28 | root = setup.roots[0].root 29 | # Xorg sends root ConfigureNotify when we add a new mode to an output 30 | rr = conn(randr.key) 31 | name = ''.join([random.choice(string.ascii_letters + string.digits) for n in range(0, 32)]) 32 | mode_info = randr.ModeInfo.synthetic(id = 0, width = 1000, height = 1000, dot_clock = 0, 33 | hsync_start = 0, hsync_end = 0, htotal = 0, hskew = 0, vsync_start = 0, vsync_end = 0, 34 | vtotal = 0, name_len = len(name), mode_flags = 0) 35 | 36 | reply = rr.CreateMode(root, mode_info, len(name), name).reply() 37 | mode = reply.mode 38 | reply = rr.GetScreenResourcesCurrent(root).reply() 39 | # our xvfb is setup to only have 1 output 40 | output = reply.outputs[0] 41 | rr.AddOutputModeChecked(output, mode).check() 42 | rr.SetCrtcConfig(reply.crtcs[0], reply.timestamp, reply.config_timestamp, 0, 0, mode, randr.Rotation.Rotate_0, 1, [output]).reply() 43 | 44 | -------------------------------------------------------------------------------- /tests/testcases/issue239.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import xcffib.xproto as xproto 4 | import xcffib 5 | import time 6 | from common import set_window_name 7 | 8 | conn = xcffib.connect() 9 | setup = conn.get_setup() 10 | root = setup.roots[0].root 11 | visual = setup.roots[0].root_visual 12 | depth = setup.roots[0].root_depth 13 | 14 | # issue 239 is caused by a window gaining a shadow during its fade-out transition 15 | wid = conn.generate_id() 16 | print("Window id is ", hex(wid)) 17 | 18 | # Create a window 19 | conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() 20 | 21 | # Set Window name so it doesn't get a shadow 22 | set_window_name(conn, wid, "NoShadow") 23 | 24 | # Map the window 25 | print("mapping") 26 | conn.core.MapWindowChecked(wid).check() 27 | 28 | time.sleep(0.5) 29 | 30 | # Set the Window name so it gets a shadow 31 | print("set new name") 32 | set_window_name(conn, wid, "YesShadow") 33 | 34 | # Unmap the window 35 | conn.core.UnmapWindowChecked(wid).check() 36 | 37 | time.sleep(0.5) 38 | 39 | # Destroy the window 40 | conn.core.DestroyWindowChecked(wid).check() 41 | -------------------------------------------------------------------------------- /tests/testcases/issue239_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import xcffib.xproto as xproto 4 | import xcffib 5 | import time 6 | 7 | conn = xcffib.connect() 8 | setup = conn.get_setup() 9 | root = setup.roots[0].root 10 | visual = setup.roots[0].root_visual 11 | depth = setup.roots[0].root_depth 12 | 13 | # issue 239 is caused by a window gaining a shadow during its fade-out transition 14 | wid = conn.generate_id() 15 | print("Window ids are ", hex(wid)) 16 | 17 | # Create a window 18 | mask = xproto.CW.BackPixel 19 | value = [ setup.roots[0].white_pixel ] 20 | conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, mask, value).check() 21 | 22 | name = "_NET_WM_STATE" 23 | name_atom = conn.core.InternAtom(False, len(name), name).reply().atom 24 | atom = "ATOM" 25 | atom_atom = conn.core.InternAtom(False, len(atom), atom).reply().atom 26 | fs = "_NET_WM_STATE_FULLSCREEN" 27 | fs_atom = conn.core.InternAtom(False, len(fs), fs).reply().atom 28 | 29 | 30 | # Map the window, causing screen to be redirected 31 | conn.core.MapWindowChecked(wid).check() 32 | 33 | time.sleep(0.5) 34 | 35 | # Set fullscreen property, causing screen to be unredirected 36 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, name_atom, atom_atom, 32, 1, [fs_atom]).check() 37 | 38 | time.sleep(0.5) 39 | 40 | # Clear fullscreen property, causing screen to be redirected 41 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, name_atom, atom_atom, 32, 0, []).check() 42 | 43 | # Do a round trip to X server so the compositor has a chance to start the rerun of _draw_callback 44 | conn.core.GetInputFocus().reply() 45 | 46 | # Unmap the window, triggers the bug 47 | conn.core.UnmapWindowChecked(wid).check() 48 | 49 | time.sleep(0.5) 50 | 51 | # Destroy the window 52 | conn.core.DestroyWindowChecked(wid).check() 53 | -------------------------------------------------------------------------------- /tests/testcases/issue239_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import xcffib.xproto as xproto 4 | import xcffib 5 | import time 6 | from common import set_window_name 7 | 8 | conn = xcffib.connect() 9 | setup = conn.get_setup() 10 | root = setup.roots[0].root 11 | visual = setup.roots[0].root_visual 12 | depth = setup.roots[0].root_depth 13 | 14 | # issue 239 is caused by a window gaining a shadow during its fade-out transition 15 | wid = conn.generate_id() 16 | print("Window id is ", hex(wid)) 17 | 18 | # Create a window 19 | conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() 20 | 21 | # Set Window name so it gets a shadow 22 | set_window_name(conn, wid, "YesShadow") 23 | 24 | # Map the window 25 | print("mapping") 26 | conn.core.MapWindowChecked(wid).check() 27 | 28 | time.sleep(0.5) 29 | 30 | print("set new name") 31 | set_window_name(conn, wid, "NoShadow") 32 | 33 | time.sleep(0.5) 34 | 35 | # Set the Window name so it gets a shadow 36 | print("set new name") 37 | set_window_name(conn, wid, "YesShadow") 38 | 39 | time.sleep(0.5) 40 | 41 | # Unmap the window 42 | conn.core.UnmapWindowChecked(wid).check() 43 | 44 | time.sleep(0.5) 45 | 46 | # Destroy the window 47 | conn.core.DestroyWindowChecked(wid).check() 48 | -------------------------------------------------------------------------------- /tests/testcases/issue239_3_norefresh.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import xcffib.xproto as xproto 4 | import xcffib 5 | import time 6 | from common import set_window_name 7 | 8 | conn = xcffib.connect() 9 | setup = conn.get_setup() 10 | root = setup.roots[0].root 11 | visual = setup.roots[0].root_visual 12 | depth = setup.roots[0].root_depth 13 | 14 | # issue 239 is caused by a window gaining a shadow during its fade-out transition 15 | wid = conn.generate_id() 16 | print("Window id is ", hex(wid)) 17 | 18 | # Create a window 19 | conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() 20 | 21 | # Set Window name so it gets a shadow 22 | set_window_name(conn, wid, "YesShadow") 23 | 24 | # Map the window 25 | print("mapping") 26 | conn.core.MapWindowChecked(wid).check() 27 | 28 | time.sleep(0.5) 29 | 30 | print("set new name") 31 | set_window_name(conn, wid, "NoShadow") 32 | 33 | # Set the Window name so it gets a shadow 34 | print("set new name") 35 | set_window_name(conn, wid, "YesShadow") 36 | 37 | time.sleep(0.5) 38 | 39 | # Unmap the window 40 | conn.core.UnmapWindowChecked(wid).check() 41 | 42 | time.sleep(0.5) 43 | 44 | # Destroy the window 45 | conn.core.DestroyWindowChecked(wid).check() 46 | --------------------------------------------------------------------------------