├── .builds ├── freebsd.yml └── openbsd.yml ├── .circleci └── config.yml ├── .clang-format ├── .clang-tidy ├── .editorconfig ├── .github ├── issue_template.md ├── pull_request_template.md └── workflows │ ├── codeql-analysis.yml │ ├── coding-style-pr.yml │ └── coding-style.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTORS ├── COPYING ├── Doxyfile ├── History.md ├── LICENSE.spdx ├── LICENSES ├── MIT └── MPL-2.0 ├── README.md ├── bin └── picom-trans ├── compton-default-fshader-win.glsl ├── compton-fake-transparency-fshader-win.glsl ├── compton.desktop ├── dbus-examples ├── cdbus-driver.sh └── inverter.sh ├── desc.txt ├── flake.lock ├── flake.nix ├── 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 │ │ ├── blur.c │ │ ├── egl.c │ │ ├── egl.h │ │ ├── gl_common.c │ │ ├── gl_common.h │ │ ├── glx.c │ │ ├── glx.h │ │ └── shaders.c │ ├── meson.build │ └── xrender │ │ └── xrender.c ├── c2.c ├── c2.h ├── cache.c ├── cache.h ├── common.h ├── 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 ├── statistics.c ├── statistics.h ├── string_utils.c ├── string_utils.h ├── types.h ├── uthash_extra.h ├── utils.c ├── utils.h ├── vblank.c ├── vblank.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 ├── clear_shadow_unredirected.conf ├── empty.conf ├── issue239.conf ├── issue239_2.conf ├── issue239_3.conf ├── issue314.conf ├── issue357.conf ├── issue394.conf ├── issue465.conf ├── parsing_test.conf └── shader.frag ├── run_one_test.sh ├── run_tests.sh └── testcases ├── basic.py ├── clear_shadow_unredirected.py ├── common.py ├── issue239.py ├── issue239_2.py ├── issue239_3.py ├── issue239_3_norefresh.py ├── issue299.py ├── issue314.py ├── issue314_2.py ├── issue314_3.py ├── issue357.py ├── issue394.py ├── issue465.py ├── issue525.py └── redirect_when_unmapped_window_has_shadow.py /.builds/freebsd.yml: -------------------------------------------------------------------------------- 1 | image: freebsd/latest 2 | packages: 3 | - libev 4 | - libXext 5 | - libxcb 6 | - meson 7 | - pkgconf 8 | - cmake 9 | - xcb-util-renderutil 10 | - xcb-util-image 11 | - pixman 12 | - uthash 13 | - libconfig 14 | - libglvnd 15 | - libepoxy 16 | - dbus 17 | - pcre 18 | sources: 19 | - https://github.com/yshui/picom 20 | tasks: 21 | - setup: | 22 | cd picom 23 | CPPFLAGS="-I/usr/local/include" meson setup -Dunittest=true --werror build 24 | - build: | 25 | cd picom 26 | ninja -C build 27 | - unittest: | 28 | cd picom 29 | ninja -C build test 30 | -------------------------------------------------------------------------------- /.builds/openbsd.yml: -------------------------------------------------------------------------------- 1 | image: openbsd/latest 2 | packages: 3 | - libev 4 | - xcb 5 | - meson 6 | - pkgconf 7 | - cmake 8 | - uthash 9 | - libconfig 10 | - dbus 11 | - pcre2 12 | sources: 13 | - https://github.com/yshui/picom 14 | tasks: 15 | - setup: | 16 | cd picom 17 | CPPFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib" meson setup -Dunittest=true --werror build 18 | - build: | 19 | cd picom 20 | ninja -C build 21 | - unittest: | 22 | cd picom 23 | ninja -C build test 24 | -------------------------------------------------------------------------------- /.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 setup << 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: -Dwith_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 tests/configs/parsing_test.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 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: > 2 | readability-*, 3 | performance-*, 4 | modernize-*, 5 | google-readability-todo, 6 | cert-err34-c, 7 | cert-flp30-c, 8 | bugprone-*, 9 | misc-misplaced-const, 10 | misc-redundant-expression, 11 | misc-static-assert, 12 | -clang-analyzer-*, 13 | -readability-isolate-declaration, 14 | -readability-magic-numbers, 15 | -readability-identifier-length, 16 | -bugprone-easily-swappable-parameters 17 | AnalyzeTemporaryDtors: false 18 | FormatStyle: file 19 | CheckOptions: 20 | - key: readability-magic-numbers.IgnoredIntegerValues 21 | value: 4;8;16;24;32;1;2;3;4096;65536; 22 | - key: readability-magic-numbers.IgnoredFloatingPointValues 23 | value: 255.0;1.0; 24 | - key: readability-function-cognitive-complexity.IgnoreMacros 25 | value: true 26 | - key: readability-function-cognitive-complexity.Threshold 27 | value: 50 28 | - key: readability-function-cognitive-complexity.DescribeBasicIncrements 29 | value: true 30 | - key: bugprone-signed-char-misuse.CharTypdefsToIgnore 31 | value: int8_t 32 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*.{c,h}] 3 | indent_style = tab 4 | indent_size = 8 5 | max_line_length = 90 6 | [*.nix] 7 | indent_style = space 8 | indent_size = 2 9 | -------------------------------------------------------------------------------- /.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 | Diagnostics 21 | 22 | 23 |
24 | 25 | ### Configuration: 26 |
27 | Configuration file 28 | 29 | ``` 30 | // Paste your configuration here 31 | ``` 32 |
33 | 34 | ### Steps of reproduction 35 | 39 | 40 | 1. 41 | 2. 42 | 43 | ### Expected behavior 44 | 45 | ### Current Behavior 46 | 47 | ### Stack trace 48 | 53 | 54 | 55 | 56 | ### OpenGL trace 57 | 61 | 62 | ### Other details 63 | 64 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [next] 6 | pull_request: 7 | branches: [next] 8 | 9 | jobs: 10 | analyze: 11 | name: Analyze 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | language: ['cpp', 'python'] 18 | 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | 23 | # Initializes the CodeQL tools for scanning. 24 | - name: Initialize CodeQL 25 | uses: github/codeql-action/init@v3 26 | with: 27 | languages: ${{ matrix.language }} 28 | 29 | # Install dependencies 30 | - run: sudo apt update && sudo apt install libconfig-dev libdbus-1-dev libegl-dev libev-dev libgl-dev libpcre2-dev libpixman-1-dev libx11-xcb-dev libxcb1-dev libxcb-composite0-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-glx0-dev libxcb-image0-dev libxcb-present-dev libxcb-randr0-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev libxcb-util-dev libxcb-xfixes0-dev libxext-dev meson ninja-build uthash-dev 31 | if: ${{ matrix.language == 'cpp' }} 32 | 33 | # Autobuild 34 | - name: Autobuild 35 | uses: github/codeql-action/autobuild@v3 36 | 37 | - name: Perform CodeQL Analysis 38 | uses: github/codeql-action/analyze@v3 39 | -------------------------------------------------------------------------------- /.github/workflows/coding-style-pr.yml: -------------------------------------------------------------------------------- 1 | name: coding-style 2 | on: pull_request 3 | 4 | jobs: 5 | check: 6 | runs-on: ubuntu-latest 7 | 8 | steps: 9 | - uses: actions/checkout@v4 10 | - run: git fetch --depth=1 origin ${{ github.event.pull_request.base.sha }} 11 | - uses: yshui/git-clang-format-lint@v1.15 12 | with: 13 | base: ${{ github.event.pull_request.base.sha }} 14 | -------------------------------------------------------------------------------- /.github/workflows/coding-style.yml: -------------------------------------------------------------------------------- 1 | name: coding-style 2 | on: push 3 | 4 | jobs: 5 | check: 6 | runs-on: ubuntu-latest 7 | 8 | steps: 9 | - uses: actions/checkout@v4 10 | with: 11 | fetch-depth: 2 12 | - uses: yshui/git-clang-format-lint@v1.15 13 | with: 14 | base: ${{ github.event.ref }}~1 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build files 2 | .deps 3 | .direnv 4 | aclocal.m4 5 | autom4te.cache 6 | config.log 7 | config.status 8 | configure 9 | depcomp 10 | install-sh 11 | missing 12 | stamp-h1 13 | compton 14 | *.o 15 | *.d 16 | build/ 17 | compile_commands.json 18 | build.ninja 19 | make.sh 20 | 21 | # language servers 22 | .ccls-cache 23 | .clangd 24 | .cache 25 | 26 | # CMake files 27 | compton-*.deb 28 | compton-*.rpm 29 | compton-*.tar.bz2 30 | release/ 31 | _CPack_Packages/ 32 | CMakeCache.txt 33 | CMakeFiles/ 34 | cmake_install.cmake 35 | CPackSourceConfig.cmake 36 | install_manifest.txt 37 | 38 | 39 | # Vim files 40 | .sw[a-z] 41 | .*.sw[a-z] 42 | *~ 43 | 44 | # Misc files 45 | core.* 46 | .gdb_history 47 | oprofile_data/ 48 | compton.plist 49 | callgrind.out.* 50 | man/*.html 51 | man/*.1 52 | doxygen/ 53 | .clang_complete 54 | .ycm_extra_conf.py 55 | .ycm_extra_conf.pyc 56 | /src/backtrace-symbols.[ch] 57 | /compton*.trace 58 | *.orig 59 | /tests/log 60 | /tests/testcases/__pycache__/ 61 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | ## New features 4 | 5 | * Allow `corner-radius-rules` to override `corner-radius = 0`. Previously setting corner radius to 0 globally disables rounded corners. (#1170) 6 | 7 | ## Notable changes 8 | 9 | * Marginally improve performance when resizing/opening/closing windows. (#1190) 10 | 11 | # v11.2 (2024-Feb-13) 12 | 13 | ## Build changes 14 | 15 | * `picom` now depends on `libepoxy` for OpenGL symbol management. 16 | 17 | ## Bug fixes 18 | 19 | * Workaround a NVIDIA problem that causes high CPU usage after suspend/resume (#1172, #1168) 20 | * Fix building on OpenBSD (#1189, #1188) 21 | * Fix occasional freezes (#1040, #1145, #1166) 22 | * Fix `corner-radius-rules` not applying sometimes (#1177) 23 | * Fix window shader not having an effect when frame opacity is enabled (#1174) 24 | * Fix binding root pixmap in case of depth mismatch (#984) 25 | 26 | # v11.1 (2024-Jan-28) 27 | 28 | ## Bug fixes 29 | 30 | * Fix missing fading on window close for some window managers. (#704) 31 | 32 | # v11 (2024-Jan-20) 33 | 34 | ## Build changes 35 | 36 | * Due to some caveats discovered related to setting the `CAP_SYS_NICE` capability, it is now recommended to **NOT** set this capability for picom. 37 | 38 | ## Deprecations 39 | 40 | * Uses of `--sw-opti`, and `--respect-prop-shadow` are now hard errors. 41 | * `-F` has been removed completely. It was deprecated before the picom fork. 42 | 43 | # v11-rc1 (2024-Jan-14) 44 | 45 | ## Notable features 46 | 47 | * picom now uses dithering to prevent banding. Banding is most notable when using a strong background blur. (#952) 48 | * Frame pacing. picom uses present feedback information to schedule new frames when it makes sense to do so. This improves latency, and replaces the `glFlush` and `GL_MaxFramesAllowed=1` hacks we used to do for NVIDIA. (#968 #1156) 49 | * Some missing features have been implemented for the EGL backend (#1004 #1007) 50 | 51 | ## Bug fixes 52 | 53 | * Many memory/resource leak fixes thanks to @absolutelynothelix . (#977 #978 #979 #980 #982 #985 #992 #1009 #1022) 54 | * Fix tiling of wallpaper. (#1002) 55 | * Fix some blur artifacts (#1095) 56 | * Fix shadow color for transparent shadows (#1124) 57 | * Don't spam logs when another compositor is running (#1104) 58 | * Fix rounded corners showing as black with the xrender backend (#1003) 59 | * Fix blur with rounded windows (#950) 60 | 61 | ## Build changes 62 | 63 | * Dependency `pcre` has been replaced by `pcre2`. 64 | * New dependency `xcb-util`. 65 | * `xinerama` is no longer used. 66 | * `picom` now tries to give itself a real-time scheduling priority. ~~Please consider giving `picom` the `CAP_SYS_NICE` capability when packaging it.~~ 67 | 68 | ## Deprecations 69 | 70 | * The `kawase` blur method is removed. Note this is just an alias to the `dual_kawase` method, which is still available. (#1102) 71 | 72 | # Earlier versions 73 | 74 | Please see the GitHub releases page. 75 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Sorted in alphabetical order. Feel free to open an issue or create a 2 | pull request if you want to change or remove your mention. 3 | 4 | Adam Jackson 5 | adelin-b 6 | Alexander Kapshuna 7 | Antonin Décimo 8 | Antonio Vivace 9 | Avi ד 10 | Ben Friesen 11 | Bernd Busse 12 | Bert Gijsbers 13 | bhagwan 14 | Bodhi 15 | Brottweiler 16 | Carl Worth 17 | Christopher Jeffrey 18 | Corax26 19 | Dan Elkouby 20 | Dana Jansens 21 | Daniel Kwan 22 | Dave Airlie 23 | David Schlachter 24 | dolio 25 | Duncan 26 | Dylan Araps 27 | Einar Lielmanis 28 | Eric Anholt 29 | Evgeniy Baskov 30 | Greg Flynn 31 | h7x4 32 | Harish Rajagopal 33 | hasufell 34 | i-c-u-p 35 | Ignacio Taranto 36 | Istvan Petres 37 | Ivan Malison 38 | Jake 39 | James Cloos 40 | Jamey Sharp 41 | Jan Beich 42 | Jarrad 43 | Javeed Shaikh 44 | Jerónimo Navarro 45 | jialeens 46 | Johnny Pribyl 47 | Jose Maldonado aka Yukiteru 48 | Keith Packard 49 | Kevin Kelley 50 | ktprograms 51 | Kurenshe Nurdaulet 52 | Lukas Schmelzeisen 53 | Mark Tiefenbruck 54 | Matthew Allum 55 | Maxim Solovyov 56 | Michael Reed 57 | Michele Lambertucci 58 | mæp 59 | Namkhai Bourquin 60 | Nate Hart 61 | nia 62 | Nikolay Borodin 63 | notfoss 64 | Omar Polo 65 | oofsauce 66 | orbea 67 | Paradigm0001 68 | Patrick Collins 69 | Peter Mattern 70 | Phil Blundell 71 | Que Quotion 72 | Rafael Kitover 73 | Reith 74 | Richard Grenville 75 | Rytis Karpuska 76 | Samuel Hand 77 | Scott Leggett 78 | scrouthtv 79 | Sebastien Waegeneire 80 | Stefan Radziuk 81 | Subhaditya Nath 82 | Tasos Sahanidis 83 | Thiago Kenji Okada 84 | Tilman Sauerbeck 85 | Tim Siegel 86 | Tim van Dalen 87 | tokyoneon78 88 | Tom Dörr 89 | Tomas Janousek 90 | Toni Jarjour 91 | Tuomas Kinnunen 92 | Uli Schlachter 93 | Walter Lapchynski 94 | Will Dietz 95 | XeCycle 96 | Yuxuan Shui 97 | zilrich 98 | ಠ_ಠ 99 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | # Picom History 2 | 3 | Picom was forked in 2016 from the original Compton because it seemed to have become unmaintained. 4 | 5 | The battle plan of the fork was 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. 6 | 7 | And also to try to fix bugs. 8 | 9 | ## Rename 10 | 11 | In 2019 the project name was changed from Compton to picom (git revision 8ddbeb and following). 12 | 13 | ### Rationale 14 | 15 | 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. 16 | 17 | 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. 18 | 19 | ### The name 20 | 21 | The criteria for a good name were 22 | 23 | 0. Being short, so it's easy to remember. 24 | 1. Pronounceability, again, helps memorability 25 | 2. Searchability, so when people search the name, it's easy for them to find this repository. 26 | 27 | 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. 28 | 29 | 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. 30 | 31 | 32 | # Compton 33 | 34 | This is a copy of the README of the [original Compton project](https://github.com/chjj/compton/). 35 | 36 | [![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) 37 | 38 | __Compton__ is a compositor for X, and a fork of __xcompmgr-dana__. 39 | 40 | I was frustrated by the low amount of standalone lightweight compositors. 41 | Compton was forked from Dana Jansens' fork of xcompmgr and refactored. I fixed 42 | whatever bug I found, and added features I wanted. Things seem stable, but don't 43 | quote me on it. I will most likely be actively working on this until I get the 44 | features I want. This is also a learning experience for me. That is, I'm 45 | partially doing this out of a desire to learn Xlib. 46 | 47 | ## Changes from xcompmgr: 48 | 49 | * OpenGL backend (`--backend glx`), in addition to the old X Render backend. 50 | * Inactive window transparency (`-i`) / dimming (`--inactive-dim`). 51 | * Titlebar/frame transparency (`-e`). 52 | * Menu transparency (`-m`, thanks to Dana). 53 | * shadows are now enabled for argb windows, e.g. terminals with transparency 54 | * removed serverside shadows (and simple compositing) to clean the code, 55 | the only option that remains is clientside shadows 56 | * configuration files (see the man page for more details) 57 | * colored shadows (`--shadow-[red/green/blue]`) 58 | * a new fade system 59 | * VSync support (not always working) 60 | * Blur of background of transparent windows, window color inversion (bad in performance) 61 | * Some more options... 62 | 63 | ## Fixes from the original xcompmgr: 64 | 65 | * fixed a segfault when opening certain window types 66 | * fixed a memory leak caused by not freeing up shadows (from the freedesktop 67 | repo) 68 | * fixed the conflict with chromium and similar windows 69 | * [many more](https://github.com/chjj/compton/issues) 70 | 71 | ## Building 72 | 73 | ### Dependencies: 74 | 75 | __B__ for build-time 76 | 77 | __R__ for runtime 78 | 79 | * libx11 (B,R) 80 | * libxcomposite (B,R) 81 | * libxdamage (B,R) 82 | * libxfixes (B,R) 83 | * libXext (B,R) 84 | * libxrender (B,R) 85 | * libXrandr (B,R) 86 | * libXinerama (B,R) (Can be disabled with `NO_XINERAMA` at compile time) 87 | * pkg-config (B) 88 | * make (B) 89 | * xproto / x11proto (B) 90 | * sh (R) 91 | * xprop,xwininfo / x11-utils (R) 92 | * libpcre (B,R) (Can be disabled with `NO_REGEX_PCRE` at compile time) 93 | * libconfig (B,R) (Can be disabled with `NO_LIBCONFIG` at compile time) 94 | * libdrm (B) (Can be disabled with `NO_VSYNC_DRM` at compile time) 95 | * libGL (B,R) (Can be disabled with `NO_VSYNC_OPENGL` at compile time) 96 | * libdbus (B,R) (Can be disabled with `NO_DBUS` at compile time) 97 | * asciidoc (B) (and docbook-xml-dtd-4.5, libxml-utils, libxslt, xsltproc, xmlto, etc. if your distro doesn't pull them in) 98 | 99 | ### How to build 100 | 101 | To build, make sure you have the dependencies above: 102 | 103 | ```bash 104 | # Make the main program 105 | $ make 106 | # Make the man pages 107 | $ make docs 108 | # Install 109 | $ make install 110 | ``` 111 | 112 | (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.) 113 | 114 | ## Known issues 115 | 116 | * Our [FAQ](https://github.com/chjj/compton/wiki/faq) covers some known issues. 117 | 118 | * 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. 119 | 120 | * 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. 121 | 122 | * compton may not track focus correctly in all situations. The focus tracking code is experimental. `--use-ewmh-active-win` might be helpful. 123 | 124 | * The performance of blur under X Render backend might be pretty bad. OpenGL backend could be faster. 125 | 126 | * With `--blur-background` you may sometimes see weird lines around damaged area. `--resize-damage YOUR_BLUR_RADIUS` might be helpful in the case. 127 | 128 | ## Usage 129 | 130 | Please refer to the Asciidoc man pages (`man/compton.1.asciidoc` & `man/compton-trans.1.asciidoc`) for more details and examples. 131 | 132 | Note a sample configuration file `compton.sample.conf` is included in the repository. 133 | 134 | ## Support 135 | 136 | * Bug reports and feature requests should go to the "Issues" section above. 137 | 138 | * Our (semi?) official IRC channel is #compton on FreeNode. 139 | 140 | * 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). 141 | 142 | ## License 143 | 144 | Although compton has kind of taken on a life of its own, it was originally 145 | an xcompmgr fork. xcompmgr has gotten around. As far as I can tell, the lineage 146 | for this particular tree is something like: 147 | 148 | * Keith Packard (original author) 149 | * Matthew Hawn 150 | * ... 151 | * Dana Jansens 152 | * chjj and richardgv 153 | 154 | Not counting the tens of people who forked it in between. 155 | 156 | Compton is distributed under MIT license, as far as I (richardgv) know. See LICENSE for more info. 157 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | picom 2 | ===== 3 | 4 | [![circleci](https://circleci.com/gh/yshui/picom.svg?style=shield)](https://circleci.com/gh/yshui/picom) 5 | [![codecov](https://codecov.io/gh/yshui/picom/branch/next/graph/badge.svg?token=NRSegi0Gze)](https://codecov.io/gh/yshui/picom) 6 | [![chat on discord](https://img.shields.io/discord/1106224720833159198?logo=discord)](https://discord.gg/SY5JJzPgME) 7 | 8 | __picom__ is a compositor for X, and a [fork of Compton](History.md). 9 | 10 | **This is a development branch, bugs to be expected** 11 | 12 | You can leave your feedback or thoughts in the [discussion tab](https://github.com/yshui/picom/discussions), or chat with other users on [discord](https://discord.gg/SY5JJzPgME)! 13 | 14 | ## Change Log 15 | 16 | See [Releases](https://github.com/yshui/picom/releases) 17 | 18 | ## Build 19 | 20 | ### Dependencies 21 | 22 | Assuming you already have all the usual building tools installed (e.g. gcc, python, meson, ninja, etc.), you still need: 23 | 24 | * libx11 25 | * libx11-xcb 26 | * libXext 27 | * xproto 28 | * xcb 29 | * xcb-util 30 | * xcb-damage 31 | * xcb-dpms 32 | * xcb-xfixes 33 | * xcb-shape 34 | * xcb-renderutil 35 | * xcb-render 36 | * xcb-randr 37 | * xcb-composite 38 | * xcb-image 39 | * xcb-present 40 | * xcb-glx 41 | * pixman 42 | * libdbus (optional, disable with the `-Ddbus=false` meson configure flag) 43 | * libconfig (optional, disable with the `-Dconfig_file=false` meson configure flag) 44 | * libGL, libEGL, libepoxy (optional, disable with the `-Dopengl=false` meson configure flag) 45 | * libpcre2 (optional, disable with the `-Dregex=false` meson configure flag) 46 | * libev 47 | * uthash 48 | 49 | On Debian based distributions (e.g. Ubuntu), the needed packages are 50 | 51 | ``` 52 | libconfig-dev libdbus-1-dev libegl-dev libev-dev libgl-dev libepoxy-dev libpcre2-dev libpixman-1-dev libx11-xcb-dev libxcb1-dev libxcb-composite0-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-glx0-dev libxcb-image0-dev libxcb-present-dev libxcb-randr0-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev libxcb-util-dev libxcb-xfixes0-dev libxext-dev meson ninja-build uthash-dev 53 | ``` 54 | 55 | On Fedora, the needed packages are 56 | 57 | ``` 58 | dbus-devel gcc git libconfig-devel libdrm-devel libev-devel libX11-devel libX11-xcb libXext-devel libxcb-devel libGL-devel libEGL-devel libepoxy-devel meson pcre2-devel pixman-devel uthash-devel xcb-util-image-devel xcb-util-renderutil-devel xorg-x11-proto-devel xcb-util-devel 59 | ``` 60 | 61 | To build the documents, you need `asciidoc` 62 | 63 | ### To build 64 | 65 | ```bash 66 | $ meson setup --buildtype=release build 67 | $ ninja -C build 68 | ``` 69 | 70 | Built binary can be found in `build/src` 71 | 72 | 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. 73 | 74 | You can do that by setting the `CPPFLAGS` and `LDFLAGS` environment variables when running `meson`. Like this: 75 | 76 | ```bash 77 | $ LDFLAGS="-L/path/to/libraries" CPPFLAGS="-I/path/to/headers" meson setup --buildtype=release build 78 | ``` 79 | 80 | As an example, on FreeBSD, you might have to run meson with: 81 | ```bash 82 | $ LDFLAGS="-L/usr/local/lib" CPPFLAGS="-I/usr/local/include" meson setup --buildtype=release build 83 | $ ninja -C build 84 | ``` 85 | 86 | ### To install 87 | 88 | #### AUR (arch) 89 | - picom-ftlabs-git 90 | Thanks to @Fxzzi for maintaining the package. 91 | 92 | 93 | ``` bash 94 | $ ninja -C build install 95 | ``` 96 | 97 | Default install prefix is `/usr/local`, you can change it with `meson configure -Dprefix= build` 98 | 99 | ## Running 100 | To launch with all animations as a background process you can use: 101 | `picom --animations -b` 102 | 103 | To only have specific animations, enable them with cli flags (see `picom --help`) or add them to your picom config. 104 | 105 | ## How to Contribute 106 | 107 | All contributions are welcome! 108 | 109 | New features you think should be included in picom, a fix for a bug you found - please open a PR! 110 | 111 | You can take a look at the [Issues](https://github.com/yshui/picom/issues). 112 | 113 | Contributions to the documents and wiki are also appreciated. 114 | 115 | Even if you don't want to add anything to picom, you are still helping by compiling and running this branch, and report any issue you can find. 116 | 117 | ### Become a Collaborator 118 | 119 | Becoming a collaborator of picom requires significant time commitment. You are expected to reply to issue reports, reviewing PRs, and sometimes fix bugs or implement new feature. You won't be able to push to the main branch directly, and all you code still has to go through code review. 120 | 121 | If this sounds good to you, feel free to contact me. 122 | 123 | ## Contributors 124 | 125 | See [CONTRIBUTORS](CONTRIBUTORS) 126 | 127 | The README for the [original Compton project](https://github.com/chjj/compton/) can be found [here](History.md#Compton). 128 | 129 | ## Licensing 130 | 131 | picom is free software, made available under the [MIT](LICENSES/MIT) and [MPL-2.0](LICENSES/MPL-2.0) software 132 | licenses. See the individual source files for details. 133 | -------------------------------------------------------------------------------- /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 | NoDisplay=true 5 | Name=compton 6 | GenericName=X compositor 7 | Comment=An X compositor 8 | Categories=Utility; 9 | Keywords=compositor;composite manager;window effects;transparency;opacity; 10 | TryExec=compton 11 | Exec=compton 12 | Icon=compton 13 | # Thanks to quequotion for providing this file! 14 | -------------------------------------------------------------------------------- /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 | # == Declare stderr function === 4 | 5 | stderr() { 6 | printf "\033[1;31m%s\n\033[0m" "$@" >&2 7 | } 8 | 9 | # === Verify `picom --dbus` status === 10 | 11 | if [ -z "$(dbus-send --session --dest=org.freedesktop.DBus --type=method_call --print-reply /org/freedesktop/DBus org.freedesktop.DBus.ListNames | grep compton)" ]; then 12 | stderr "picom DBus interface unavailable" 13 | if [ -n "$(pgrep picom)" ]; then 14 | stderr "picom running without dbus interface" 15 | #killall picom & # Causes all windows to flicker away and come back ugly. 16 | #picom --dbus & # Causes all windows to flicker away and come back beautiful 17 | else 18 | stderr "picom not running" 19 | fi 20 | exit 1 21 | fi 22 | 23 | # === Setup sed === 24 | 25 | SED="${SED:-$(command -v gsed || printf 'sed')}" 26 | 27 | # === Get connection parameters === 28 | 29 | dpy=$(printf "$DISPLAY" | tr -c '[:alnum:]' _) 30 | 31 | if [ -z "$dpy" ]; then 32 | stderr "Cannot find display." 33 | exit 1 34 | fi 35 | 36 | service="com.github.chjj.compton.${dpy}" 37 | interface="com.github.chjj.compton" 38 | picom_dbus="dbus-send --print-reply --dest="${service}" / "${interface}"." 39 | type_win='uint32' 40 | type_enum='uint32' 41 | 42 | # === Color Inversion === 43 | 44 | # Get window ID of window to invert 45 | if [ -z "$1" -o "$1" = "selected" ]; then 46 | window=$(xwininfo -frame | sed -n 's/^xwininfo: Window id: \(0x[[:xdigit:]][[:xdigit:]]*\).*/\1/p') # Select window by mouse 47 | elif [ "$1" = "focused" ]; then 48 | # Ensure we are tracking focus 49 | window=$(${picom_dbus}find_win string:focused | $SED -n 's/^[[:space:]]*'${type_win}'[[:space:]]*\([[:digit:]]*\).*/\1/p') # Query picom for the active window 50 | elif echo "$1" | grep -Eiq '^([[:digit:]][[:digit:]]*|0x[[:xdigit:]][[:xdigit:]]*)$'; then 51 | window="$1" # Accept user-specified window-id if the format is correct 52 | else 53 | echo "$0" "[ selected | focused | window-id ]" 54 | fi 55 | 56 | # Color invert the selected or focused window 57 | if [ -n "$window" ]; then 58 | invert_status="$(${picom_dbus}win_get "${type_win}:${window}" string:invert_color | $SED -n 's/^[[:space:]]*boolean[[:space:]]*\([[:alpha:]]*\).*/\1/p')" 59 | if [ "$invert_status" = true ]; then 60 | invert=0 # Set the window to have normal color 61 | else 62 | invert=1 # Set the window to have inverted color 63 | fi 64 | ${picom_dbus}win_set "${type_win}:${window}" string:invert_color_force "${type_enum}:${invert}" & 65 | else 66 | stderr "Cannot find $1 window." 67 | exit 1 68 | fi 69 | exit 0 70 | -------------------------------------------------------------------------------- /desc.txt: -------------------------------------------------------------------------------- 1 | Compton is a X compositing window manager, fork of xcompmgr-dana. 2 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1705309234, 9 | "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "git-ignore-nix": { 22 | "inputs": { 23 | "nixpkgs": [ 24 | "nixpkgs" 25 | ] 26 | }, 27 | "locked": { 28 | "lastModified": 1703887061, 29 | "narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=", 30 | "owner": "hercules-ci", 31 | "repo": "gitignore.nix", 32 | "rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5", 33 | "type": "github" 34 | }, 35 | "original": { 36 | "owner": "hercules-ci", 37 | "ref": "master", 38 | "repo": "gitignore.nix", 39 | "type": "github" 40 | } 41 | }, 42 | "nixpkgs": { 43 | "locked": { 44 | "lastModified": 1705856552, 45 | "narHash": "sha256-JXfnuEf5Yd6bhMs/uvM67/joxYKoysyE3M2k6T3eWbg=", 46 | "owner": "NixOS", 47 | "repo": "nixpkgs", 48 | "rev": "612f97239e2cc474c13c9dafa0df378058c5ad8d", 49 | "type": "github" 50 | }, 51 | "original": { 52 | "id": "nixpkgs", 53 | "type": "indirect" 54 | } 55 | }, 56 | "root": { 57 | "inputs": { 58 | "flake-utils": "flake-utils", 59 | "git-ignore-nix": "git-ignore-nix", 60 | "nixpkgs": "nixpkgs" 61 | } 62 | }, 63 | "systems": { 64 | "locked": { 65 | "lastModified": 1681028828, 66 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 67 | "owner": "nix-systems", 68 | "repo": "default", 69 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 70 | "type": "github" 71 | }, 72 | "original": { 73 | "owner": "nix-systems", 74 | "repo": "default", 75 | "type": "github" 76 | } 77 | } 78 | }, 79 | "root": "root", 80 | "version": 7 81 | } 82 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | flake-utils.url = github:numtide/flake-utils; 4 | git-ignore-nix = { 5 | url = github:hercules-ci/gitignore.nix/master; 6 | inputs.nixpkgs.follows = "nixpkgs"; 7 | }; 8 | }; 9 | outputs = { 10 | self, 11 | flake-utils, 12 | nixpkgs, 13 | git-ignore-nix, 14 | ... 15 | }: 16 | flake-utils.lib.eachDefaultSystem (system: let 17 | overlay = self: super: { 18 | picom = super.picom.overrideAttrs (oldAttrs: rec { 19 | version = "11"; 20 | pname = "picom"; 21 | buildInputs = 22 | [ 23 | self.pcre2 24 | self.xorg.xcbutil 25 | self.libepoxy 26 | ] 27 | ++ self.lib.remove self.xorg.libXinerama ( 28 | self.lib.remove self.pcre oldAttrs.buildInputs 29 | ); 30 | src = git-ignore-nix.lib.gitignoreSource ./.; 31 | }); 32 | }; 33 | 34 | pkgs = import nixpkgs { 35 | inherit system overlays; 36 | config.allowBroken = true; 37 | }; 38 | 39 | overlays = [overlay]; 40 | in rec { 41 | inherit 42 | overlay 43 | overlays 44 | ; 45 | defaultPackage = pkgs.picom; 46 | devShell = defaultPackage.overrideAttrs { 47 | buildInputs = 48 | defaultPackage.buildInputs 49 | ++ (with pkgs; [ 50 | clang-tools_17 51 | llvmPackages_17.clang-unwrapped.python 52 | ]); 53 | hardeningDisable = ["fortify"]; 54 | shellHook = '' 55 | # Workaround a NixOS limitation on sanitizers: 56 | # See: https://github.com/NixOS/nixpkgs/issues/287763 57 | export LD_LIBRARY_PATH+=":/run/opengl-driver/lib" 58 | ''; 59 | }; 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /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, 11 | install_dir: join_paths(get_option('mandir'), 'man1')) 12 | endforeach 13 | endif 14 | -------------------------------------------------------------------------------- /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*='WINDOW_ID':: 25 | Specify the window id of the target window. 26 | 27 | *-n*, *--name*='WINDOW_NAME':: 28 | Specify and try to match a window name. 29 | 30 | *-c*, *--current*:: 31 | Specify the currently active window as target. Only works if EWMH '_NET_ACTIVE_WINDOW' property exists on root window. 32 | 33 | *-s*, *--select*:: 34 | Select target window with mouse cursor. This is the default if no window has been specified. 35 | 36 | *-o*, *--opacity*='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 | *-g*, *--get*:: 40 | Print the target window's opacity instead of setting it. 41 | 42 | *-d*, *--delete*:: 43 | Delete opacity of the target window instead of setting it. 44 | 45 | *-t*, *--toggle*:: 46 | Toggle the target window's opacity: Set opacity if not already set, and delete if already set. 47 | 48 | *-r*, *--reset*:: 49 | Reset opacity for all windows instead of setting it. 50 | 51 | EXAMPLES 52 | -------- 53 | 54 | * Set the opacity of the window with specific window ID to 75%: 55 | + 56 | ------------ 57 | picom-trans -w "$WINDOWID" 75 58 | ------------ 59 | 60 | * Set the opacity of the window with the name "urxvt" to 75%: 61 | + 62 | ------------ 63 | picom-trans -n "urxvt" 75 64 | ------------ 65 | 66 | * Set current window to opacity of 75%: 67 | + 68 | ------------ 69 | picom-trans -c 75 70 | ------------ 71 | 72 | * Select target window and set opacity to 75%: 73 | + 74 | ------------ 75 | picom-trans -s 75 76 | ------------ 77 | 78 | * Increment opacity of current active window by 5%: 79 | + 80 | ------------ 81 | picom-trans -c +5 82 | ------------ 83 | 84 | * Decrement opacity of current active window by 5%: 85 | + 86 | ------------ 87 | picom-trans -c -- -5 88 | ------------ 89 | 90 | * Delete current window's opacity: 91 | + 92 | ------------ 93 | picom-trans -c --delete 94 | ------------ 95 | 96 | * Toggle current window's opacity between 90 and unset 97 | + 98 | ------------ 99 | picom-trans -c --toggle 90 100 | ------------ 101 | 102 | * Reset all windows: 103 | + 104 | ------------ 105 | picom-trans --reset 106 | ------------ 107 | 108 | BUGS 109 | ---- 110 | Please submit bug reports to . 111 | 112 | SEE ALSO 113 | -------- 114 | link:picom.1.html[*picom*(1)], *xprop*(1), *xwininfo*(1) 115 | -------------------------------------------------------------------------------- /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/FT-Labs/picom/df4c6a3d9b11e14ed7f3142540babea4c775ddb1/media/icons/48x48/compton.png -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('picom', 'c', version: '11', 2 | default_options: ['c_std=c11', 'warning_level=1']) 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', check: false) 13 | if gitv.returncode() == 0 14 | version = 'vgit-'+gitv.stdout().strip() 15 | endif 16 | endif 17 | 18 | add_global_arguments('-DPICOM_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 | if get_option('warning_level') != '0' 57 | warns = [ 'cast-function-type', 'ignored-qualifiers', 'missing-parameter-type', 58 | 'nonnull', 'shadow', 'no-type-limits', 'old-style-declaration', 'override-init', 59 | 'sign-compare', 'type-limits', 'uninitialized', 'shift-negative-value', 60 | 'unused-but-set-parameter', 'unused-parameter', 'implicit-fallthrough=2', 61 | 'no-unknown-warning-option', 'no-missing-braces', 'conversion', 'empty-body' ] 62 | foreach w : warns 63 | if cc.has_argument('-W'+w) 64 | add_global_arguments('-W'+w, language: 'c') 65 | endif 66 | endforeach 67 | endif 68 | 69 | test_h_dep = subproject('test.h').get_variable('test_h_dep') 70 | 71 | subdir('src') 72 | subdir('man') 73 | 74 | install_data('bin/picom-trans', install_dir: get_option('bindir')) 75 | install_data('picom.desktop', install_dir: 'share/applications') 76 | install_data('picom.desktop', install_dir: get_option('sysconfdir') / 'xdg' / 'autostart') 77 | 78 | if get_option('compton') 79 | install_data('compton.desktop', install_dir: 'share/applications') 80 | install_data('media/icons/48x48/compton.png', 81 | install_dir: 'share/icons/hicolor/48x48/apps') 82 | install_data('media/compton.svg', 83 | install_dir: 'share/icons/hicolor/scalable/apps') 84 | 85 | meson.add_install_script('meson/install.sh') 86 | endif 87 | -------------------------------------------------------------------------------- /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('compton', type: 'boolean', value: true, description: 'Install backwards compat with compton') 13 | 14 | option('with_docs', type: 'boolean', value: false, description: 'Build documentation and man pages') 15 | 16 | option('modularize', type: 'boolean', value: false, description: 'Build with clang\'s module system') 17 | 18 | option('unittest', type: 'boolean', value: false, description: 'Enable unittests in the code') 19 | -------------------------------------------------------------------------------- /picom-dbus.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | NoDisplay=true 5 | Name=picom (dbus) 6 | GenericName=X compositor (dbus) 7 | Comment=An X compositor with dbus backend enabled 8 | Categories=Utility; 9 | Keywords=compositor;composite manager;window effects;transparency;opacity; 10 | TryExec=picom 11 | Exec=picom --dbus 12 | -------------------------------------------------------------------------------- /picom.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | NoDisplay=false 5 | Name=picom 6 | GenericName=X compositor 7 | Comment=An X compositor 8 | Categories=Utility; 9 | Keywords=compositor;composite manager;window effects;transparency;opacity; 10 | TryExec=picom 11 | Exec=picom 12 | StartupNotify=false 13 | Terminal=false 14 | # Thanks to quequotion for providing this file! 15 | Icon=picom 16 | -------------------------------------------------------------------------------- /src/atom.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "atom.h" 5 | #include "cache.h" 6 | #include "common.h" 7 | #include "compiler.h" 8 | #include "log.h" 9 | #include "utils.h" 10 | 11 | struct atom_entry { 12 | struct cache_handle entry; 13 | xcb_atom_t atom; 14 | }; 15 | 16 | static inline int atom_getter(struct cache *cache attr_unused, const char *atom_name, 17 | struct cache_handle **value, void *user_data) { 18 | xcb_connection_t *c = user_data; 19 | xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply( 20 | c, xcb_intern_atom(c, 0, to_u16_checked(strlen(atom_name)), atom_name), NULL); 21 | 22 | xcb_atom_t atom = XCB_NONE; 23 | if (reply) { 24 | log_debug("Atom %s is %d", atom_name, reply->atom); 25 | atom = reply->atom; 26 | free(reply); 27 | } else { 28 | log_error("Failed to intern atoms"); 29 | return -1; 30 | } 31 | 32 | struct atom_entry *entry = ccalloc(1, struct atom_entry); 33 | entry->atom = atom; 34 | *value = &entry->entry; 35 | return 0; 36 | } 37 | 38 | static inline void 39 | atom_entry_free(struct cache *cache attr_unused, struct cache_handle *handle) { 40 | struct atom_entry *entry = cache_entry(handle, struct atom_entry, entry); 41 | free(entry); 42 | } 43 | 44 | xcb_atom_t get_atom(struct atom *a, const char *key, xcb_connection_t *c) { 45 | struct cache_handle *entry = NULL; 46 | if (cache_get_or_fetch(&a->c, key, &entry, c, atom_getter) < 0) { 47 | log_error("Failed to get atom %s", key); 48 | return XCB_NONE; 49 | } 50 | return cache_entry(entry, struct atom_entry, entry)->atom; 51 | } 52 | 53 | xcb_atom_t get_atom_cached(struct atom *a, const char *key) { 54 | return cache_entry(cache_get(&a->c, key), struct atom_entry, entry)->atom; 55 | } 56 | 57 | /** 58 | * Create a new atom structure and fetch all predefined atoms 59 | */ 60 | struct atom *init_atoms(xcb_connection_t *c) { 61 | auto atoms = ccalloc(1, struct atom); 62 | atoms->c = CACHE_INIT; 63 | #define ATOM_GET(x) atoms->a##x = get_atom(atoms, #x, c) 64 | LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST1); 65 | LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST2); 66 | #undef ATOM_GET 67 | return atoms; 68 | } 69 | 70 | void destroy_atoms(struct atom *a) { 71 | cache_invalidate_all(&a->c, atom_entry_free); 72 | free(a); 73 | } 74 | -------------------------------------------------------------------------------- /src/atom.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "cache.h" 5 | #include "meta.h" 6 | 7 | // clang-format off 8 | // Splitted into 2 lists because of the limitation of our macros 9 | #define ATOM_LIST1 \ 10 | _NET_WM_WINDOW_OPACITY, \ 11 | _NET_FRAME_EXTENTS, \ 12 | WM_STATE, \ 13 | _NET_WM_NAME, \ 14 | _NET_WM_PID, \ 15 | WM_NAME, \ 16 | WM_CLASS, \ 17 | WM_ICON_NAME, \ 18 | WM_TRANSIENT_FOR, \ 19 | WM_WINDOW_ROLE, \ 20 | WM_CLIENT_LEADER, \ 21 | WM_CLIENT_MACHINE, \ 22 | _NET_ACTIVE_WINDOW, \ 23 | _COMPTON_SHADOW, \ 24 | COMPTON_VERSION, \ 25 | _NET_WM_WINDOW_TYPE, \ 26 | _XROOTPMAP_ID, \ 27 | ESETROOT_PMAP_ID, \ 28 | _XSETROOT_ID 29 | 30 | #define ATOM_LIST2 \ 31 | _NET_WM_WINDOW_TYPE_DESKTOP, \ 32 | _NET_WM_WINDOW_TYPE_DOCK, \ 33 | _NET_WM_WINDOW_TYPE_TOOLBAR, \ 34 | _NET_WM_WINDOW_TYPE_MENU, \ 35 | _NET_WM_WINDOW_TYPE_UTILITY, \ 36 | _NET_WM_WINDOW_TYPE_SPLASH, \ 37 | _NET_WM_WINDOW_TYPE_DIALOG, \ 38 | _NET_WM_WINDOW_TYPE_NORMAL, \ 39 | _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, \ 40 | _NET_WM_WINDOW_TYPE_POPUP_MENU, \ 41 | _NET_WM_WINDOW_TYPE_TOOLTIP, \ 42 | _NET_WM_WINDOW_TYPE_NOTIFICATION, \ 43 | _NET_WM_WINDOW_TYPE_COMBO, \ 44 | _NET_WM_WINDOW_TYPE_DND, \ 45 | _NET_WM_STATE, \ 46 | _NET_WM_STATE_FULLSCREEN, \ 47 | _NET_WM_BYPASS_COMPOSITOR, \ 48 | UTF8_STRING, \ 49 | C_STRING 50 | // clang-format on 51 | 52 | #define ATOM_DEF(x) xcb_atom_t a##x 53 | 54 | struct atom_entry; 55 | struct atom { 56 | struct cache c; 57 | LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST1); 58 | LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST2); 59 | }; 60 | 61 | /// Create a new atom object with a xcb connection. `struct atom` does not hold 62 | /// a reference to the connection. 63 | struct atom *init_atoms(xcb_connection_t *c); 64 | 65 | xcb_atom_t get_atom(struct atom *a, const char *key, xcb_connection_t *c); 66 | xcb_atom_t get_atom_cached(struct atom *a, const char *key); 67 | 68 | void destroy_atoms(struct atom *a); 69 | -------------------------------------------------------------------------------- /src/backend/backend_common.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "backend.h" 11 | #include "config.h" 12 | #include "region.h" 13 | 14 | typedef struct session session_t; 15 | typedef struct win win; 16 | typedef struct conv conv; 17 | typedef struct backend_base backend_t; 18 | struct backend_operations; 19 | 20 | struct dual_kawase_params { 21 | /// Number of downsample passes 22 | int iterations; 23 | /// Pixel offset for down- and upsample 24 | float offset; 25 | /// Save area around blur target (@ref resize_width, @ref resize_height) 26 | int expand; 27 | }; 28 | 29 | struct backend_image_inner_base { 30 | int refcount; 31 | bool has_alpha; 32 | }; 33 | 34 | struct backend_image { 35 | // Backend dependent inner image data 36 | struct backend_image_inner_base *inner; 37 | double opacity; 38 | double dim; 39 | double max_brightness; 40 | double corner_radius; 41 | // Effective size of the image 42 | int ewidth, eheight; 43 | bool color_inverted; 44 | int border_width; 45 | }; 46 | 47 | bool build_shadow(struct x_connection *, double opacity, int width, int height, 48 | const conv *kernel, xcb_render_picture_t shadow_pixel, 49 | xcb_pixmap_t *pixmap, xcb_render_picture_t *pict); 50 | 51 | xcb_render_picture_t 52 | solid_picture(struct x_connection *, bool argb, double a, double r, double g, double b); 53 | 54 | xcb_image_t *make_shadow(struct x_connection *c, const conv *kernel, double opacity, 55 | int width, int height); 56 | 57 | image_handle default_render_shadow(backend_t *backend_data, int width, int height, 58 | struct backend_shadow_context *sctx, struct color color); 59 | 60 | /// Implement `render_shadow` with `shadow_from_mask`. 61 | image_handle 62 | backend_render_shadow_from_mask(backend_t *backend_data, int width, int height, 63 | struct backend_shadow_context *sctx, struct color color); 64 | struct backend_shadow_context * 65 | default_create_shadow_context(backend_t *backend_data, double radius); 66 | 67 | void default_destroy_shadow_context(backend_t *backend_data, 68 | struct backend_shadow_context *sctx); 69 | 70 | void init_backend_base(struct backend_base *base, session_t *ps); 71 | 72 | struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count); 73 | struct dual_kawase_params *generate_dual_kawase_params(void *args); 74 | 75 | image_handle default_clone_image(backend_t *base, image_handle image, const region_t *reg); 76 | bool default_is_image_transparent(backend_t *base attr_unused, image_handle image); 77 | bool default_set_image_property(backend_t *base attr_unused, enum image_properties op, 78 | image_handle image, void *arg); 79 | struct backend_image *default_new_backend_image(int w, int h); 80 | -------------------------------------------------------------------------------- /src/backend/driver.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "backend/backend.h" 10 | #include "backend/driver.h" 11 | #include "common.h" 12 | #include "compiler.h" 13 | #include "log.h" 14 | 15 | /// Apply driver specified global workarounds. It's safe to call this multiple times. 16 | void apply_driver_workarounds(struct session *ps, enum driver driver) { 17 | if (driver & DRIVER_NVIDIA) { 18 | ps->o.xrender_sync_fence = true; 19 | } 20 | } 21 | 22 | enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver attr_unused) { 23 | enum vblank_scheduler_type type = VBLANK_SCHEDULER_PRESENT; 24 | #ifdef CONFIG_OPENGL 25 | if (driver & DRIVER_NVIDIA) { 26 | type = VBLANK_SCHEDULER_SGI_VIDEO_SYNC; 27 | } 28 | #endif 29 | return type; 30 | } 31 | 32 | enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) { 33 | enum driver ret = 0; 34 | // First we try doing backend agnostic detection using RANDR 35 | // There's no way to query the X server about what driver is loaded, so RANDR is 36 | // our best shot. 37 | auto randr_version = xcb_randr_query_version_reply( 38 | c, xcb_randr_query_version(c, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), 39 | NULL); 40 | if (randr_version && 41 | (randr_version->major_version > 1 || randr_version->minor_version >= 4)) { 42 | auto r = xcb_randr_get_providers_reply( 43 | c, xcb_randr_get_providers(c, window), NULL); 44 | if (r == NULL) { 45 | log_warn("Failed to get RANDR providers"); 46 | free(randr_version); 47 | return 0; 48 | } 49 | 50 | auto providers = xcb_randr_get_providers_providers(r); 51 | for (auto i = 0; i < xcb_randr_get_providers_providers_length(r); i++) { 52 | auto r2 = xcb_randr_get_provider_info_reply( 53 | c, xcb_randr_get_provider_info(c, providers[i], r->timestamp), NULL); 54 | if (r2 == NULL) { 55 | continue; 56 | } 57 | if (r2->num_outputs == 0) { 58 | free(r2); 59 | continue; 60 | } 61 | 62 | auto name_len = xcb_randr_get_provider_info_name_length(r2); 63 | assert(name_len >= 0); 64 | auto name = 65 | strndup(xcb_randr_get_provider_info_name(r2), (size_t)name_len); 66 | if (strcasestr(name, "modesetting") != NULL) { 67 | ret |= DRIVER_MODESETTING; 68 | } else if (strcasestr(name, "Radeon") != NULL) { 69 | // Be conservative, add both radeon drivers 70 | ret |= DRIVER_AMDGPU | DRIVER_RADEON; 71 | } else if (strcasestr(name, "NVIDIA") != NULL) { 72 | ret |= DRIVER_NVIDIA; 73 | } else if (strcasestr(name, "nouveau") != NULL) { 74 | ret |= DRIVER_NOUVEAU; 75 | } else if (strcasestr(name, "Intel") != NULL) { 76 | ret |= DRIVER_INTEL; 77 | } 78 | free(name); 79 | free(r2); 80 | } 81 | free(r); 82 | } 83 | free(randr_version); 84 | 85 | // If the backend supports driver detection, use that as well 86 | if (backend_data && backend_data->ops->detect_driver) { 87 | ret |= backend_data->ops->detect_driver(backend_data); 88 | } 89 | return ret; 90 | } 91 | -------------------------------------------------------------------------------- /src/backend/driver.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "config.h" 11 | #include "utils.h" 12 | 13 | struct session; 14 | struct backend_base; 15 | 16 | // A list of known driver quirks: 17 | // * NVIDIA driver doesn't like seeing the same pixmap under different 18 | // ids, so avoid naming the pixmap again when it didn't actually change. 19 | 20 | /// A list of possible drivers. 21 | /// The driver situation is a bit complicated. There are two drivers we care about: the 22 | /// DDX, and the OpenGL driver. They are usually paired, but not always, since there is 23 | /// also the generic modesetting driver. 24 | /// This enum represents _both_ drivers. 25 | enum driver { 26 | DRIVER_AMDGPU = 1, // AMDGPU for DDX, radeonsi for OpenGL 27 | DRIVER_RADEON = 2, // ATI for DDX, mesa r600 for OpenGL 28 | DRIVER_FGLRX = 4, 29 | DRIVER_NVIDIA = 8, 30 | DRIVER_NOUVEAU = 16, 31 | DRIVER_INTEL = 32, 32 | DRIVER_MODESETTING = 64, 33 | }; 34 | 35 | static const char *driver_names[] = { 36 | "AMDGPU", "Radeon", "fglrx", "NVIDIA", "nouveau", "Intel", "modesetting", 37 | }; 38 | 39 | /// Return a list of all drivers currently in use by the X server. 40 | /// Note, this is a best-effort test, so no guarantee all drivers will be detected. 41 | enum driver detect_driver(xcb_connection_t *, struct backend_base *, xcb_window_t); 42 | 43 | /// Apply driver specified global workarounds. It's safe to call this multiple times. 44 | void apply_driver_workarounds(struct session *ps, enum driver); 45 | /// Choose a vblank scheduler based on the driver. 46 | enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver); 47 | 48 | // Print driver names to stdout, for diagnostics 49 | static inline void print_drivers(enum driver drivers) { 50 | const char *seen_drivers[ARR_SIZE(driver_names)]; 51 | int driver_count = 0; 52 | for (size_t i = 0; i < ARR_SIZE(driver_names); i++) { 53 | if (drivers & (1UL << i)) { 54 | seen_drivers[driver_count++] = driver_names[i]; 55 | } 56 | } 57 | 58 | if (driver_count > 0) { 59 | printf("%s", seen_drivers[0]); 60 | for (int i = 1; i < driver_count; i++) { 61 | printf(", %s", seen_drivers[i]); 62 | } 63 | } 64 | printf("\n"); 65 | } 66 | -------------------------------------------------------------------------------- /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 | bool owned; 21 | UT_hash_handle hh; 22 | }; 23 | 24 | struct dummy_data { 25 | struct backend_base base; 26 | struct dummy_image *images; 27 | 28 | struct backend_image mask; 29 | }; 30 | 31 | struct backend_base *dummy_init(session_t *ps attr_unused, xcb_window_t target attr_unused) { 32 | auto ret = (struct backend_base *)ccalloc(1, struct dummy_data); 33 | ret->c = &ps->c; 34 | ret->loop = ps->loop; 35 | ret->busy = false; 36 | return ret; 37 | } 38 | 39 | void dummy_deinit(struct backend_base *data) { 40 | auto dummy = (struct dummy_data *)data; 41 | HASH_ITER2(dummy->images, img) { 42 | log_warn("Backend image for pixmap %#010x is not freed", img->pixmap); 43 | HASH_DEL(dummy->images, img); 44 | free(img->refcount); 45 | if (img->owned) { 46 | xcb_free_pixmap(data->c->c, img->pixmap); 47 | } 48 | free(img); 49 | } 50 | free(dummy); 51 | } 52 | 53 | static void dummy_check_image(struct backend_base *base, image_handle image) { 54 | auto dummy = (struct dummy_data *)base; 55 | auto img = (struct dummy_image *)image; 56 | if (img == (struct dummy_image *)&dummy->mask) { 57 | return; 58 | } 59 | struct dummy_image *tmp = NULL; 60 | HASH_FIND_INT(dummy->images, &img->pixmap, tmp); 61 | if (!tmp) { 62 | log_warn("Using an invalid (possibly freed) image"); 63 | assert(false); 64 | } 65 | assert(*tmp->refcount > 0); 66 | } 67 | 68 | void dummy_compose(struct backend_base *base, image_handle image, coord_t dst attr_unused, 69 | image_handle mask attr_unused, coord_t mask_dst attr_unused, 70 | const region_t *reg_paint attr_unused, 71 | const region_t *reg_visible attr_unused, bool lerp attr_unused) { 72 | auto dummy attr_unused = (struct dummy_data *)base; 73 | dummy_check_image(base, image); 74 | assert(mask == NULL || (struct backend_image *)mask == &dummy->mask); 75 | } 76 | 77 | void dummy_fill(struct backend_base *backend_data attr_unused, struct color c attr_unused, 78 | const region_t *clip attr_unused) { 79 | } 80 | 81 | bool dummy_blur(struct backend_base *backend_data attr_unused, double opacity attr_unused, 82 | void *blur_ctx attr_unused, image_handle mask attr_unused, 83 | coord_t mask_dst attr_unused, const region_t *reg_blur attr_unused, 84 | const region_t *reg_visible attr_unused) { 85 | return true; 86 | } 87 | 88 | image_handle dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap, 89 | struct xvisual_info fmt, bool owned) { 90 | auto dummy = (struct dummy_data *)base; 91 | struct dummy_image *img = NULL; 92 | HASH_FIND_INT(dummy->images, &pixmap, img); 93 | if (img) { 94 | (*img->refcount)++; 95 | return (image_handle)img; 96 | } 97 | 98 | img = ccalloc(1, struct dummy_image); 99 | img->pixmap = pixmap; 100 | img->transparent = fmt.alpha_size != 0; 101 | img->refcount = ccalloc(1, int); 102 | *img->refcount = 1; 103 | img->owned = owned; 104 | 105 | HASH_ADD_INT(dummy->images, pixmap, img); 106 | return (image_handle)img; 107 | } 108 | 109 | void dummy_release_image(backend_t *base, image_handle image) { 110 | auto dummy = (struct dummy_data *)base; 111 | if ((struct backend_image *)image == &dummy->mask) { 112 | return; 113 | } 114 | auto img = (struct dummy_image *)image; 115 | assert(*img->refcount > 0); 116 | (*img->refcount)--; 117 | if (*img->refcount == 0) { 118 | HASH_DEL(dummy->images, img); 119 | free(img->refcount); 120 | if (img->owned) { 121 | xcb_free_pixmap(base->c->c, img->pixmap); 122 | } 123 | free(img); 124 | } 125 | } 126 | 127 | bool dummy_is_image_transparent(struct backend_base *base, image_handle image) { 128 | dummy_check_image(base, image); 129 | return ((struct dummy_image *)image)->transparent; 130 | } 131 | 132 | int dummy_buffer_age(struct backend_base *base attr_unused) { 133 | return 2; 134 | } 135 | 136 | bool dummy_image_op(struct backend_base *base, enum image_operations op attr_unused, 137 | image_handle image, const region_t *reg_op attr_unused, 138 | const region_t *reg_visible attr_unused, void *args attr_unused) { 139 | dummy_check_image(base, image); 140 | return true; 141 | } 142 | 143 | image_handle dummy_make_mask(struct backend_base *base, geometry_t size attr_unused, 144 | const region_t *reg attr_unused) { 145 | auto dummy = (struct dummy_data *)base; 146 | auto mask = &dummy->mask; 147 | return (image_handle)mask; 148 | } 149 | 150 | bool dummy_set_image_property(struct backend_base *base, enum image_properties prop attr_unused, 151 | image_handle image, void *arg attr_unused) { 152 | dummy_check_image(base, image); 153 | return true; 154 | } 155 | 156 | image_handle dummy_clone_image(struct backend_base *base, image_handle image, 157 | const region_t *reg_visible attr_unused) { 158 | dummy_check_image(base, image); 159 | auto image_impl = (struct dummy_image *)image; 160 | (*image_impl->refcount)++; 161 | return image; 162 | } 163 | 164 | void *dummy_create_blur_context(struct backend_base *base attr_unused, 165 | enum blur_method method attr_unused, void *args attr_unused) { 166 | static int dummy_context; 167 | return &dummy_context; 168 | } 169 | 170 | void dummy_destroy_blur_context(struct backend_base *base attr_unused, void *ctx attr_unused) { 171 | } 172 | 173 | void dummy_get_blur_size(void *ctx attr_unused, int *width, int *height) { 174 | // These numbers are arbitrary, to make sure the resize_region code path is 175 | // covered. 176 | *width = 5; 177 | *height = 5; 178 | } 179 | 180 | struct backend_operations dummy_ops = { 181 | .init = dummy_init, 182 | .deinit = dummy_deinit, 183 | .compose = dummy_compose, 184 | .fill = dummy_fill, 185 | .blur = dummy_blur, 186 | .bind_pixmap = dummy_bind_pixmap, 187 | .create_shadow_context = default_create_shadow_context, 188 | .destroy_shadow_context = default_destroy_shadow_context, 189 | .render_shadow = default_render_shadow, 190 | .make_mask = dummy_make_mask, 191 | .release_image = dummy_release_image, 192 | .is_image_transparent = dummy_is_image_transparent, 193 | .buffer_age = dummy_buffer_age, 194 | .max_buffer_age = 5, 195 | 196 | .image_op = dummy_image_op, 197 | .clone_image = dummy_clone_image, 198 | .set_image_property = dummy_set_image_property, 199 | .create_blur_context = dummy_create_blur_context, 200 | .destroy_blur_context = dummy_destroy_blur_context, 201 | .get_blur_size = dummy_get_blur_size, 202 | 203 | }; 204 | -------------------------------------------------------------------------------- /src/backend/gl/egl.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 | #include 9 | 10 | #include "compiler.h" 11 | #include "log.h" 12 | #include "utils.h" 13 | #include "x.h" 14 | 15 | struct eglext_info { 16 | bool initialized; 17 | bool has_EGL_MESA_query_driver; 18 | bool has_EGL_EXT_buffer_age; 19 | bool has_EGL_EXT_create_context_robustness; 20 | bool has_EGL_KHR_image_pixmap; 21 | }; 22 | 23 | extern struct eglext_info eglext; 24 | 25 | void eglext_init(EGLDisplay); 26 | -------------------------------------------------------------------------------- /src/backend/gl/glx.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 | #include 9 | 10 | #include "compiler.h" 11 | #include "log.h" 12 | #include "utils.h" 13 | #include "x.h" 14 | 15 | struct glx_fbconfig_info { 16 | GLXFBConfig cfg; 17 | int texture_tgts; 18 | int texture_fmt; 19 | int y_inverted; 20 | }; 21 | 22 | bool glx_find_fbconfig(struct x_connection *c, struct xvisual_info m, 23 | struct glx_fbconfig_info *info); 24 | 25 | struct glxext_info { 26 | bool initialized; 27 | bool has_GLX_SGI_video_sync; 28 | bool has_GLX_SGI_swap_control; 29 | bool has_GLX_OML_sync_control; 30 | bool has_GLX_MESA_swap_control; 31 | bool has_GLX_EXT_swap_control; 32 | bool has_GLX_EXT_texture_from_pixmap; 33 | bool has_GLX_ARB_create_context; 34 | bool has_GLX_EXT_buffer_age; 35 | bool has_GLX_MESA_query_renderer; 36 | bool has_GLX_ARB_create_context_robustness; 37 | }; 38 | 39 | extern struct glxext_info glxext; 40 | 41 | void glxext_init(Display *, int screen); 42 | -------------------------------------------------------------------------------- /src/backend/gl/shaders.c: -------------------------------------------------------------------------------- 1 | #include "gl_common.h" 2 | 3 | // clang-format off 4 | const char dummy_frag[] = GLSL(330, 5 | uniform sampler2D tex; 6 | in vec2 texcoord; 7 | void main() { 8 | gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0); 9 | } 10 | ); 11 | 12 | const char present_frag[] = GLSL(330, 13 | uniform sampler2D tex; 14 | in vec2 texcoord; 15 | vec4 dither(vec4, vec2); 16 | void main() { 17 | gl_FragColor = dither(texelFetch(tex, ivec2(texcoord.xy), 0), texcoord); 18 | } 19 | ); 20 | 21 | const char copy_with_mask_frag[] = GLSL(330, 22 | uniform sampler2D tex; 23 | in vec2 texcoord; 24 | float mask_factor(); 25 | void main() { 26 | gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0) * mask_factor(); 27 | } 28 | ); 29 | 30 | const char fill_frag[] = GLSL(330, 31 | uniform vec4 color; 32 | void main() { 33 | gl_FragColor = color; 34 | } 35 | ); 36 | 37 | const char fill_vert[] = GLSL(330, 38 | layout(location = 0) in vec2 in_coord; 39 | uniform mat4 projection; 40 | void main() { 41 | gl_Position = projection * vec4(in_coord, 0, 1); 42 | } 43 | ); 44 | 45 | const char interpolating_frag[] = GLSL(330, 46 | uniform sampler2D tex; 47 | in vec2 texcoord; 48 | void main() { 49 | gl_FragColor = vec4(texture2D(tex, vec2(texcoord.xy), 0).rgb, 1); 50 | } 51 | ); 52 | 53 | const char interpolating_vert[] = GLSL(330, 54 | uniform mat4 projection; 55 | uniform vec2 texsize; 56 | layout(location = 0) in vec2 in_coord; 57 | layout(location = 1) in vec2 in_texcoord; 58 | out vec2 texcoord; 59 | void main() { 60 | gl_Position = projection * vec4(in_coord, 0, 1); 61 | texcoord = in_texcoord / texsize; 62 | } 63 | ); 64 | const char masking_glsl[] = GLSL(330, 65 | uniform sampler2D mask_tex; 66 | uniform vec2 mask_offset; 67 | uniform float mask_corner_radius; 68 | uniform bool mask_inverted; 69 | in vec2 texcoord; 70 | float mask_rectangle_sdf(vec2 point, vec2 half_size) { 71 | vec2 d = abs(point) - half_size; 72 | return length(max(d, 0.0)); 73 | } 74 | float mask_factor() { 75 | vec2 mask_size = textureSize(mask_tex, 0); 76 | vec2 maskcoord = texcoord - mask_offset; 77 | vec4 mask = texture2D(mask_tex, maskcoord / mask_size); 78 | if (mask_corner_radius != 0) { 79 | vec2 inner_size = mask_size - vec2(mask_corner_radius) * 2.0f; 80 | float dist = mask_rectangle_sdf(maskcoord - mask_size / 2.0f, 81 | inner_size / 2.0f) - mask_corner_radius; 82 | if (dist > 0.0f) { 83 | mask.r *= (1.0f - clamp(dist, 0.0f, 1.0f)); 84 | } 85 | } 86 | if (mask_inverted) { 87 | mask.rgb = 1.0 - mask.rgb; 88 | } 89 | return mask.r; 90 | } 91 | ); 92 | const char win_shader_glsl[] = GLSL(330, 93 | uniform float opacity; 94 | uniform float dim; 95 | uniform float corner_radius; 96 | uniform float border_width; 97 | uniform bool invert_color; 98 | in vec2 texcoord; 99 | uniform sampler2D tex; 100 | uniform vec2 effective_size; 101 | uniform sampler2D brightness; 102 | uniform float max_brightness; 103 | // Signed distance field for rectangle center at (0, 0), with size of 104 | // half_size * 2 105 | float rectangle_sdf(vec2 point, vec2 half_size) { 106 | vec2 d = abs(point) - half_size; 107 | return length(max(d, 0.0)); 108 | } 109 | 110 | vec4 default_post_processing(vec4 c) { 111 | vec4 border_color = texture(tex, vec2(0.0, 0.5)); 112 | if (invert_color) { 113 | c = vec4(c.aaa - c.rgb, c.a); 114 | border_color = vec4(border_color.aaa - border_color.rgb, border_color.a); 115 | } 116 | c = vec4(c.rgb * (1.0 - dim), c.a) * opacity; 117 | border_color = vec4(border_color.rgb * (1.0 - dim), border_color.a) * opacity; 118 | 119 | vec3 rgb_brightness = texelFetch(brightness, ivec2(0, 0), 0).rgb; 120 | // Ref: https://en.wikipedia.org/wiki/Relative_luminance 121 | float brightness = rgb_brightness.r * 0.21 + 122 | rgb_brightness.g * 0.72 + 123 | rgb_brightness.b * 0.07; 124 | if (brightness > max_brightness) { 125 | c.rgb = c.rgb * (max_brightness / brightness); 126 | border_color.rgb = border_color.rgb * (max_brightness / brightness); 127 | } 128 | 129 | // Rim color is the color of the outer rim of the window, if there is no 130 | // border, it's the color of the window itself, otherwise it's the border. 131 | // Using mix() to avoid a branch here. 132 | vec4 rim_color = mix(c, border_color, clamp(border_width, 0.0f, 1.0f)); 133 | 134 | vec2 outer_size = effective_size; 135 | vec2 inner_size = outer_size - vec2(corner_radius) * 2.0f; 136 | float rect_distance = rectangle_sdf(texcoord - outer_size / 2.0f, 137 | inner_size / 2.0f) - corner_radius; 138 | if (rect_distance > 0.0f) { 139 | c = (1.0f - clamp(rect_distance, 0.0f, 1.0f)) * rim_color; 140 | } else { 141 | float factor = clamp(rect_distance + border_width, 0.0f, 1.0f); 142 | c = (1.0f - factor) * c + factor * border_color; 143 | } 144 | 145 | return c; 146 | } 147 | 148 | vec4 window_shader(); 149 | float mask_factor(); 150 | 151 | void main() { 152 | gl_FragColor = window_shader() * mask_factor(); 153 | } 154 | ); 155 | 156 | const char win_shader_default[] = GLSL(330, 157 | in vec2 texcoord; 158 | uniform sampler2D tex; 159 | vec4 default_post_processing(vec4 c); 160 | vec4 window_shader() { 161 | vec2 texsize = textureSize(tex, 0); 162 | vec4 c = texture2D(tex, texcoord / texsize, 0); 163 | return default_post_processing(c); 164 | } 165 | ); 166 | 167 | const char present_vertex_shader[] = GLSL(330, 168 | uniform mat4 projection; 169 | layout(location = 0) in vec2 coord; 170 | out vec2 texcoord; 171 | void main() { 172 | gl_Position = projection * vec4(coord, 0, 1); 173 | texcoord = coord; 174 | } 175 | ); 176 | const char vertex_shader[] = GLSL(330, 177 | uniform mat4 projection; 178 | uniform float scale = 1.0; 179 | uniform vec2 texorig; 180 | layout(location = 0) in vec2 coord; 181 | layout(location = 1) in vec2 in_texcoord; 182 | out vec2 texcoord; 183 | void main() { 184 | gl_Position = projection * vec4(coord, 0, scale); 185 | texcoord = in_texcoord + texorig; 186 | } 187 | ); 188 | const char dither_glsl[] = GLSL(330, 189 | // Stolen from: https://www.shadertoy.com/view/7sfXDn 190 | float bayer2(vec2 a) { 191 | a = floor(a); 192 | return fract(a.x / 2. + a.y * a.y * .75); 193 | } 194 | // 16 * 16 is 2^8, so in total we have equivalent of 16-bit 195 | // color depth, should be enough? 196 | float bayer(vec2 a16) { 197 | vec2 a8 = a16 * .5; 198 | vec2 a4 = a8 * .5; 199 | vec2 a2 = a4 * .5; 200 | float bayer32 = ((bayer2(a2) * .25 + bayer2( a4)) 201 | * .25 + bayer2( a8)) 202 | * .25 + bayer2(a16); 203 | return bayer32; 204 | } 205 | vec4 dither(vec4 c, vec2 coord) { 206 | vec4 residual = mod(c, 1.0 / 255.0); 207 | residual = min(residual, vec4(1.0 / 255.0) - residual); 208 | vec4 dithered = vec4(greaterThan(residual, vec4(1.0 / 65535.0))); 209 | return vec4(c + dithered * bayer(coord) / 255.0); 210 | } 211 | ); 212 | const char shadow_colorization_frag[] = GLSL(330, 213 | uniform vec4 color; 214 | uniform sampler2D tex; 215 | in vec2 texcoord; 216 | out vec4 out_color; 217 | void main() { 218 | vec4 c = texelFetch(tex, ivec2(texcoord), 0); 219 | out_color = c.r * color; 220 | } 221 | ); 222 | // clang-format on 223 | -------------------------------------------------------------------------------- /src/backend/meson.build: -------------------------------------------------------------------------------- 1 | # enable xrender 2 | srcs += [ files('backend_common.c', 'xrender/xrender.c', 'dummy/dummy.c', 'backend.c', 'driver.c') ] 3 | 4 | # enable opengl 5 | if get_option('opengl') 6 | srcs += [ files('gl/gl_common.c', 'gl/glx.c', 'gl/blur.c', 'gl/shaders.c', 'gl/egl.c') ] 7 | endif 8 | -------------------------------------------------------------------------------- /src/c2.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * 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 | #include 16 | 17 | typedef struct _c2_lptr c2_lptr_t; 18 | typedef struct session session_t; 19 | struct managed_win; 20 | 21 | typedef void (*c2_userdata_free)(void *); 22 | c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data); 23 | 24 | c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f); 25 | 26 | bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst, 27 | void **pdata); 28 | 29 | bool c2_list_postprocess(session_t *ps, c2_lptr_t *list); 30 | typedef bool (*c2_list_foreach_cb_t)(const c2_lptr_t *cond, void *data); 31 | bool c2_list_foreach(const c2_lptr_t *list, c2_list_foreach_cb_t cb, void *data); 32 | /// Return user data stored in a condition. 33 | void *c2_list_get_data(const c2_lptr_t *condlist); 34 | 35 | /** 36 | * Destroy a condition list. 37 | */ 38 | static inline void c2_list_free(c2_lptr_t **pcondlst, c2_userdata_free f) { 39 | while ((*pcondlst = c2_free_lptr(*pcondlst, f))) { 40 | } 41 | *pcondlst = NULL; 42 | } 43 | -------------------------------------------------------------------------------- /src/cache.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cache.h" 4 | 5 | struct cache_handle *cache_get(struct cache *c, const char *key) { 6 | struct cache_handle *e; 7 | HASH_FIND_STR(c->entries, key, e); 8 | return e; 9 | } 10 | 11 | int cache_get_or_fetch(struct cache *c, const char *key, struct cache_handle **value, 12 | void *user_data, cache_getter_t getter) { 13 | *value = cache_get(c, key); 14 | if (*value) { 15 | return 0; 16 | } 17 | 18 | int err = getter(c, key, value, user_data); 19 | assert(err <= 0); 20 | if (err < 0) { 21 | return err; 22 | } 23 | (*value)->key = strdup(key); 24 | 25 | HASH_ADD_STR(c->entries, key, *value); 26 | return 1; 27 | } 28 | 29 | static inline void 30 | cache_invalidate_impl(struct cache *c, struct cache_handle *e, cache_free_t free_fn) { 31 | free(e->key); 32 | HASH_DEL(c->entries, e); 33 | if (free_fn) { 34 | free_fn(c, e); 35 | } 36 | } 37 | 38 | void cache_invalidate_all(struct cache *c, cache_free_t free_fn) { 39 | struct cache_handle *e, *tmpe; 40 | HASH_ITER(hh, c->entries, e, tmpe) { 41 | cache_invalidate_impl(c, e, free_fn); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/cache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "utils.h" 5 | 6 | #define cache_entry(ptr, type, member) container_of(ptr, type, member) 7 | 8 | struct cache; 9 | struct cache_handle; 10 | 11 | /// User-provided function to fetch a value for the cache, when it's not present. 12 | /// Should return 0 if the value is fetched successfully, and a negative number if the 13 | /// value cannot be fetched. Getter doesn't need to initialize fields of `struct 14 | /// cache_handle`. 15 | typedef int (*cache_getter_t)(struct cache *, const char *key, 16 | struct cache_handle **value, void *user_data); 17 | typedef void (*cache_free_t)(struct cache *, struct cache_handle *value); 18 | 19 | struct cache { 20 | struct cache_handle *entries; 21 | }; 22 | 23 | static const struct cache CACHE_INIT = {NULL}; 24 | 25 | struct cache_handle { 26 | char *key; 27 | UT_hash_handle hh; 28 | }; 29 | 30 | /// Get a value from the cache. If the value doesn't present in the cache yet, the 31 | /// getter will be called, and the returned value will be stored into the cache. 32 | /// Returns 0 if the value is already present in the cache, 1 if the value is fetched 33 | /// successfully, and a negative number if the value cannot be fetched. 34 | int cache_get_or_fetch(struct cache *, const char *key, struct cache_handle **value, 35 | void *user_data, cache_getter_t getter); 36 | 37 | /// Get a value from the cache. If the value doesn't present in the cache, NULL will be 38 | /// returned. 39 | struct cache_handle *cache_get(struct cache *, const char *key); 40 | 41 | /// Invalidate all values in the cache. After this call, `struct cache` holds no allocated 42 | /// memory, and can be discarded. 43 | void cache_invalidate_all(struct cache *, cache_free_t free_fn); 44 | -------------------------------------------------------------------------------- /src/compiler.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2018 Yuxuan Shui 3 | #pragma once 4 | 5 | #ifdef HAS_STDC_PREDEF_H 6 | #include 7 | #endif 8 | 9 | // clang-format off 10 | #if __STDC_VERSION__ <= 201710L 11 | #define auto __auto_type 12 | #endif 13 | #define likely(x) __builtin_expect(!!(x), 1) 14 | #define unlikely(x) __builtin_expect(!!(x), 0) 15 | #define likely_if(x) if (likely(x)) 16 | #define unlikely_if(x) if (unlikely(x)) 17 | 18 | #ifndef __has_attribute 19 | # if __GNUC__ >= 4 20 | # define __has_attribute(x) 1 21 | # else 22 | # define __has_attribute(x) 0 23 | # endif 24 | #endif 25 | 26 | #if __has_attribute(const) 27 | # define attr_const __attribute__((const)) 28 | #else 29 | # define attr_const 30 | #endif 31 | 32 | #if __has_attribute(format) 33 | # define attr_printf(a, b) __attribute__((format(printf, a, b))) 34 | #else 35 | # define attr_printf(a, b) 36 | #endif 37 | 38 | #if __has_attribute(pure) 39 | # define attr_pure __attribute__((pure)) 40 | #else 41 | # define attr_pure 42 | #endif 43 | 44 | #if __has_attribute(unused) 45 | # define attr_unused __attribute__((unused)) 46 | #else 47 | # define attr_unused 48 | #endif 49 | 50 | #if __has_attribute(warn_unused_result) 51 | # define attr_warn_unused_result __attribute__((warn_unused_result)) 52 | #else 53 | # define attr_warn_unused_result 54 | #endif 55 | // An alias for convenience 56 | #define must_use attr_warn_unused_result 57 | 58 | #if __has_attribute(nonnull) 59 | # define attr_nonnull(...) __attribute__((nonnull(__VA_ARGS__))) 60 | # define attr_nonnull_all __attribute__((nonnull)) 61 | #else 62 | # define attr_nonnull(...) 63 | # define attr_nonnull_all 64 | #endif 65 | 66 | #if __has_attribute(returns_nonnull) 67 | # define attr_ret_nonnull __attribute__((returns_nonnull)) 68 | #else 69 | # define attr_ret_nonnull 70 | #endif 71 | 72 | #if __has_attribute(deprecated) 73 | # define attr_deprecated __attribute__((deprecated)) 74 | #else 75 | # define attr_deprecated 76 | #endif 77 | 78 | #if __has_attribute(malloc) 79 | # define attr_malloc __attribute__((malloc)) 80 | #else 81 | # define attr_malloc 82 | #endif 83 | 84 | #if __has_attribute(fallthrough) 85 | # define fallthrough() __attribute__((fallthrough)) 86 | #else 87 | # define fallthrough() 88 | #endif 89 | 90 | #if __has_attribute(cleanup) 91 | # define cleanup(func) __attribute__((cleanup(func))) 92 | #else 93 | # error "Compiler is missing cleanup attribute" 94 | #endif 95 | 96 | #if __STDC_VERSION__ >= 201112L 97 | # define attr_noret _Noreturn 98 | #else 99 | # if __has_attribute(noreturn) 100 | # define attr_noret __attribute__((noreturn)) 101 | # else 102 | # define attr_noret 103 | # endif 104 | #endif 105 | 106 | #ifndef unreachable 107 | # if defined(__GNUC__) || defined(__clang__) 108 | # define unreachable() assert(false); __builtin_unreachable() 109 | # else 110 | # define unreachable() assert(false); do {} while(0) 111 | # endif 112 | #endif 113 | 114 | #ifndef __has_include 115 | # define __has_include(x) 0 116 | #endif 117 | 118 | #if !defined(__STDC_NO_THREADS__) && __has_include() 119 | # include 120 | #elif __STDC_VERSION__ >= 201112L 121 | # define thread_local _Thread_local 122 | #elif defined(__GNUC__) || defined(__clang__) 123 | # define thread_local __thread 124 | #else 125 | # define thread_local _Pragma("GCC error \"No thread local storage support\"") __error__ 126 | #endif 127 | // clang-format on 128 | 129 | typedef unsigned long ulong; 130 | typedef unsigned int uint; 131 | 132 | static inline int attr_const popcntul(unsigned long a) { 133 | return __builtin_popcountl(a); 134 | } 135 | -------------------------------------------------------------------------------- /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 "common.h" 10 | #include "config.h" 11 | #include "diagnostic.h" 12 | #include "picom.h" 13 | 14 | void print_diagnostics(session_t *ps, const char *config_file, bool compositor_running) { 15 | printf("**Version:** " PICOM_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("* RandR: %s\n", ps->randr_exists ? "Yes" : "No"); 20 | printf("* Present: %s\n", ps->present_exists ? "Present" : "Not Present"); 21 | printf("\n### Misc:\n\n"); 22 | printf("* Use Overlay: %s\n", ps->overlay != XCB_NONE ? "Yes" : "No"); 23 | if (ps->overlay == XCB_NONE) { 24 | if (compositor_running) { 25 | printf(" (Another compositor is already running)\n"); 26 | } else if (session_redirection_mode(ps) != XCB_COMPOSITE_REDIRECT_MANUAL) { 27 | printf(" (Not in manual redirection mode)\n"); 28 | } else { 29 | printf("\n"); 30 | } 31 | } 32 | #ifdef __FAST_MATH__ 33 | printf("* Fast Math: Yes\n"); 34 | #endif 35 | printf("* Config file used: %s\n", config_file ?: "None"); 36 | printf("\n### Drivers (inaccurate):\n\n"); 37 | print_drivers(ps->drivers); 38 | 39 | for (int i = 0; i < NUM_BKEND; i++) { 40 | if (backend_list[i] && backend_list[i]->diagnostics) { 41 | printf("\n### Backend: %s\n\n", BACKEND_STRS[i]); 42 | auto data = backend_list[i]->init(ps, session_get_target_window(ps)); 43 | if (!data) { 44 | printf(" Cannot initialize this backend\n"); 45 | } else { 46 | backend_list[i]->diagnostics(data); 47 | backend_list[i]->deinit(data); 48 | } 49 | } 50 | } 51 | } 52 | 53 | // vim: set noet sw=8 ts=8 : 54 | -------------------------------------------------------------------------------- /src/diagnostic.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2018 Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | 7 | typedef struct session session_t; 8 | 9 | void print_diagnostics(session_t *, const char *config_file, bool compositor_running); 10 | -------------------------------------------------------------------------------- /src/err.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2019 Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | #include 7 | #include "compiler.h" 8 | 9 | // Functions for error reporting, adopted from Linux 10 | 11 | // INFO in user space we can probably be more liberal about what pointer we consider 12 | // error. e.g. In x86_64 Linux, all addresses with the highest bit set is invalid in user 13 | // space. 14 | #define MAX_ERRNO 4095 15 | 16 | static inline void *must_use ERR_PTR(intptr_t err) { 17 | return (void *)err; 18 | } 19 | 20 | static inline intptr_t must_use PTR_ERR(void *ptr) { 21 | return (intptr_t)ptr; 22 | } 23 | 24 | static inline bool must_use IS_ERR(void *ptr) { 25 | return unlikely((uintptr_t)ptr > (uintptr_t)-MAX_ERRNO); 26 | } 27 | 28 | static inline bool must_use IS_ERR_OR_NULL(void *ptr) { 29 | return unlikely(!ptr) || IS_ERR(ptr); 30 | } 31 | 32 | static inline intptr_t must_use PTR_ERR_OR_ZERO(void *ptr) { 33 | if (IS_ERR(ptr)) { 34 | return PTR_ERR(ptr); 35 | } 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /src/event.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2019, Yuxuan Shui 3 | 4 | #include 5 | 6 | #include "common.h" 7 | 8 | void ev_handle(session_t *ps, xcb_generic_event_t *ev); 9 | -------------------------------------------------------------------------------- /src/file_watch.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #ifdef HAS_INOTIFY 4 | #include 5 | #elif HAS_KQUEUE 6 | // clang-format off 7 | #include 8 | // clang-format on 9 | #include 10 | #undef EV_ERROR // Avoid clashing with libev's EV_ERROR 11 | #include // For O_RDONLY 12 | #include // For struct timespec 13 | #include // For open 14 | #endif 15 | 16 | #include 17 | #include 18 | 19 | #include "file_watch.h" 20 | #include "list.h" 21 | #include "log.h" 22 | #include "utils.h" 23 | 24 | struct watched_file { 25 | int wd; 26 | void *ud; 27 | file_watch_cb_t cb; 28 | 29 | UT_hash_handle hh; 30 | }; 31 | 32 | struct file_watch_registry { 33 | struct ev_io w; 34 | 35 | struct watched_file *reg; 36 | }; 37 | 38 | static void file_watch_ev_cb(EV_P attr_unused, struct ev_io *w, int revent attr_unused) { 39 | auto fwr = (struct file_watch_registry *)w; 40 | 41 | while (true) { 42 | int wd = -1; 43 | #ifdef HAS_INOTIFY 44 | struct inotify_event inotify_event; 45 | auto ret = read(w->fd, &inotify_event, sizeof(struct inotify_event)); 46 | if (ret < 0) { 47 | if (errno != EAGAIN) { 48 | log_error_errno("Failed to read from inotify fd"); 49 | } 50 | break; 51 | } 52 | wd = inotify_event.wd; 53 | #elif HAS_KQUEUE 54 | struct kevent ev; 55 | struct timespec timeout = {0}; 56 | int ret = kevent(fwr->w.fd, NULL, 0, &ev, 1, &timeout); 57 | if (ret <= 0) { 58 | if (ret < 0) { 59 | log_error_errno("Failed to get kevent"); 60 | } 61 | break; 62 | } 63 | wd = (int)ev.ident; 64 | #else 65 | assert(false); 66 | #endif 67 | 68 | struct watched_file *wf = NULL; 69 | HASH_FIND_INT(fwr->reg, &wd, wf); 70 | if (!wf) { 71 | log_warn("Got notification for a file I didn't watch."); 72 | continue; 73 | } 74 | wf->cb(wf->ud); 75 | } 76 | } 77 | 78 | void *file_watch_init(EV_P) { 79 | log_debug("Starting watching for file changes"); 80 | int fd = -1; 81 | #ifdef HAS_INOTIFY 82 | fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 83 | if (fd < 0) { 84 | log_error_errno("inotify_init1 failed"); 85 | return NULL; 86 | } 87 | #elif HAS_KQUEUE 88 | fd = kqueue(); 89 | if (fd < 0) { 90 | log_error_errno("Failed to create kqueue"); 91 | return NULL; 92 | } 93 | #else 94 | log_info("No file watching support found on the host system."); 95 | return NULL; 96 | #endif 97 | auto fwr = ccalloc(1, struct file_watch_registry); 98 | ev_io_init(&fwr->w, file_watch_ev_cb, fd, EV_READ); 99 | ev_io_start(EV_A_ & fwr->w); 100 | 101 | return fwr; 102 | } 103 | 104 | void file_watch_destroy(EV_P_ void *_fwr) { 105 | log_debug("Stopping watching for file changes"); 106 | auto fwr = (struct file_watch_registry *)_fwr; 107 | struct watched_file *i, *tmp; 108 | 109 | HASH_ITER(hh, fwr->reg, i, tmp) { 110 | HASH_DEL(fwr->reg, i); 111 | #ifdef HAS_KQUEUE 112 | // kqueue watch descriptors are file descriptors of 113 | // the files we are watching, so we need to close 114 | // them 115 | close(i->wd); 116 | #endif 117 | free(i); 118 | } 119 | 120 | ev_io_stop(EV_A_ & fwr->w); 121 | close(fwr->w.fd); 122 | free(fwr); 123 | } 124 | 125 | bool file_watch_add(void *_fwr, const char *filename, file_watch_cb_t cb, void *ud) { 126 | log_debug("Adding \"%s\" to watched files", filename); 127 | auto fwr = (struct file_watch_registry *)_fwr; 128 | int wd = -1; 129 | 130 | struct stat statbuf; 131 | int ret = stat(filename, &statbuf); 132 | if (ret < 0) { 133 | log_error_errno("Failed to retrieve information about file \"%s\"", filename); 134 | return false; 135 | } 136 | if (!S_ISREG(statbuf.st_mode)) { 137 | log_info("\"%s\" is not a regular file, not watching it.", filename); 138 | return false; 139 | } 140 | 141 | #ifdef HAS_INOTIFY 142 | wd = inotify_add_watch(fwr->w.fd, filename, 143 | IN_CLOSE_WRITE | IN_MOVE_SELF | IN_DELETE_SELF); 144 | if (wd < 0) { 145 | log_error_errno("Failed to watch file \"%s\"", filename); 146 | return false; 147 | } 148 | #elif HAS_KQUEUE 149 | wd = open(filename, O_RDONLY); 150 | if (wd < 0) { 151 | log_error_errno("Cannot open file \"%s\" for watching", filename); 152 | return false; 153 | } 154 | 155 | uint32_t fflags = NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE | NOTE_ATTRIB; 156 | // NOTE_CLOSE_WRITE is relatively new, so we cannot just use it 157 | #ifdef NOTE_CLOSE_WRITE 158 | fflags |= NOTE_CLOSE_WRITE; 159 | #else 160 | // NOTE_WRITE will receive notification more frequent than necessary, so is less 161 | // preferable 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 | } 60 | return exp(-0.5 * (x * x + y * y) / (r * r)) / (2 * M_PI * r * r); 61 | } 62 | 63 | conv *gaussian_kernel(double r, int size) { 64 | conv *c; 65 | int center = size / 2; 66 | double t; 67 | assert(size % 2 == 1); 68 | 69 | c = cvalloc(sizeof(conv) + (size_t)(size * size) * sizeof(double)); 70 | c->w = c->h = size; 71 | c->rsum = NULL; 72 | t = 0.0; 73 | 74 | for (int y = 0; y < size; y++) { 75 | for (int x = 0; x < size; x++) { 76 | double g = gaussian(r, x - center, y - center); 77 | t += g; 78 | c->data[y * size + x] = g; 79 | } 80 | } 81 | 82 | for (int y = 0; y < size; y++) { 83 | for (int x = 0; x < size; x++) { 84 | c->data[y * size + x] /= t; 85 | } 86 | } 87 | 88 | return c; 89 | } 90 | 91 | /// Estimate the element of the sum of the first row in a gaussian kernel with standard 92 | /// deviation `r` and size `size`, 93 | static inline double estimate_first_row_sum(double size, double r) { 94 | // `factor` is integral of gaussian from -size to size 95 | double factor = erf(size / r / sqrt(2)); 96 | // `a` is gaussian at (size, 0) 97 | double a = exp(-0.5 * size * size / (r * r)) / sqrt(2 * M_PI) / r; 98 | // The sum of the whole kernel is normalized to 1, i.e. each element is divided by 99 | // factor squared. So the sum of the first row is a * factor / factor^2 = a / 100 | // factor 101 | return a / factor; 102 | } 103 | 104 | /// Pick a suitable gaussian kernel standard deviation for a given kernel size. The 105 | /// returned radius is the maximum possible radius (<= size*2) that satisfies no sum of 106 | /// the rows in the kernel are less than `row_limit` (up to certain precision). 107 | double gaussian_kernel_std_for_size(double size, double row_limit) { 108 | assert(size > 0); 109 | if (row_limit >= 1.0 / 2.0 / size) { 110 | return size * 2; 111 | } 112 | double l = 0, r = size * 2; 113 | while (r - l > 1e-2) { 114 | double mid = (l + r) / 2.0; 115 | double vmid = estimate_first_row_sum(size, mid); 116 | if (vmid > row_limit) { 117 | r = mid; 118 | } else { 119 | l = mid; 120 | } 121 | } 122 | return (l + r) / 2.0; 123 | } 124 | 125 | /// Create a gaussian kernel with auto detected standard deviation. The choosen standard 126 | /// deviation tries to make sure the outer most pixels of the shadow are completely 127 | /// transparent, so the transition from shadow to the background is smooth. 128 | /// 129 | /// @param[in] shadow_radius the radius of the shadow 130 | conv *gaussian_kernel_autodetect_deviation(double shadow_radius) { 131 | assert(shadow_radius >= 0); 132 | int size = (int)(shadow_radius * 2 + 1); 133 | 134 | if (shadow_radius == 0) { 135 | return gaussian_kernel(0, size); 136 | } 137 | double std = gaussian_kernel_std_for_size(shadow_radius, 0.5 / 256.0); 138 | return gaussian_kernel(std, size); 139 | } 140 | 141 | /// preprocess kernels to make shadow generation faster 142 | /// shadow_sum[x*d+y] is the sum of the kernel from (0, 0) to (x, y), inclusive 143 | void sum_kernel_preprocess(conv *map) { 144 | if (map->rsum) { 145 | free(map->rsum); 146 | } 147 | 148 | auto sum = map->rsum = ccalloc(map->w * map->h, double); 149 | sum[0] = map->data[0]; 150 | 151 | for (int x = 1; x < map->w; x++) { 152 | sum[x] = sum[x - 1] + map->data[x]; 153 | } 154 | 155 | const int d = map->w; 156 | for (int y = 1; y < map->h; y++) { 157 | sum[y * d] = sum[(y - 1) * d] + map->data[y * d]; 158 | for (int x = 1; x < map->w; x++) { 159 | double tmp = sum[(y - 1) * d + x] + sum[y * d + x - 1] - 160 | sum[(y - 1) * d + x - 1]; 161 | sum[y * d + x] = tmp + map->data[y * d + x]; 162 | } 163 | } 164 | } 165 | 166 | // vim: set noet sw=8 ts=8 : 167 | -------------------------------------------------------------------------------- /src/kernel.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | #include "compiler.h" 7 | 8 | /// Code for generating convolution kernels 9 | 10 | typedef struct conv { 11 | int w, h; 12 | double *rsum; 13 | double data[]; 14 | } conv; 15 | 16 | /// Calculate the sum of a rectangle part of the convolution kernel 17 | /// the rectangle is defined by top left (x, y), and a size (width x height) 18 | double attr_pure sum_kernel(const conv *map, int x, int y, int width, int height); 19 | double attr_pure sum_kernel_normalized(const conv *map, int x, int y, int width, int height); 20 | 21 | /// Create a kernel with gaussian distribution with standard deviation `r`, and size 22 | /// `size`. 23 | conv *gaussian_kernel(double r, int size); 24 | 25 | /// Estimate the best standard deviation for a give kernel size. 26 | double gaussian_kernel_std_for_size(double size, double row_limit); 27 | 28 | /// Create a gaussian kernel with auto detected standard deviation. The choosen standard 29 | /// deviation tries to make sure the outer most pixels of the shadow are completely 30 | /// transparent. 31 | /// 32 | /// @param[in] shadow_radius the radius of the shadow 33 | conv *gaussian_kernel_autodetect_deviation(double shadow_radius); 34 | 35 | /// preprocess kernels to make shadow generation faster 36 | /// shadow_sum[x*d+y] is the sum of the kernel from (0, 0) to (x, y), inclusive 37 | void sum_kernel_preprocess(conv *map); 38 | 39 | static inline void free_conv(conv *k) { 40 | free(k->rsum); 41 | free(k); 42 | } 43 | -------------------------------------------------------------------------------- /src/list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include "utils.h" 6 | 7 | struct list_node { 8 | struct list_node *next, *prev; 9 | }; 10 | 11 | #define list_entry(ptr, type, node) container_of(ptr, type, node) 12 | #define list_next_entry(ptr, node) list_entry((ptr)->node.next, __typeof__(*(ptr)), node) 13 | #define list_prev_entry(ptr, node) list_entry((ptr)->node.prev, __typeof__(*(ptr)), node) 14 | 15 | /// Insert a new node between two adjacent nodes in the list 16 | static inline void __list_insert_between(struct list_node *prev, struct list_node *next, 17 | struct list_node *new_) { 18 | new_->prev = prev; 19 | new_->next = next; 20 | next->prev = new_; 21 | prev->next = new_; 22 | } 23 | 24 | /// Insert a new node after `curr` 25 | static inline void list_insert_after(struct list_node *curr, struct list_node *new_) { 26 | __list_insert_between(curr, curr->next, new_); 27 | } 28 | 29 | /// Insert a new node before `curr` 30 | static inline void list_insert_before(struct list_node *curr, struct list_node *new_) { 31 | __list_insert_between(curr->prev, curr, new_); 32 | } 33 | 34 | /// Link two nodes in the list, so `next` becomes the successor node of `prev` 35 | static inline void __list_link(struct list_node *prev, struct list_node *next) { 36 | next->prev = prev; 37 | prev->next = next; 38 | } 39 | 40 | /// Remove a node from the list 41 | static inline void list_remove(struct list_node *to_remove) { 42 | __list_link(to_remove->prev, to_remove->next); 43 | to_remove->prev = (void *)-1; 44 | to_remove->next = (void *)-2; 45 | } 46 | 47 | /// Move `to_move` so that it's before `new_next` 48 | static inline void list_move_before(struct list_node *to_move, struct list_node *new_next) { 49 | list_remove(to_move); 50 | list_insert_before(new_next, to_move); 51 | } 52 | 53 | /// Move `to_move` so that it's after `new_prev` 54 | static inline void list_move_after(struct list_node *to_move, struct list_node *new_prev) { 55 | list_remove(to_move); 56 | list_insert_after(new_prev, to_move); 57 | } 58 | 59 | /// Initialize a list node that's intended to be the head node 60 | static inline void list_init_head(struct list_node *head) { 61 | head->next = head->prev = head; 62 | } 63 | 64 | /// Replace list node `old` with `n` 65 | static inline void list_replace(struct list_node *old, struct list_node *n) { 66 | __list_insert_between(old->prev, old->next, n); 67 | old->prev = (void *)-1; 68 | old->next = (void *)-2; 69 | } 70 | 71 | /// Return true if head is the only node in the list. Under usual circumstances this means 72 | /// the list is empty 73 | static inline bool list_is_empty(const struct list_node *head) { 74 | return head->prev == head; 75 | } 76 | 77 | /// Return true if `to_check` is the first node in list headed by `head` 78 | static inline bool 79 | list_node_is_first(const struct list_node *head, const struct list_node *to_check) { 80 | return head->next == to_check; 81 | } 82 | 83 | /// Return true if `to_check` is the last node in list headed by `head` 84 | static inline bool 85 | list_node_is_last(const struct list_node *head, const struct list_node *to_check) { 86 | return head->prev == to_check; 87 | } 88 | 89 | #define list_foreach(type, i, head, member) \ 90 | for (type *i = list_entry((head)->next, type, member); &i->member != (head); \ 91 | i = list_next_entry(i, member)) 92 | 93 | /// Like list_for_each, but it's safe to remove the current list node from the list 94 | #define list_foreach_safe(type, i, head, member) \ 95 | for (type *i = list_entry((head)->next, type, member), \ 96 | *__tmp = list_next_entry(i, member); \ 97 | &i->member != (head); i = __tmp, __tmp = list_next_entry(i, member)) 98 | -------------------------------------------------------------------------------- /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 | /// Very noisy debug messages, many lines per frame. 13 | LOG_LEVEL_TRACE = 0, 14 | /// Frequent debug messages, a few lines per frame. 15 | LOG_LEVEL_VERBOSE, 16 | /// Less frequent debug messages. 17 | LOG_LEVEL_DEBUG, 18 | /// Informational messages. 19 | LOG_LEVEL_INFO, 20 | /// Warnings. 21 | LOG_LEVEL_WARN, 22 | /// Errors. 23 | LOG_LEVEL_ERROR, 24 | /// Fatal errors. 25 | LOG_LEVEL_FATAL, 26 | }; 27 | 28 | #define LOG_UNLIKELY(level, x, ...) \ 29 | do { \ 30 | if (unlikely(LOG_LEVEL_##level >= log_get_level_tls())) { \ 31 | log_printf(tls_logger, LOG_LEVEL_##level, __func__, x, ##__VA_ARGS__); \ 32 | } \ 33 | } while (0) 34 | 35 | #define LOG(level, x, ...) \ 36 | do { \ 37 | if (LOG_LEVEL_##level >= log_get_level_tls()) { \ 38 | log_printf(tls_logger, LOG_LEVEL_##level, __func__, x, ##__VA_ARGS__); \ 39 | } \ 40 | } while (0) 41 | #define log_trace(x, ...) LOG_UNLIKELY(TRACE, x, ##__VA_ARGS__) 42 | #define log_verbose(x, ...) LOG_UNLIKELY(VERBOSE, x, ##__VA_ARGS__) 43 | #define log_debug(x, ...) LOG_UNLIKELY(DEBUG, x, ##__VA_ARGS__) 44 | #define log_info(x, ...) LOG(INFO, x, ##__VA_ARGS__) 45 | #define log_warn(x, ...) LOG(WARN, x, ##__VA_ARGS__) 46 | #define log_error(x, ...) LOG(ERROR, x, ##__VA_ARGS__) 47 | #define log_fatal(x, ...) LOG(FATAL, x, ##__VA_ARGS__) 48 | 49 | #define log_error_errno(x, ...) LOG(ERROR, x ": %s", ##__VA_ARGS__, strerror(errno)) 50 | 51 | struct log; 52 | struct log_target; 53 | 54 | attr_printf(4, 5) void log_printf(struct log *, int level, const char *func, 55 | const char *fmt, ...); 56 | 57 | attr_malloc struct log *log_new(void); 58 | /// Destroy a log struct and every log target added to it 59 | attr_nonnull_all void log_destroy(struct log *); 60 | attr_nonnull(1) void log_set_level(struct log *l, int level); 61 | attr_pure enum log_level log_get_level(const struct log *l); 62 | attr_nonnull_all void log_add_target(struct log *, struct log_target *); 63 | attr_pure enum log_level string_to_log_level(const char *); 64 | /// Remove a previously added log target for a log struct, and destroy it. If the log 65 | /// target was never added, nothing happens. 66 | void log_remove_target(struct log *l, struct log_target *tgt); 67 | 68 | extern thread_local struct log *tls_logger; 69 | 70 | /// Create a thread local logger 71 | static inline void log_init_tls(void) { 72 | tls_logger = log_new(); 73 | } 74 | /// Set thread local logger log level 75 | static inline void log_set_level_tls(int level) { 76 | assert(tls_logger); 77 | log_set_level(tls_logger, level); 78 | } 79 | static inline attr_nonnull_all void log_add_target_tls(struct log_target *tgt) { 80 | assert(tls_logger); 81 | log_add_target(tls_logger, tgt); 82 | } 83 | 84 | static inline attr_nonnull_all void log_remove_target_tls(struct log_target *tgt) { 85 | assert(tls_logger); 86 | log_remove_target(tls_logger, tgt); 87 | } 88 | 89 | static inline attr_pure enum log_level log_get_level_tls(void) { 90 | assert(tls_logger); 91 | return log_get_level(tls_logger); 92 | } 93 | 94 | static inline void log_deinit_tls(void) { 95 | assert(tls_logger); 96 | log_destroy(tls_logger); 97 | tls_logger = NULL; 98 | } 99 | 100 | attr_malloc struct log_target *stderr_logger_new(void); 101 | attr_malloc struct log_target *file_logger_new(const char *file); 102 | attr_malloc struct log_target *null_logger_new(void); 103 | attr_malloc struct log_target *gl_string_marker_logger_new(void); 104 | 105 | // vim: set noet sw=8 ts=8: 106 | -------------------------------------------------------------------------------- /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', 'statistics.c', 13 | 'vblank.c') ] 14 | picom_inc = include_directories('.') 15 | 16 | cflags = [] 17 | 18 | required_xcb_packages = [ 19 | 'xcb', 'xcb-composite', 'xcb-damage', 'xcb-dpms', 'xcb-glx', 'xcb-present', 20 | 'xcb-randr', 'xcb-render', 'xcb-shape', 'xcb-sync', 'xcb-xfixes' 21 | ] 22 | 23 | # Some XCB packages are here because their versioning differs (see check below). 24 | required_packages = [ 25 | 'pixman-1', 'x11', 'x11-xcb', 'xcb-image', 'xcb-renderutil', 'xcb-util', 26 | 'xext', 'threads', 27 | ] 28 | 29 | foreach i : required_packages 30 | base_deps += [dependency(i, required: true)] 31 | endforeach 32 | 33 | foreach i : required_xcb_packages 34 | base_deps += [dependency(i, version: '>=1.12.0', required: true)] 35 | endforeach 36 | 37 | if not cc.has_header('uthash.h') 38 | error('Dependency uthash not found') 39 | endif 40 | 41 | deps = [] 42 | 43 | if get_option('config_file') 44 | deps += [dependency('libconfig', version: '>=1.4', required: true)] 45 | 46 | cflags += ['-DCONFIG_LIBCONFIG'] 47 | srcs += [ 'config_libconfig.c' ] 48 | endif 49 | if get_option('regex') 50 | pcre = dependency('libpcre2-8', required: true) 51 | cflags += ['-DCONFIG_REGEX_PCRE'] 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'] 62 | deps += [dependency('epoxy', 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 "factor_center" in blur GLSL program. 40 | GLint unifm_factor_center; 41 | } glx_blur_pass_t; 42 | 43 | typedef struct { 44 | /// Fragment shader for rounded corners. 45 | GLuint frag_shader; 46 | /// GLSL program for rounded corners. 47 | GLuint prog; 48 | /// Location of uniform "radius" in rounded-corners GLSL program. 49 | GLint unifm_radius; 50 | /// Location of uniform "texcoord" in rounded-corners GLSL program. 51 | GLint unifm_texcoord; 52 | /// Location of uniform "texsize" in rounded-corners GLSL program. 53 | GLint unifm_texsize; 54 | /// Location of uniform "borderw" in rounded-corners GLSL program. 55 | GLint unifm_borderw; 56 | /// Location of uniform "borderc" in rounded-corners GLSL program. 57 | GLint unifm_borderc; 58 | /// Location of uniform "resolution" in rounded-corners GLSL program. 59 | GLint unifm_resolution; 60 | /// Location of uniform "texture_scr" in rounded-corners GLSL program. 61 | GLint unifm_tex_scr; 62 | 63 | } glx_round_pass_t; 64 | 65 | /// Structure containing GLX-dependent data for a session. 66 | typedef struct glx_session { 67 | // === OpenGL related === 68 | /// GLX context. 69 | GLXContext context; 70 | /// Whether we have GL_ARB_texture_non_power_of_two. 71 | bool has_texture_non_power_of_two; 72 | /// Current GLX Z value. 73 | int z; 74 | glx_blur_pass_t *blur_passes; 75 | glx_round_pass_t *round_passes; 76 | } glx_session_t; 77 | 78 | /// @brief Wrapper of a bound GLX texture. 79 | typedef struct _glx_texture { 80 | GLuint texture; 81 | GLXPixmap glpixmap; 82 | xcb_pixmap_t pixmap; 83 | GLenum target; 84 | int width; 85 | int height; 86 | bool y_inverted; 87 | } glx_texture_t; 88 | 89 | #define CGLX_SESSION_INIT \ 90 | { .context = NULL } 91 | 92 | bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z, 93 | GLfloat factor, const region_t *reg_tgt); 94 | 95 | bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, int dy, 96 | int width, int height, int z, double opacity, bool argb, bool neg, 97 | const region_t *reg_tgt, const glx_prog_main_t *pprogram); 98 | 99 | bool glx_init(session_t *ps, bool need_render); 100 | 101 | void glx_destroy(session_t *ps); 102 | 103 | void glx_on_root_change(session_t *ps); 104 | 105 | bool glx_init_blur(session_t *ps); 106 | 107 | bool glx_init_rounded_corners(session_t *ps); 108 | 109 | #ifdef CONFIG_OPENGL 110 | bool glx_load_prog_main(const char *vshader_str, const char *fshader_str, 111 | glx_prog_main_t *pprogram); 112 | #endif 113 | 114 | bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, int width, 115 | int height, bool repeat, const struct glx_fbconfig_info *); 116 | 117 | void glx_release_pixmap(session_t *ps, glx_texture_t *ptex); 118 | 119 | bool glx_bind_texture(session_t *ps, glx_texture_t **pptex, int x, int y, int width, int height); 120 | 121 | void glx_paint_pre(session_t *ps, region_t *preg) attr_nonnull(1, 2); 122 | 123 | /** 124 | * Check if a texture is bound, or is bound to the given pixmap. 125 | */ 126 | static inline bool glx_tex_bound(const glx_texture_t *ptex, xcb_pixmap_t pixmap) { 127 | return ptex && ptex->glpixmap && ptex->texture && (!pixmap || pixmap == ptex->pixmap); 128 | } 129 | 130 | void glx_set_clip(session_t *ps, const region_t *reg); 131 | 132 | bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, 133 | GLfloat factor_center, const region_t *reg_tgt, glx_blur_cache_t *pbc); 134 | 135 | bool glx_round_corners_dst(session_t *ps, struct managed_win *w, 136 | const glx_texture_t *ptex, int dx, int dy, int width, 137 | int height, float z, float cr, const region_t *reg_tgt); 138 | 139 | GLuint glx_create_shader(GLenum shader_type, const char *shader_str); 140 | 141 | GLuint glx_create_program(const GLuint *const shaders, int nshaders); 142 | 143 | GLuint glx_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str); 144 | 145 | unsigned char *glx_take_screenshot(session_t *ps, int *out_length); 146 | 147 | /** 148 | * Check if there's a GLX context. 149 | */ 150 | static inline bool glx_has_context(session_t *ps) { 151 | return ps->psglx && ps->psglx->context; 152 | } 153 | 154 | /** 155 | * Ensure we have a GLX context. 156 | */ 157 | static inline bool ensure_glx_context(session_t *ps) { 158 | // Create GLX context 159 | if (!glx_has_context(ps)) { 160 | glx_init(ps, false); 161 | } 162 | 163 | return glx_has_context(ps); 164 | } 165 | 166 | /** 167 | * Free a GLX texture. 168 | */ 169 | static inline void free_texture_r(session_t *ps attr_unused, GLuint *ptexture) { 170 | if (*ptexture) { 171 | assert(glx_has_context(ps)); 172 | glDeleteTextures(1, ptexture); 173 | *ptexture = 0; 174 | } 175 | } 176 | 177 | /** 178 | * Free a GLX Framebuffer object. 179 | */ 180 | static inline void free_glx_fbo(GLuint *pfbo) { 181 | if (*pfbo) { 182 | glDeleteFramebuffers(1, pfbo); 183 | *pfbo = 0; 184 | } 185 | assert(!*pfbo); 186 | } 187 | 188 | /** 189 | * Free data in glx_blur_cache_t on resize. 190 | */ 191 | static inline void free_glx_bc_resize(session_t *ps, glx_blur_cache_t *pbc) { 192 | free_texture_r(ps, &pbc->textures[0]); 193 | free_texture_r(ps, &pbc->textures[1]); 194 | pbc->width = 0; 195 | pbc->height = 0; 196 | } 197 | 198 | /** 199 | * Free a glx_blur_cache_t 200 | */ 201 | static inline void free_glx_bc(session_t *ps, glx_blur_cache_t *pbc) { 202 | free_glx_fbo(&pbc->fbo); 203 | free_glx_bc_resize(ps, pbc); 204 | } 205 | 206 | /** 207 | * Free a glx_texture_t. 208 | */ 209 | static inline void free_texture(session_t *ps, glx_texture_t **pptex) { 210 | glx_texture_t *ptex = *pptex; 211 | 212 | // Quit if there's nothing 213 | if (!ptex) { 214 | return; 215 | } 216 | 217 | glx_release_pixmap(ps, ptex); 218 | 219 | free_texture_r(ps, &ptex->texture); 220 | 221 | // Free structure itself 222 | free(ptex); 223 | *pptex = NULL; 224 | } 225 | 226 | /** 227 | * Free GLX part of paint_t. 228 | */ 229 | static inline void free_paint_glx(session_t *ps, paint_t *ppaint) { 230 | free_texture(ps, &ppaint->ptex); 231 | ppaint->fbcfg = (struct glx_fbconfig_info){0}; 232 | } 233 | 234 | /** 235 | * Free GLX part of win. 236 | */ 237 | static inline void free_win_res_glx(session_t *ps, struct managed_win *w) { 238 | free_paint_glx(ps, &w->paint); 239 | free_paint_glx(ps, &w->shadow_paint); 240 | #ifdef CONFIG_OPENGL 241 | free_glx_bc(ps, &w->glx_blur_cache); 242 | free_texture(ps, &w->glx_texture_bg); 243 | #endif 244 | } 245 | -------------------------------------------------------------------------------- /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 { 29 | ROOT_FLAGS_SCREEN_CHANGE = 1, // Received RandR screen change notify, we 30 | // use this to track refresh rate changes 31 | ROOT_FLAGS_CONFIGURED = 2 // Received configure notify on the root window 32 | }; 33 | 34 | // == Functions == 35 | // TODO(yshui) move static inline functions that are only used in picom.c, into picom.c 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 | void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce); 42 | 43 | void root_damaged(session_t *ps); 44 | 45 | void queue_redraw(session_t *ps); 46 | 47 | void discard_pending(session_t *ps, uint32_t sequence); 48 | 49 | void set_root_flags(session_t *ps, uint64_t flags); 50 | 51 | void quit(session_t *ps); 52 | 53 | xcb_window_t session_get_target_window(session_t *); 54 | 55 | uint8_t session_redirection_mode(session_t *ps); 56 | 57 | /** 58 | * Set a switch_t array of all unset wintypes to true. 59 | */ 60 | static inline void wintype_arr_enable_unset(switch_t arr[]) { 61 | wintype_t i; 62 | 63 | for (i = 0; i < NUM_WINTYPES; ++i) { 64 | if (UNSET == arr[i]) { 65 | arr[i] = ON; 66 | } 67 | } 68 | } 69 | 70 | /** 71 | * Check if a window ID exists in an array of window IDs. 72 | * 73 | * @param arr the array of window IDs 74 | * @param count amount of elements in the array 75 | * @param wid window ID to search for 76 | */ 77 | static inline bool array_wid_exists(const xcb_window_t *arr, int count, xcb_window_t wid) { 78 | while (count--) { 79 | if (arr[count] == wid) { 80 | return true; 81 | } 82 | } 83 | 84 | return false; 85 | } 86 | 87 | #ifndef CONFIG_OPENGL 88 | static inline void free_paint_glx(session_t *ps attr_unused, paint_t *p attr_unused) { 89 | } 90 | static inline void 91 | free_win_res_glx(session_t *ps attr_unused, struct managed_win *w attr_unused) { 92 | } 93 | #endif 94 | 95 | /** 96 | * Dump an drawable's info. 97 | */ 98 | static inline void dump_drawable(session_t *ps, xcb_drawable_t drawable) { 99 | auto r = xcb_get_geometry_reply(ps->c.c, xcb_get_geometry(ps->c.c, drawable), NULL); 100 | if (!r) { 101 | log_trace("Drawable %#010x: Failed", drawable); 102 | return; 103 | } 104 | log_trace("Drawable %#010x: x = %u, y = %u, wid = %u, hei = %d, b = %u, d = %u", 105 | drawable, r->x, r->y, r->width, r->height, r->border_width, r->depth); 106 | free(r); 107 | } 108 | -------------------------------------------------------------------------------- /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 | 32 | /// Convert one xcb rectangle to our rectangle type 33 | static inline rect_t from_x_rect(const xcb_rectangle_t *rect) { 34 | return (rect_t){ 35 | .x1 = rect->x, 36 | .y1 = rect->y, 37 | .x2 = rect->x + rect->width, 38 | .y2 = rect->y + rect->height, 39 | }; 40 | } 41 | 42 | /// Convert an array of xcb rectangles to our rectangle type 43 | /// Returning an array that needs to be freed 44 | static inline rect_t *from_x_rects(int nrects, const xcb_rectangle_t *rects) { 45 | rect_t *ret = ccalloc(nrects, rect_t); 46 | for (int i = 0; i < nrects; i++) { 47 | ret[i] = from_x_rect(rects + i); 48 | } 49 | return ret; 50 | } 51 | 52 | /** 53 | * Resize a region. 54 | */ 55 | static inline void _resize_region(const region_t *region, region_t *output, int dx, 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] = (rect_t){.x1 = x1, .x2 = x2, .y1 = y1, .y2 = y2}; 81 | ++nnewrects; 82 | } 83 | 84 | pixman_region32_fini(output); 85 | pixman_region32_init_rects(output, newrects, nnewrects); 86 | 87 | free(newrects); 88 | } 89 | 90 | static inline region_t resize_region(const region_t *region, int dx, int dy) { 91 | region_t ret; 92 | pixman_region32_init(&ret); 93 | _resize_region(region, &ret, dx, dy); 94 | return ret; 95 | } 96 | 97 | static inline void resize_region_in_place(region_t *region, int dx, int dy) { 98 | return _resize_region(region, region, dx, dy); 99 | } 100 | -------------------------------------------------------------------------------- /src/render.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #ifdef CONFIG_OPENGL 9 | #include "backend/gl/glx.h" 10 | #endif 11 | #include "region.h" 12 | 13 | typedef struct _glx_texture glx_texture_t; 14 | typedef struct glx_prog_main glx_prog_main_t; 15 | typedef struct session session_t; 16 | 17 | struct managed_win; 18 | 19 | typedef struct paint { 20 | xcb_pixmap_t pixmap; 21 | xcb_render_picture_t pict; 22 | glx_texture_t *ptex; 23 | #ifdef CONFIG_OPENGL 24 | struct glx_fbconfig_info fbcfg; 25 | #endif 26 | } paint_t; 27 | 28 | typedef struct clip { 29 | xcb_render_picture_t pict; 30 | int x; 31 | int y; 32 | } clip_t; 33 | 34 | void render(session_t *ps, int x, int y, int dx, int dy, int w, int h, int fullw, 35 | int fullh, double opacity, bool argb, bool neg, int cr, 36 | xcb_render_picture_t pict, glx_texture_t *ptex, const region_t *reg_paint, 37 | const glx_prog_main_t *pprogram, clip_t *clip); 38 | void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint); 39 | 40 | void paint_all(session_t *ps, struct managed_win *const t); 41 | 42 | void free_paint(session_t *ps, paint_t *ppaint); 43 | void free_root_tile(session_t *ps); 44 | 45 | bool init_render(session_t *ps); 46 | void deinit_render(session_t *ps); 47 | 48 | int maximum_buffer_age(session_t *); 49 | -------------------------------------------------------------------------------- /src/statistics.c: -------------------------------------------------------------------------------- 1 | //! Rendering statistics 2 | //! 3 | //! Tracks how long it takes to render a frame, for measuring performance, and for pacing 4 | //! the frames. 5 | 6 | #include "statistics.h" 7 | #include "log.h" 8 | #include "utils.h" 9 | 10 | void render_statistics_init(struct render_statistics *rs, int window_size) { 11 | *rs = (struct render_statistics){0}; 12 | 13 | rolling_window_init(&rs->render_times, window_size); 14 | rolling_quantile_init_with_tolerance(&rs->render_time_quantile, window_size, 15 | /* q */ 0.98, /* tolerance */ 0.01); 16 | } 17 | 18 | void render_statistics_add_vblank_time_sample(struct render_statistics *rs, int time_us) { 19 | auto sample_sd = sqrt(cumulative_mean_and_var_get_var(&rs->vblank_time_us)); 20 | auto current_estimate = render_statistics_get_vblank_time(rs); 21 | if (current_estimate != 0 && fabs((double)time_us - current_estimate) > sample_sd * 3) { 22 | // Deviated from the mean by more than 3 sigma (p < 0.003) 23 | log_debug("vblank time outlier: %d %f %f", time_us, rs->vblank_time_us.mean, 24 | cumulative_mean_and_var_get_var(&rs->vblank_time_us)); 25 | // An outlier sample, this could mean things like refresh rate changes, so 26 | // we reset the statistics. This could also be benign, but we like to be 27 | // cautious. 28 | cumulative_mean_and_var_init(&rs->vblank_time_us); 29 | } 30 | 31 | if (rs->vblank_time_us.mean != 0) { 32 | auto nframes_in_10_seconds = 33 | (unsigned int)(10. * 1000000. / rs->vblank_time_us.mean); 34 | if (rs->vblank_time_us.n > 20 && rs->vblank_time_us.n > nframes_in_10_seconds) { 35 | // We collected 10 seconds worth of samples, we assume the 36 | // estimated refresh rate is stable. We will still reset the 37 | // statistics if we get an outlier sample though, see above. 38 | return; 39 | } 40 | } 41 | cumulative_mean_and_var_update(&rs->vblank_time_us, time_us); 42 | } 43 | 44 | void render_statistics_add_render_time_sample(struct render_statistics *rs, int time_us) { 45 | int oldest; 46 | if (rolling_window_push_back(&rs->render_times, time_us, &oldest)) { 47 | rolling_quantile_pop_front(&rs->render_time_quantile, oldest); 48 | } 49 | 50 | rolling_quantile_push_back(&rs->render_time_quantile, time_us); 51 | } 52 | 53 | /// How much time budget we should give to the backend for rendering, in microseconds. 54 | unsigned int render_statistics_get_budget(struct render_statistics *rs) { 55 | if (rs->render_times.nelem < rs->render_times.window_size) { 56 | // No valid render time estimates yet. Assume maximum budget. 57 | return UINT_MAX; 58 | } 59 | 60 | // N-th percentile of render times, see render_statistics_init for N. 61 | auto render_time_percentile = 62 | rolling_quantile_estimate(&rs->render_time_quantile, &rs->render_times); 63 | return (unsigned int)render_time_percentile; 64 | } 65 | 66 | unsigned int render_statistics_get_vblank_time(struct render_statistics *rs) { 67 | if (rs->vblank_time_us.n <= 20 || rs->vblank_time_us.mean < 100) { 68 | // Not enough samples yet, or the vblank time is too short to be 69 | // meaningful. Assume maximum budget. 70 | return 0; 71 | } 72 | return (unsigned int)rs->vblank_time_us.mean; 73 | } 74 | 75 | void render_statistics_reset(struct render_statistics *rs) { 76 | rolling_window_reset(&rs->render_times); 77 | rolling_quantile_reset(&rs->render_time_quantile); 78 | rs->vblank_time_us = (struct cumulative_mean_and_var){0}; 79 | } 80 | 81 | void render_statistics_destroy(struct render_statistics *rs) { 82 | render_statistics_reset(rs); 83 | rolling_window_destroy(&rs->render_times); 84 | rolling_quantile_destroy(&rs->render_time_quantile); 85 | } 86 | -------------------------------------------------------------------------------- /src/statistics.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "utils.h" 4 | 5 | #define NTIERS (3) 6 | 7 | struct render_statistics { 8 | /// Rolling window of rendering times (in us) and the tiers they belong to. 9 | /// We keep track of the tiers because the vblank time estimate can change over 10 | /// time. 11 | struct rolling_window render_times; 12 | /// Estimate the 95-th percentile of rendering times 13 | struct rolling_quantile render_time_quantile; 14 | /// Time between each vblanks 15 | struct cumulative_mean_and_var vblank_time_us; 16 | }; 17 | 18 | void render_statistics_init(struct render_statistics *rs, int window_size); 19 | void render_statistics_reset(struct render_statistics *rs); 20 | void render_statistics_destroy(struct render_statistics *rs); 21 | 22 | void render_statistics_add_vblank_time_sample(struct render_statistics *rs, int time_us); 23 | void render_statistics_add_render_time_sample(struct render_statistics *rs, int time_us); 24 | 25 | /// How much time budget we should give to the backend for rendering, in microseconds. 26 | unsigned int render_statistics_get_budget(struct render_statistics *rs); 27 | 28 | /// Return the measured vblank interval in microseconds. Returns 0 if not enough 29 | /// samples have been collected yet. 30 | unsigned int render_statistics_get_vblank_time(struct render_statistics *rs); 31 | -------------------------------------------------------------------------------- /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 | 131 | const char *trim_both(const char *src, size_t *length) { 132 | size_t i = 0; 133 | while (isspace(src[i])) { 134 | i++; 135 | } 136 | size_t j = strlen(src) - 1; 137 | while (j > i && isspace(src[j])) { 138 | j--; 139 | } 140 | *length = j - i + 1; 141 | return src + i; 142 | } 143 | 144 | TEST_CASE(trim_both) { 145 | size_t length; 146 | const char *str = trim_both(" \t\n\r\f", &length); 147 | TEST_EQUAL(length, 0); 148 | TEST_EQUAL(*str, '\0'); 149 | 150 | str = trim_both(" asdfas ", &length); 151 | TEST_EQUAL(length, 6); 152 | TEST_STRNEQUAL(str, "asdfas", length); 153 | 154 | str = trim_both(" asdf asdf ", &length); 155 | TEST_EQUAL(length, 9); 156 | TEST_STRNEQUAL(str, "asdf asdf", length); 157 | } 158 | -------------------------------------------------------------------------------- /src/string_utils.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | #pragma once 4 | #include 5 | #include 6 | 7 | #include "compiler.h" 8 | 9 | #define mstrncmp(s1, s2) strncmp((s1), (s2), strlen(s1)) 10 | 11 | char *mstrjoin(const char *src1, const char *src2); 12 | char *mstrjoin3(const char *src1, const char *src2, const char *src3); 13 | void mstrextend(char **psrc1, const char *src2); 14 | const char *trim_both(const char *src, size_t *length); 15 | 16 | /// Parse a floating point number of form (+|-)?[0-9]*(\.[0-9]*) 17 | double strtod_simple(const char *, const char **); 18 | 19 | static inline int uitostr(unsigned int n, char *buf) { 20 | int ret = 0; 21 | unsigned int tmp = n; 22 | while (tmp > 0) { 23 | tmp /= 10; 24 | ret++; 25 | } 26 | 27 | if (ret == 0) { 28 | ret = 1; 29 | } 30 | 31 | int pos = ret; 32 | while (pos--) { 33 | buf[pos] = (char)(n % 10 + '0'); 34 | n /= 10; 35 | } 36 | return ret; 37 | } 38 | 39 | static inline const char *skip_space_const(const char *src) { 40 | if (!src) { 41 | return NULL; 42 | } 43 | while (*src && isspace((unsigned char)*src)) { 44 | src++; 45 | } 46 | return src; 47 | } 48 | 49 | static inline char *skip_space_mut(char *src) { 50 | if (!src) { 51 | return NULL; 52 | } 53 | while (*src && isspace((unsigned char)*src)) { 54 | src++; 55 | } 56 | return src; 57 | } 58 | 59 | #define skip_space(x) \ 60 | _Generic((x), char * : skip_space_mut, const char * : skip_space_const)(x) 61 | -------------------------------------------------------------------------------- /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 "test.h" 8 | #include "utils.h" 9 | 10 | /// Report allocation failure without allocating memory 11 | void report_allocation_failure(const char *func, const char *file, unsigned int line) { 12 | // Since memory allocation failed, we try to print this error message without any 13 | // memory allocation. Since logging framework allocates memory (and might even 14 | // have not been initialized yet), so we can't use it. 15 | char buf[11]; 16 | int llen = uitostr(line, buf); 17 | const char msg1[] = " has failed to allocate memory, "; 18 | const char msg2[] = ". Aborting...\n"; 19 | const struct iovec v[] = { 20 | {.iov_base = (void *)func, .iov_len = strlen(func)}, 21 | {.iov_base = "()", .iov_len = 2}, 22 | {.iov_base = (void *)msg1, .iov_len = sizeof(msg1) - 1}, 23 | {.iov_base = "at ", .iov_len = 3}, 24 | {.iov_base = (void *)file, .iov_len = strlen(file)}, 25 | {.iov_base = ":", .iov_len = 1}, 26 | {.iov_base = buf, .iov_len = (size_t)llen}, 27 | {.iov_base = (void *)msg2, .iov_len = sizeof(msg2) - 1}, 28 | }; 29 | 30 | ssize_t _ attr_unused = writev(STDERR_FILENO, v, ARR_SIZE(v)); 31 | abort(); 32 | 33 | unreachable(); 34 | } 35 | 36 | /// 37 | /// Calculates next closest power of two of 32bit integer n 38 | /// ref: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 39 | /// 40 | int next_power_of_two(int n) { 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 | void rolling_window_destroy(struct rolling_window *rw) { 52 | free(rw->elem); 53 | rw->elem = NULL; 54 | } 55 | 56 | void rolling_window_reset(struct rolling_window *rw) { 57 | rw->nelem = 0; 58 | rw->elem_head = 0; 59 | } 60 | 61 | void rolling_window_init(struct rolling_window *rw, int size) { 62 | rw->elem = ccalloc(size, int); 63 | rw->window_size = size; 64 | rolling_window_reset(rw); 65 | } 66 | 67 | int rolling_window_pop_front(struct rolling_window *rw) { 68 | assert(rw->nelem > 0); 69 | auto ret = rw->elem[rw->elem_head]; 70 | rw->elem_head = (rw->elem_head + 1) % rw->window_size; 71 | rw->nelem--; 72 | return ret; 73 | } 74 | 75 | bool rolling_window_push_back(struct rolling_window *rw, int val, int *front) { 76 | bool full = rw->nelem == rw->window_size; 77 | if (full) { 78 | *front = rolling_window_pop_front(rw); 79 | } 80 | rw->elem[(rw->elem_head + rw->nelem) % rw->window_size] = val; 81 | rw->nelem++; 82 | return full; 83 | } 84 | 85 | /// Track the maximum member of a FIFO queue of integers. Integers are pushed to the back 86 | /// and popped from the front, the maximum of the current members in the queue is 87 | /// tracked. 88 | struct rolling_max { 89 | /// A priority queue holding the indices of the maximum element candidates. 90 | /// The head of the queue is the index of the maximum element. 91 | /// The indices in the queue are the "original" indices. 92 | /// 93 | /// There are only `capacity` elements in `elem`, all previous elements are 94 | /// discarded. But the discarded elements' indices are not forgotten, that's why 95 | /// it's called the "original" indices. 96 | int *p; 97 | int p_head, np; 98 | /// The maximum number of in flight elements. 99 | int capacity; 100 | }; 101 | 102 | void rolling_max_destroy(struct rolling_max *rm) { 103 | free(rm->p); 104 | free(rm); 105 | } 106 | 107 | struct rolling_max *rolling_max_new(int capacity) { 108 | auto rm = ccalloc(1, struct rolling_max); 109 | if (!rm) { 110 | return NULL; 111 | } 112 | 113 | rm->p = ccalloc(capacity, int); 114 | if (!rm->p) { 115 | goto err; 116 | } 117 | rm->capacity = capacity; 118 | 119 | return rm; 120 | 121 | err: 122 | rolling_max_destroy(rm); 123 | return NULL; 124 | } 125 | 126 | void rolling_max_reset(struct rolling_max *rm) { 127 | rm->p_head = 0; 128 | rm->np = 0; 129 | } 130 | 131 | #define IDX(n) ((n) % rm->capacity) 132 | /// Remove the oldest element in the window. The caller must maintain the list of elements 133 | /// themselves, i.e. the behavior is undefined if `front` does not 1match the oldest 134 | /// element. 135 | void rolling_max_pop_front(struct rolling_max *rm, int front) { 136 | if (rm->p[rm->p_head] == front) { 137 | // rm->p.pop_front() 138 | rm->p_head = IDX(rm->p_head + 1); 139 | rm->np--; 140 | } 141 | } 142 | 143 | void rolling_max_push_back(struct rolling_max *rm, int val) { 144 | // Update the priority queue. 145 | // Remove all elements smaller than the new element from the queue. Because 146 | // the new element will become the maximum element before them, and since they 147 | // come before the new element, they will have been popped before the new 148 | // element, so they will never become the maximum element. 149 | while (rm->np) { 150 | int p_tail = IDX(rm->p_head + rm->np - 1); 151 | if (rm->p[p_tail] > val) { 152 | break; 153 | } 154 | // rm->p.pop_back() 155 | rm->np--; 156 | } 157 | // Add the new element to the end of the queue. 158 | // rm->p.push_back(rm->start_index + rm->nelem - 1) 159 | assert(rm->np < rm->capacity); 160 | rm->p[IDX(rm->p_head + rm->np)] = val; 161 | rm->np++; 162 | } 163 | #undef IDX 164 | 165 | int rolling_max_get_max(struct rolling_max *rm) { 166 | if (rm->np == 0) { 167 | return INT_MIN; 168 | } 169 | return rm->p[rm->p_head]; 170 | } 171 | 172 | TEST_CASE(rolling_max_test) { 173 | #define NELEM 15 174 | struct rolling_window queue; 175 | rolling_window_init(&queue, 3); 176 | auto rm = rolling_max_new(3); 177 | const int data[NELEM] = {1, 2, 3, 1, 4, 5, 2, 3, 6, 5, 4, 3, 2, 0, 0}; 178 | const int expected_max[NELEM] = {1, 2, 3, 3, 4, 5, 5, 5, 6, 6, 6, 5, 4, 3, 2}; 179 | int max[NELEM] = {0}; 180 | for (int i = 0; i < NELEM; i++) { 181 | int front; 182 | bool full = rolling_window_push_back(&queue, data[i], &front); 183 | if (full) { 184 | rolling_max_pop_front(rm, front); 185 | } 186 | rolling_max_push_back(rm, data[i]); 187 | max[i] = rolling_max_get_max(rm); 188 | } 189 | rolling_window_destroy(&queue); 190 | rolling_max_destroy(rm); 191 | TEST_TRUE(memcmp(max, expected_max, sizeof(max)) == 0); 192 | #undef NELEM 193 | } 194 | 195 | // Find the k-th smallest element in an array. 196 | int quickselect(int *elems, int nelem, int k) { 197 | int l = 0, r = nelem; // [l, r) is the range of candidates 198 | while (l != r) { 199 | int pivot = elems[l]; 200 | int i = l, j = r; 201 | while (i < j) { 202 | while (i < j && elems[--j] >= pivot) { 203 | } 204 | elems[i] = elems[j]; 205 | while (i < j && elems[++i] <= pivot) { 206 | } 207 | elems[j] = elems[i]; 208 | } 209 | elems[i] = pivot; 210 | 211 | if (i == k) { 212 | break; 213 | } 214 | 215 | if (i < k) { 216 | l = i + 1; 217 | } else { 218 | r = i; 219 | } 220 | } 221 | return elems[k]; 222 | } 223 | 224 | void rolling_quantile_init(struct rolling_quantile *rq, int capacity, int mink, int maxk) { 225 | *rq = (struct rolling_quantile){0}; 226 | rq->tmp_buffer = malloc(sizeof(int) * (size_t)capacity); 227 | rq->capacity = capacity; 228 | rq->min_target_rank = mink; 229 | rq->max_target_rank = maxk; 230 | } 231 | 232 | void rolling_quantile_init_with_tolerance(struct rolling_quantile *rq, int window_size, 233 | double target, double tolerance) { 234 | rolling_quantile_init(rq, window_size, (int)((target - tolerance) * window_size), 235 | (int)((target + tolerance) * window_size)); 236 | } 237 | 238 | void rolling_quantile_reset(struct rolling_quantile *rq) { 239 | rq->current_rank = 0; 240 | rq->estimate = 0; 241 | } 242 | 243 | void rolling_quantile_destroy(struct rolling_quantile *rq) { 244 | free(rq->tmp_buffer); 245 | } 246 | 247 | int rolling_quantile_estimate(struct rolling_quantile *rq, struct rolling_window *elements) { 248 | if (rq->current_rank < rq->min_target_rank || rq->current_rank > rq->max_target_rank) { 249 | if (elements->nelem != elements->window_size) { 250 | return INT_MIN; 251 | } 252 | // Re-estimate the quantile. 253 | assert(elements->nelem <= rq->capacity); 254 | rolling_window_copy_to_array(elements, rq->tmp_buffer); 255 | const int target_rank = 256 | rq->min_target_rank + (rq->max_target_rank - rq->min_target_rank) / 2; 257 | rq->estimate = quickselect(rq->tmp_buffer, elements->nelem, target_rank); 258 | rq->current_rank = target_rank; 259 | } 260 | return rq->estimate; 261 | } 262 | 263 | void rolling_quantile_push_back(struct rolling_quantile *rq, int x) { 264 | if (x <= rq->estimate) { 265 | rq->current_rank++; 266 | } 267 | } 268 | 269 | void rolling_quantile_pop_front(struct rolling_quantile *rq, int x) { 270 | if (x <= rq->estimate) { 271 | rq->current_rank--; 272 | } 273 | } 274 | 275 | // vim: set noet sw=8 ts=8 : 276 | -------------------------------------------------------------------------------- /src/vblank.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "config.h" 12 | #include "x.h" 13 | 14 | /// An object that schedule vblank events. 15 | struct vblank_scheduler; 16 | 17 | struct vblank_event { 18 | uint64_t msc; 19 | uint64_t ust; 20 | }; 21 | 22 | enum vblank_callback_action { 23 | /// The callback should be called again in the next vblank. 24 | VBLANK_CALLBACK_AGAIN, 25 | /// The callback is done and should not be called again. 26 | VBLANK_CALLBACK_DONE, 27 | }; 28 | 29 | typedef enum vblank_callback_action (*vblank_callback_t)(struct vblank_event *event, 30 | void *user_data); 31 | 32 | /// Schedule a vblank event. 33 | /// 34 | /// Schedule for `cb` to be called when the current vblank ends. If this is called 35 | /// from a callback function for the current vblank, the newly scheduled callback 36 | /// will be called in the next vblank. 37 | /// 38 | /// Returns whether the scheduling is successful. Scheduling can fail if there 39 | /// is not enough memory. 40 | bool vblank_scheduler_schedule(struct vblank_scheduler *self, vblank_callback_t cb, 41 | void *user_data); 42 | struct vblank_scheduler * 43 | vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c, 44 | xcb_window_t target_window, enum vblank_scheduler_type type); 45 | void vblank_scheduler_free(struct vblank_scheduler *); 46 | 47 | bool vblank_handle_x_events(struct vblank_scheduler *self); 48 | -------------------------------------------------------------------------------- /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 | 84 | return glxext.has_GLX_SGI_video_sync; 85 | } 86 | 87 | static bool vsync_opengl_oml_init(session_t *ps) { 88 | if (!ensure_glx_context(ps)) { 89 | return false; 90 | } 91 | 92 | return glxext.has_GLX_OML_sync_control; 93 | } 94 | 95 | static inline bool vsync_opengl_swc_swap_interval(session_t *ps, int interval) { 96 | if (glxext.has_GLX_MESA_swap_control) { 97 | return glXSwapIntervalMESA((uint)interval) == 0; 98 | } 99 | if (glxext.has_GLX_SGI_swap_control) { 100 | return glXSwapIntervalSGI(interval) == 0; 101 | } 102 | if (glxext.has_GLX_EXT_swap_control) { 103 | GLXDrawable d = glXGetCurrentDrawable(); 104 | if (d == None) { 105 | // We don't have a context?? 106 | return false; 107 | } 108 | glXSwapIntervalEXT(ps->c.dpy, glXGetCurrentDrawable(), interval); 109 | return true; 110 | } 111 | return false; 112 | } 113 | 114 | static bool vsync_opengl_swc_init(session_t *ps) { 115 | if (!bkend_use_glx(ps)) { 116 | log_error("OpenGL swap control requires the GLX backend."); 117 | return false; 118 | } 119 | 120 | if (!vsync_opengl_swc_swap_interval(ps, 1)) { 121 | log_error("Failed to load a swap control extension."); 122 | return false; 123 | } 124 | 125 | return true; 126 | } 127 | 128 | /** 129 | * Wait for next VSync, OpenGL method. 130 | */ 131 | static int vsync_opengl_wait(session_t *ps attr_unused) { 132 | unsigned vblank_count = 0; 133 | 134 | glXGetVideoSyncSGI(&vblank_count); 135 | glXWaitVideoSyncSGI(2, (vblank_count + 1) % 2, &vblank_count); 136 | return 0; 137 | } 138 | 139 | /** 140 | * Wait for next VSync, OpenGL OML method. 141 | * 142 | * https://mail.gnome.org/archives/clutter-list/2012-November/msg00031.html 143 | */ 144 | static int vsync_opengl_oml_wait(session_t *ps) { 145 | int64_t ust = 0, msc = 0, sbc = 0; 146 | 147 | glXGetSyncValuesOML(ps->c.dpy, ps->reg_win, &ust, &msc, &sbc); 148 | glXWaitForMscOML(ps->c.dpy, ps->reg_win, 0, 2, (msc + 1) % 2, &ust, &msc, &sbc); 149 | return 0; 150 | } 151 | #endif 152 | 153 | /** 154 | * Initialize current VSync method. 155 | */ 156 | bool vsync_init(session_t *ps) { 157 | #ifdef CONFIG_OPENGL 158 | if (bkend_use_glx(ps)) { 159 | // Mesa turns on swap control by default, undo that 160 | vsync_opengl_swc_swap_interval(ps, 0); 161 | } 162 | #endif 163 | #ifdef CONFIG_VSYNC_DRM 164 | log_warn("The DRM vsync method is deprecated, please don't enable it."); 165 | #endif 166 | 167 | if (!ps->o.vsync) { 168 | return true; 169 | } 170 | 171 | #ifdef CONFIG_OPENGL 172 | if (bkend_use_glx(ps)) { 173 | if (!vsync_opengl_swc_init(ps)) { 174 | return false; 175 | } 176 | ps->vsync_wait = NULL; // glXSwapBuffers will automatically wait 177 | // for vsync, we don't need to do anything. 178 | return true; 179 | } 180 | #endif 181 | 182 | // Oh no, we are not using glx backend. 183 | // Throwing things at wall. 184 | #ifdef CONFIG_OPENGL 185 | if (vsync_opengl_oml_init(ps)) { 186 | log_info("Using the opengl-oml vsync method"); 187 | ps->vsync_wait = vsync_opengl_oml_wait; 188 | return true; 189 | } 190 | 191 | if (vsync_opengl_init(ps)) { 192 | log_info("Using the opengl vsync method"); 193 | ps->vsync_wait = vsync_opengl_wait; 194 | return true; 195 | } 196 | #endif 197 | 198 | #ifdef CONFIG_VSYNC_DRM 199 | if (vsync_drm_init(ps)) { 200 | log_info("Using the drm vsync method"); 201 | ps->vsync_wait = vsync_drm_wait; 202 | return true; 203 | } 204 | #endif 205 | 206 | log_error("No supported vsync method found for this backend"); 207 | return false; 208 | } 209 | -------------------------------------------------------------------------------- /src/vsync.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | #include 4 | 5 | typedef struct session session_t; 6 | 7 | bool vsync_init(session_t *ps); 8 | -------------------------------------------------------------------------------- /src/win_defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | typedef enum { 5 | WINTYPE_UNKNOWN, 6 | WINTYPE_DESKTOP, 7 | WINTYPE_DOCK, 8 | WINTYPE_TOOLBAR, 9 | WINTYPE_MENU, 10 | WINTYPE_UTILITY, 11 | WINTYPE_SPLASH, 12 | WINTYPE_DIALOG, 13 | WINTYPE_NORMAL, 14 | WINTYPE_DROPDOWN_MENU, 15 | WINTYPE_POPUP_MENU, 16 | WINTYPE_TOOLTIP, 17 | WINTYPE_NOTIFICATION, 18 | WINTYPE_COMBO, 19 | WINTYPE_DND, 20 | NUM_WINTYPES 21 | } wintype_t; 22 | 23 | /// Enumeration type of window painting mode. 24 | typedef enum { 25 | WMODE_TRANS, // The window body is (potentially) transparent 26 | WMODE_FRAME_TRANS, // The window body is opaque, but the frame is not 27 | WMODE_SOLID, // The window is opaque including the frame 28 | } winmode_t; 29 | 30 | /// Transition table: 31 | /// (DESTROYED is when the win struct is destroyed and freed) 32 | /// ('o' means in all other cases) 33 | /// (Window is created in the UNMAPPED state) 34 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 35 | /// | |UNMAPPING|DESTROYING|MAPPING|FADING |UNMAPPED| MAPPED |DESTROYED| 36 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 37 | /// | UNMAPPING | o | Window |Window | - | Fading | - | - | 38 | /// | | |destroyed |mapped | |finished| | | 39 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 40 | /// | DESTROYING | - | o | - | - | - | - | Fading | 41 | /// | | | | | | | |finished | 42 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 43 | /// | MAPPING | Window | Window | o |Opacity| - | Fading | - | 44 | /// | |unmapped |destroyed | |change | |finished| | 45 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 46 | /// | FADING | Window | Window | - | o | - | Fading | - | 47 | /// | |unmapped |destroyed | | | |finished| | 48 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 49 | /// | UNMAPPED | - | - |Window | - | o | - | Window | 50 | /// | | | |mapped | | | |destroyed| 51 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 52 | /// | MAPPED | Window | Window | - |Opacity| - | o | - | 53 | /// | |unmapped |destroyed | |change | | | | 54 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ 55 | typedef enum { 56 | // The window is being faded out because it's unmapped. 57 | WSTATE_UNMAPPING, 58 | // The window is being faded out because it's destroyed, 59 | WSTATE_DESTROYING, 60 | // The window is being faded in 61 | WSTATE_MAPPING, 62 | // Window opacity is not at the target level 63 | WSTATE_FADING, 64 | // The window is mapped, no fading is in progress. 65 | WSTATE_MAPPED, 66 | // The window is unmapped, no fading is in progress. 67 | WSTATE_UNMAPPED, 68 | } winstate_t; 69 | 70 | enum win_flags { 71 | // Note: *_NONE flags are mostly redundant and meant for detecting logical errors 72 | // in the code 73 | 74 | /// pixmap is out of date, will be update in win_process_flags 75 | WIN_FLAGS_PIXMAP_STALE = 1, 76 | /// window does not have pixmap bound 77 | WIN_FLAGS_PIXMAP_NONE = 2, 78 | /// there was an error trying to bind the images 79 | WIN_FLAGS_IMAGE_ERROR = 4, 80 | /// shadow is out of date, will be updated in win_process_flags 81 | WIN_FLAGS_SHADOW_STALE = 8, 82 | /// shadow has not been generated 83 | WIN_FLAGS_SHADOW_NONE = 16, 84 | /// the client window needs to be updated 85 | WIN_FLAGS_CLIENT_STALE = 32, 86 | /// the window is mapped by X, we need to call map_win_start for it 87 | WIN_FLAGS_MAPPED = 64, 88 | /// this window has properties which needs to be updated 89 | WIN_FLAGS_PROPERTY_STALE = 128, 90 | // TODO(yshui) _maybe_ split SIZE_STALE into SIZE_STALE and SHAPE_STALE 91 | /// this window has an unhandled size/shape change 92 | WIN_FLAGS_SIZE_STALE = 256, 93 | /// this window has an unhandled position (i.e. x and y) change 94 | WIN_FLAGS_POSITION_STALE = 512, 95 | /// need better name for this, is set when some aspects of the window changed 96 | WIN_FLAGS_FACTOR_CHANGED = 1024, 97 | }; 98 | 99 | static const uint64_t WIN_FLAGS_IMAGES_STALE = 100 | WIN_FLAGS_PIXMAP_STALE | WIN_FLAGS_SHADOW_STALE; 101 | 102 | #define WIN_FLAGS_IMAGES_NONE (WIN_FLAGS_PIXMAP_NONE | WIN_FLAGS_SHADOW_NONE) 103 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/configs/clear_shadow_unredirected.conf: -------------------------------------------------------------------------------- 1 | shadow = true; 2 | shadow-exclude = [ 3 | "name = 'NoShadow'" 4 | ] 5 | unredir-if-possible = true; 6 | -------------------------------------------------------------------------------- /tests/configs/empty.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FT-Labs/picom/df4c6a3d9b11e14ed7f3142540babea4c775ddb1/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/configs/issue314.conf: -------------------------------------------------------------------------------- 1 | fading = true 2 | fade-in-step = 0.01 3 | fade-out-step = 0.01 4 | inactive-opacity = 0 5 | blur-background = true 6 | force-win-blend = true 7 | -------------------------------------------------------------------------------- /tests/configs/issue357.conf: -------------------------------------------------------------------------------- 1 | fading = true; 2 | fade-in-step = 1; 3 | fade-out-step = 0.01; 4 | -------------------------------------------------------------------------------- /tests/configs/issue394.conf: -------------------------------------------------------------------------------- 1 | fading = true; 2 | fade-in-step = 1; 3 | fade-out-step = 0.01; 4 | shadow = true; 5 | -------------------------------------------------------------------------------- /tests/configs/issue465.conf: -------------------------------------------------------------------------------- 1 | shadow = true; 2 | shadow-exclude = [ 3 | "focused != 1" 4 | ]; 5 | -------------------------------------------------------------------------------- /tests/configs/shader.frag: -------------------------------------------------------------------------------- 1 | vec4 window_shader() {} 2 | -------------------------------------------------------------------------------- /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 --dbus --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 | eval `dbus-launch --sh-syntax` 7 | 8 | ./run_one_test.sh $exe configs/empty.conf testcases/basic.py 9 | ./run_one_test.sh $exe configs/issue357.conf testcases/issue357.py 10 | ./run_one_test.sh $exe configs/issue239.conf testcases/issue239.py 11 | ./run_one_test.sh $exe configs/issue239_2.conf testcases/issue239_2.py 12 | ./run_one_test.sh $exe configs/issue239_3.conf testcases/issue239_3.py 13 | ./run_one_test.sh $exe configs/issue239_3.conf testcases/issue239_3_norefresh.py 14 | ./run_one_test.sh $exe configs/issue314.conf testcases/issue314.py 15 | ./run_one_test.sh $exe configs/issue314.conf testcases/issue314_2.py 16 | ./run_one_test.sh $exe configs/issue314.conf testcases/issue314_3.py 17 | ./run_one_test.sh $exe /dev/null testcases/issue299.py 18 | ./run_one_test.sh $exe configs/issue465.conf testcases/issue465.py 19 | ./run_one_test.sh $exe configs/clear_shadow_unredirected.conf testcases/clear_shadow_unredirected.py 20 | ./run_one_test.sh $exe configs/clear_shadow_unredirected.conf testcases/redirect_when_unmapped_window_has_shadow.py 21 | ./run_one_test.sh $exe configs/issue394.conf testcases/issue394.py 22 | ./run_one_test.sh $exe configs/issue239.conf testcases/issue525.py 23 | -------------------------------------------------------------------------------- /tests/testcases/basic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 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/clear_shadow_unredirected.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 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 | name = "_NET_WM_STATE" 15 | name_atom = conn.core.InternAtom(False, len(name), name).reply().atom 16 | atom = "ATOM" 17 | atom_atom = conn.core.InternAtom(False, len(atom), atom).reply().atom 18 | fs = "_NET_WM_STATE_FULLSCREEN" 19 | fs_atom = conn.core.InternAtom(False, len(fs), fs).reply().atom 20 | 21 | # making sure disabling shadow while screen is unredirected doesn't cause assertion failure 22 | wid = conn.generate_id() 23 | print("Window id is ", hex(wid)) 24 | 25 | # Create a window 26 | conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() 27 | 28 | # Set Window name so it does get a shadow 29 | set_window_name(conn, wid, "YesShadow") 30 | 31 | # Map the window 32 | print("mapping") 33 | conn.core.MapWindowChecked(wid).check() 34 | 35 | time.sleep(0.5) 36 | 37 | # Set fullscreen property, causing screen to be unredirected 38 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, name_atom, atom_atom, 32, 1, [fs_atom]).check() 39 | 40 | time.sleep(0.5) 41 | 42 | # Set the Window name so it loses its shadow 43 | print("set new name") 44 | set_window_name(conn, wid, "NoShadow") 45 | 46 | # Unmap the window 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/common.py: -------------------------------------------------------------------------------- 1 | import xcffib.xproto as xproto 2 | import xcffib.randr as randr 3 | import xcffib 4 | import time 5 | import random 6 | import string 7 | 8 | def to_atom(conn, string): 9 | return conn.core.InternAtom(False, len(string), string).reply().atom 10 | 11 | def set_window_name(conn, wid, name): 12 | prop_name = to_atom(conn, "_NET_WM_NAME") 13 | str_type = to_atom(conn, "UTF8_STRING") 14 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, prop_name, str_type, 8, len(name), name).check() 15 | prop_name = to_atom(conn, "WM_NAME") 16 | str_type = to_atom(conn, "STRING") 17 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, prop_name, str_type, 8, len(name), name).check() 18 | 19 | def set_window_state(conn, wid, state): 20 | prop_name = to_atom(conn, "WM_STATE") 21 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, prop_name, prop_name, 32, 2, [state, 0]).check() 22 | 23 | def set_window_class(conn, wid, name): 24 | if not isinstance(name, bytearray): 25 | name = name.encode() 26 | name = name+b"\0"+name+b"\0" 27 | prop_name = to_atom(conn, "WM_CLASS") 28 | str_type = to_atom(conn, "STRING") 29 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, prop_name, str_type, 8, len(name), name).check() 30 | 31 | def set_window_size_async(conn, wid, width, height): 32 | value_mask = xproto.ConfigWindow.Width | xproto.ConfigWindow.Height 33 | value_list = [width, height] 34 | return conn.core.ConfigureWindowChecked(wid, value_mask, value_list) 35 | 36 | def find_picom_window(conn): 37 | prop_name = to_atom(conn, "WM_NAME") 38 | setup = conn.get_setup() 39 | root = setup.roots[0].root 40 | windows = conn.core.QueryTree(root).reply() 41 | 42 | ext = xproto.xprotoExtension(conn) 43 | for w in windows.children: 44 | name = ext.GetProperty(False, w, prop_name, xproto.GetPropertyType.Any, 0, (2 ** 32) - 1).reply() 45 | if name.value.buf() == b"picom": 46 | return w 47 | 48 | def prepare_root_configure(conn): 49 | setup = conn.get_setup() 50 | root = setup.roots[0].root 51 | # Xorg sends root ConfigureNotify when we add a new mode to an output 52 | rr = conn(randr.key) 53 | name = ''.join([random.choice(string.ascii_letters + string.digits) for n in range(0, 32)]) 54 | mode_info = randr.ModeInfo.synthetic(id = 0, width = 1000, height = 1000, dot_clock = 0, 55 | hsync_start = 0, hsync_end = 0, htotal = 0, hskew = 0, vsync_start = 0, vsync_end = 0, 56 | vtotal = 0, name_len = len(name), mode_flags = 0) 57 | 58 | reply = rr.CreateMode(root, mode_info, len(name), name).reply() 59 | mode = reply.mode 60 | reply = rr.GetScreenResourcesCurrent(root).reply() 61 | # our xvfb is setup to only have 1 output 62 | output = reply.outputs[0] 63 | rr.AddOutputModeChecked(output, mode).check() 64 | return reply, mode, output 65 | 66 | def trigger_root_configure(conn, reply, mode, output): 67 | rr = conn(randr.key) 68 | return rr.SetCrtcConfig(reply.crtcs[0], reply.timestamp, reply.config_timestamp, 0, 0, mode, randr.Rotation.Rotate_0, 1, [output]) 69 | 70 | def find_32bit_visual(conn): 71 | setup = conn.get_setup() 72 | render = conn(xcffib.render.key) 73 | r = render.QueryPictFormats().reply() 74 | pictfmt_ids = set() 75 | for pictform in r.formats: 76 | if (pictform.depth == 32 and 77 | pictform.type == xcffib.render.PictType.Direct and 78 | pictform.direct.alpha_mask != 0): 79 | pictfmt_ids.add(pictform.id) 80 | print(pictfmt_ids) 81 | for screen in r.screens: 82 | for depth in screen.depths: 83 | for pv in depth.visuals: 84 | if pv.format in pictfmt_ids: 85 | return pv.visual 86 | -------------------------------------------------------------------------------- /tests/testcases/issue239.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 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 python3 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 python3 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 python3 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 | -------------------------------------------------------------------------------- /tests/testcases/issue299.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import xcffib.xproto as xproto 4 | import xcffib 5 | import time 6 | import os 7 | import subprocess 8 | import asyncio 9 | from dbus_next.aio import MessageBus 10 | from dbus_next.message import Message, MessageType 11 | from common import * 12 | 13 | display = os.environ["DISPLAY"].replace(":", "_") 14 | conn = xcffib.connect() 15 | setup = conn.get_setup() 16 | root = setup.roots[0].root 17 | visual = setup.roots[0].root_visual 18 | depth = setup.roots[0].root_depth 19 | x = xproto.xprotoExtension(conn) 20 | visual32 = find_32bit_visual(conn) 21 | 22 | async def get_client_win_async(wid): 23 | message = await bus.call(Message(destination='com.github.chjj.compton.'+display, 24 | path='/com/github/chjj/compton', 25 | interface='com.github.chjj.compton', 26 | member='win_get', 27 | signature='us', 28 | body=[wid, 'client_win'])) 29 | return message.body[0] 30 | 31 | def get_client_win(wid): 32 | return loop.run_until_complete(get_client_win_async(wid)) 33 | 34 | def wait(): 35 | time.sleep(0.5) 36 | 37 | def create_client_window(name): 38 | client_win = conn.generate_id() 39 | print("Window : ", hex(client_win)) 40 | conn.core.CreateWindowChecked(depth, client_win, root, 0, 0, 100, 100, 0, 41 | xproto.WindowClass.InputOutput, visual, 0, []).check() 42 | set_window_name(conn, client_win, "Test window "+name) 43 | set_window_class(conn, client_win, "Test windows") 44 | set_window_state(conn, client_win, 1) 45 | conn.core.MapWindowChecked(client_win).check() 46 | return client_win 47 | 48 | loop = asyncio.get_event_loop() 49 | bus = loop.run_until_complete(MessageBus().connect()) 50 | 51 | cmid = conn.generate_id() 52 | colormap = conn.core.CreateColormapChecked(xproto.ColormapAlloc._None, cmid, root, visual32).check() 53 | 54 | # Create window 55 | client_wins = [] 56 | for i in range(0,2): 57 | client_wins.append(create_client_window(str(i))) 58 | 59 | # Create frame window 60 | frame_win = conn.generate_id() 61 | print("Window : ", hex(frame_win)) 62 | conn.core.CreateWindowChecked(depth, frame_win, root, 0, 0, 200, 200, 0, 63 | xproto.WindowClass.InputOutput, visual, 0, []).check() 64 | set_window_name(conn, frame_win, "Frame") 65 | conn.core.MapWindowChecked(frame_win).check() 66 | 67 | # Scenario 1.1 68 | # 1. reparent placeholder to frame 69 | conn.core.ReparentWindowChecked(client_wins[0], frame_win, 0, 0).check() 70 | wait() 71 | # 2. reparent real client to frame 72 | conn.core.ReparentWindowChecked(client_wins[1], frame_win, 0, 0).check() 73 | wait() 74 | # 3. detach the placeholder 75 | conn.core.ReparentWindowChecked(client_wins[0], root, 0, 0).check() 76 | wait() 77 | assert get_client_win(frame_win) == client_wins[1] 78 | 79 | # Scenario 1.2 80 | # 1. reparent placeholder to frame 81 | conn.core.ReparentWindowChecked(client_wins[0], frame_win, 0, 0).check() 82 | wait() 83 | # 2. reparent real client to frame 84 | conn.core.ReparentWindowChecked(client_wins[1], frame_win, 0, 0).check() 85 | wait() 86 | # 3. destroy the placeholder 87 | conn.core.DestroyWindowChecked(client_wins[0]).check() 88 | wait() 89 | assert get_client_win(frame_win) == client_wins[1] 90 | 91 | client_wins[0] = create_client_window("0") 92 | 93 | # Scenario 2 94 | # 1. frame is unmapped 95 | conn.core.UnmapWindowChecked(frame_win).check() 96 | wait() 97 | # 2. reparent placeholder to frame 98 | conn.core.ReparentWindowChecked(client_wins[0], frame_win, 0, 0).check() 99 | wait() 100 | # 3. destroy placeholder, map frame and reparent real client to frame 101 | conn.core.DestroyWindowChecked(client_wins[0]).check() 102 | conn.core.MapWindowChecked(frame_win).check() 103 | conn.core.ReparentWindowChecked(client_wins[1], frame_win, 0, 0).check() 104 | wait() 105 | assert get_client_win(frame_win) == client_wins[1] 106 | 107 | client_wins[0] = create_client_window("0") 108 | 109 | # Destroy the windows 110 | for wid in client_wins: 111 | conn.core.DestroyWindowChecked(wid).check() 112 | conn.core.DestroyWindowChecked(frame_win).check() 113 | -------------------------------------------------------------------------------- /tests/testcases/issue314.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import xcffib.xproto as xproto 4 | import xcffib 5 | import time 6 | from common import set_window_name, trigger_root_configure 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 | x = xproto.xprotoExtension(conn) 14 | 15 | # issue 314 is caused by changing a windows target opacity during its fade-in/-out transition 16 | wid1 = conn.generate_id() 17 | print("Window 1: ", hex(wid1)) 18 | wid2 = conn.generate_id() 19 | print("Window 2: ", hex(wid2)) 20 | 21 | # Create windows 22 | conn.core.CreateWindowChecked(depth, wid1, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() 23 | conn.core.CreateWindowChecked(depth, wid2, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() 24 | 25 | # Set Window names 26 | set_window_name(conn, wid1, "Test window 1") 27 | set_window_name(conn, wid2, "Test window 2") 28 | 29 | # Check updating opacity while UNMAPPING/DESTROYING windows 30 | print("Mapping 1") 31 | conn.core.MapWindowChecked(wid1).check() 32 | print("Mapping 2") 33 | conn.core.MapWindowChecked(wid2).check() 34 | time.sleep(0.5) 35 | 36 | x.SetInputFocusChecked(0, wid1, xproto.Time.CurrentTime).check() 37 | time.sleep(0.5) 38 | 39 | # Destroy the windows 40 | print("Destroy 1 while fading out") 41 | conn.core.DestroyWindowChecked(wid1).check() 42 | x.SetInputFocusChecked(0, wid2, xproto.Time.CurrentTime).check() 43 | time.sleep(1) 44 | conn.core.DestroyWindowChecked(wid2).check() 45 | -------------------------------------------------------------------------------- /tests/testcases/issue314_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 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 | x = xproto.xprotoExtension(conn) 14 | 15 | opacity_80 = [int(0xffffffff * 0.8), ] 16 | opacity_single = [int(0xffffffff * 0.002), ] 17 | 18 | # issue 314 is caused by changing a windows target opacity during its fade-in/-out transition 19 | wid1 = conn.generate_id() 20 | print("Window 1: ", hex(wid1)) 21 | 22 | atom = "_NET_WM_WINDOW_OPACITY" 23 | opacity_atom = conn.core.InternAtom(False, len(atom), atom).reply().atom 24 | 25 | # Create windows 26 | conn.core.CreateWindowChecked(depth, wid1, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() 27 | 28 | # Set Window names 29 | set_window_name(conn, wid1, "Test window 1") 30 | 31 | # Check updating opacity while MAPPING windows 32 | print("Mapping window") 33 | conn.core.MapWindowChecked(wid1).check() 34 | time.sleep(0.5) 35 | 36 | print("Update opacity while fading in") 37 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_80).check() 38 | time.sleep(0.2) 39 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_single).check() 40 | time.sleep(1) 41 | 42 | conn.core.DeletePropertyChecked(wid1, opacity_atom).check() 43 | time.sleep(0.5) 44 | 45 | # Destroy the windows 46 | conn.core.DestroyWindowChecked(wid1).check() 47 | -------------------------------------------------------------------------------- /tests/testcases/issue314_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 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 | x = xproto.xprotoExtension(conn) 14 | 15 | opacity_100 = [0xffffffff, ] 16 | opacity_80 = [int(0xffffffff * 0.8), ] 17 | opacity_single = [int(0xffffffff * 0.002), ] 18 | opacity_0 = [0, ] 19 | 20 | # issue 314 is caused by changing a windows target opacity during its fade-in/-out transition 21 | wid1 = conn.generate_id() 22 | print("Window 1: ", hex(wid1)) 23 | 24 | atom = "_NET_WM_WINDOW_OPACITY" 25 | opacity_atom = conn.core.InternAtom(False, len(atom), atom).reply().atom 26 | 27 | # Create windows 28 | conn.core.CreateWindowChecked(depth, wid1, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() 29 | 30 | # Set Window names 31 | set_window_name(conn, wid1, "Test window 1") 32 | 33 | # Check updating opacity while FADING windows 34 | print("Mapping window") 35 | conn.core.MapWindowChecked(wid1).check() 36 | time.sleep(1.2) 37 | 38 | print("Update opacity while fading out") 39 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_single).check() 40 | time.sleep(0.2) 41 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_0).check() 42 | time.sleep(1) 43 | 44 | print("Change from fading in to fading out") 45 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_80).check() 46 | time.sleep(0.5) 47 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_0).check() 48 | time.sleep(1) 49 | 50 | print("Update opacity while fading in") 51 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_80).check() 52 | time.sleep(0.2) 53 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_100).check() 54 | time.sleep(1) 55 | 56 | print("Change from fading out to fading in") 57 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_0).check() 58 | time.sleep(0.5) 59 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_80).check() 60 | time.sleep(1) 61 | 62 | # Destroy the windows 63 | conn.core.DestroyWindowChecked(wid1).check() 64 | -------------------------------------------------------------------------------- /tests/testcases/issue357.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import xcffib.xproto as xproto 4 | import xcffib 5 | import time 6 | from common import set_window_name, trigger_root_configure, prepare_root_configure 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 357 is triggered when a window is destroyed right after configure_root 15 | wid = conn.generate_id() 16 | print("Window 1: ", 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 22 | set_window_name(conn, wid, "Test window 1") 23 | 24 | print("mapping 1") 25 | conn.core.MapWindowChecked(wid).check() 26 | time.sleep(0.5) 27 | 28 | reply, mode, output = prepare_root_configure(conn) 29 | trigger_root_configure(conn, reply, mode, output).reply() 30 | 31 | # Destroy the windows 32 | conn.core.DestroyWindowChecked(wid).check() 33 | 34 | time.sleep(1) 35 | -------------------------------------------------------------------------------- /tests/testcases/issue394.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import xcffib.xproto as xproto 4 | import xcffib 5 | import time 6 | from common import set_window_name, set_window_size_async 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 394 is caused by a window getting a size update just before destroying leading to a shadow update on destroyed window. 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, "Test Window") 23 | 24 | # Map the window 25 | print("mapping") 26 | conn.core.MapWindowChecked(wid).check() 27 | 28 | time.sleep(0.5) 29 | 30 | # Resize the window and destroy 31 | print("resize and destroy") 32 | set_window_size_async(conn, wid, 150, 150) 33 | conn.core.DestroyWindowChecked(wid).check() 34 | 35 | time.sleep(0.5) 36 | -------------------------------------------------------------------------------- /tests/testcases/issue465.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 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 | x = xproto.xprotoExtension(conn) 14 | 15 | # issue 465 is triggered when focusing a new window with a shadow-exclude rule for unfocused windows. 16 | wid1 = conn.generate_id() 17 | print("Window 1: ", hex(wid1)) 18 | wid2 = conn.generate_id() 19 | print("Window 2: ", hex(wid2)) 20 | 21 | # Create a window 22 | conn.core.CreateWindowChecked(depth, wid1, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() 23 | conn.core.CreateWindowChecked(depth, wid2, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() 24 | 25 | # Set Window name 26 | set_window_name(conn, wid1, "Test window 1") 27 | set_window_name(conn, wid2, "Test window 2") 28 | 29 | print("mapping 1") 30 | conn.core.MapWindowChecked(wid1).check() 31 | print("mapping 2") 32 | conn.core.MapWindowChecked(wid2).check() 33 | time.sleep(0.5) 34 | 35 | x.SetInputFocusChecked(0, wid1, xproto.Time.CurrentTime).check() 36 | time.sleep(0.5) 37 | 38 | # Destroy the windows 39 | conn.core.DestroyWindowChecked(wid1).check() 40 | time.sleep(1) 41 | -------------------------------------------------------------------------------- /tests/testcases/issue525.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 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 525 happens when a window is unmapped with pixmap stale flag set 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 | # Map the window 22 | print("mapping") 23 | conn.core.MapWindowChecked(wid).check() 24 | 25 | time.sleep(0.5) 26 | 27 | # change window size, invalidate the pixmap 28 | conn.core.ConfigureWindow(wid, xproto.ConfigWindow.X | xproto.ConfigWindow.Width, [100, 200]) 29 | 30 | # unmap the window immediately after 31 | conn.core.UnmapWindowChecked(wid).check() 32 | 33 | time.sleep(0.1) 34 | 35 | # Destroy the window 36 | conn.core.DestroyWindowChecked(wid).check() 37 | -------------------------------------------------------------------------------- /tests/testcases/redirect_when_unmapped_window_has_shadow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 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 | name = "_NET_WM_STATE" 15 | name_atom = conn.core.InternAtom(False, len(name), name).reply().atom 16 | atom = "ATOM" 17 | atom_atom = conn.core.InternAtom(False, len(atom), atom).reply().atom 18 | fs = "_NET_WM_STATE_FULLSCREEN" 19 | fs_atom = conn.core.InternAtom(False, len(fs), fs).reply().atom 20 | 21 | wid1 = conn.generate_id() 22 | print("Window 1 id is ", hex(wid1)) 23 | 24 | # Create a window 25 | conn.core.CreateWindowChecked(depth, wid1, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() 26 | 27 | # Map the window 28 | print("mapping 1") 29 | conn.core.MapWindowChecked(wid1).check() 30 | 31 | time.sleep(0.5) 32 | 33 | print("unmapping 1") 34 | # Unmap the window 35 | conn.core.UnmapWindowChecked(wid1).check() 36 | 37 | time.sleep(0.5) 38 | 39 | # create and map a second window 40 | wid2 = conn.generate_id() 41 | print("Window 2 id is ", hex(wid2)) 42 | conn.core.CreateWindowChecked(depth, wid2, root, 200, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() 43 | print("mapping 2") 44 | conn.core.MapWindowChecked(wid2).check() 45 | time.sleep(0.5) 46 | 47 | # Set fullscreen property on the second window, causing screen to be unredirected 48 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid2, name_atom, atom_atom, 32, 1, [fs_atom]).check() 49 | 50 | time.sleep(0.5) 51 | 52 | # Unset fullscreen property on the second window, causing screen to be redirected 53 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid2, name_atom, atom_atom, 32, 0, []).check() 54 | 55 | time.sleep(0.5) 56 | 57 | # map the first window again 58 | print("mapping 1") 59 | conn.core.MapWindowChecked(wid1).check() 60 | 61 | time.sleep(0.5) 62 | 63 | # Destroy the windows 64 | conn.core.DestroyWindowChecked(wid1).check() 65 | conn.core.DestroyWindowChecked(wid2).check() 66 | --------------------------------------------------------------------------------