├── .circleci └── config.yml ├── .clang-format ├── .clang-tidy ├── .editorconfig ├── .github ├── FUNDING.yml ├── issue_template.md ├── pull_request_template.md └── workflows │ ├── codeql-analysis.yml │ ├── coding-style-pr.yml │ ├── coding-style.yml │ ├── cross.yml │ ├── freebsd.yml │ ├── openbsd.yml │ └── page.yml ├── .gitignore ├── .well-known └── funding-manifest-urls ├── CHANGELOG.md ├── CONTRIBUTORS ├── COPYING ├── Doxyfile ├── History.md ├── LICENSE.spdx ├── LICENSES ├── MIT └── MPL-2.0 ├── README.md ├── assets ├── appear.mp4 ├── fly.mp4 ├── geometry-change.mp4 ├── next.css └── slide.mp4 ├── bin └── picom-trans ├── compton-default-fshader-win.glsl ├── compton-fake-transparency-fshader-win.glsl ├── compton.desktop ├── data └── animation_presets.conf ├── dbus-examples ├── cdbus-driver.sh └── inverter.sh ├── desc.txt ├── flake.lock ├── flake.nix ├── include ├── meson.build └── picom │ ├── api.h │ ├── backend.h │ ├── meson.build │ └── types.h ├── man ├── meson.build ├── picom-inspect.1.adoc ├── picom-trans.1.adoc └── picom.1.adoc ├── media ├── compton.svg └── icons │ └── 48x48 │ └── compton.png ├── meson.build ├── meson └── install.sh ├── meson_options.txt ├── package.nix ├── picom-dbus.desktop ├── picom.desktop ├── picom.pijulius.conf ├── picom.sample.conf ├── screenshot.pijulius.png ├── src ├── api.c ├── api_internal.h ├── 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 │ │ ├── meson.build │ │ └── shaders.c │ ├── meson.build │ └── xrender │ │ ├── meson.build │ │ └── xrender.c ├── c2.c ├── c2.h ├── common.h ├── compiler.h ├── config.c ├── config.h ├── config_libconfig.c ├── dbus.c ├── dbus.h ├── diagnostic.c ├── diagnostic.h ├── event.c ├── event.h ├── fuzzer │ └── c2.c ├── inspect.c ├── inspect.h ├── log.c ├── log.h ├── meson.build ├── options.c ├── options.h ├── picom.c ├── picom.h ├── picom.modulemap ├── region.h ├── renderer │ ├── command_builder.c │ ├── command_builder.h │ ├── damage.c │ ├── damage.h │ ├── layout.c │ ├── layout.h │ ├── meson.build │ ├── renderer.c │ └── renderer.h ├── rtkit.c ├── rtkit.h ├── transition │ ├── curve.c │ ├── curve.h │ ├── generated │ │ └── script_templates.c │ ├── meson.build │ ├── preset.c │ ├── preset.h │ ├── script.c │ ├── script.h │ └── script_internal.h ├── utils │ ├── cache.c │ ├── cache.h │ ├── console.h │ ├── dynarr.c │ ├── dynarr.h │ ├── file_watch.c │ ├── file_watch.h │ ├── kernel.c │ ├── kernel.h │ ├── list.h │ ├── meson.build │ ├── meta.h │ ├── misc.c │ ├── misc.h │ ├── process.c │ ├── process.h │ ├── statistics.c │ ├── statistics.h │ ├── str.c │ ├── str.h │ ├── ui.c │ ├── ui.h │ └── uthash_extra.h ├── vblank.c ├── vblank.h ├── wm │ ├── defs.h │ ├── meson.build │ ├── tree.c │ ├── win.c │ ├── win.h │ ├── wm.c │ ├── wm.h │ └── wm_internal.h ├── x.c ├── x.h ├── xrescheck.c └── xrescheck.h ├── subprojects ├── libconfig.wrap └── test.h │ ├── meson.build │ └── test.h ├── tests ├── configs │ ├── clear_shadow_unredirected.conf │ ├── empty.conf │ ├── issue239.conf │ ├── issue239_2.conf │ ├── issue239_3.conf │ ├── issue239_universal.conf │ ├── issue314.conf │ ├── issue357.conf │ ├── issue394.conf │ ├── issue465.conf │ ├── parsing_test.conf │ ├── pull1091.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 │ ├── pull1091.py │ └── redirect_when_unmapped_window_has_shadow.py └── tools ├── animgen.c ├── make-a-release.py └── meson.build /.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 | - run: 44 | name: build animgen 45 | command: ninja -vC build tools/animgen 46 | - persist_to_workspace: 47 | root: . 48 | paths: 49 | - . 50 | test: 51 | executor: e 52 | steps: 53 | - attach_workspace: 54 | at: /tmp/workspace 55 | - run: 56 | name: Tests 57 | command: | 58 | ulimit -c unlimited 59 | 60 | printf "\n::: test animgen :::\n" 61 | build/tools/animgen data/animation_presets.conf >/dev/null 2>error.log 62 | [ -s error.log ] && cat error.log && exit 1 63 | 64 | printf "\n::: Unit tests :::\n" 65 | ninja -vC build test 66 | 67 | printf "\n::: test config file parsing :::\n" 68 | xvfb-run -a -s "-screen 0 640x480x24" build/src/picom --config tests/configs/parsing_test.conf --no-vsync --diagnostics 69 | 70 | printf "\n::: test config file parsing in a different locale :::\n" 71 | LC_NUMERIC=de_DE.UTF-8 xvfb-run -a -s "-screen 0 640x480x24" build/src/picom --config tests/configs/parsing_test.conf --no-vsync --diagnostics 72 | 73 | printf "\n::: run testsuite :::\n" 74 | tests/run_tests.sh build/src/picom 75 | - run: 76 | name: generate coverage reports 77 | command: cd build; find -name '*.gcno' -exec gcov -pb {} + 78 | - run: 79 | name: download codecov scripts 80 | command: curl -s https://codecov.io/bash > codecov.sh 81 | - run: 82 | name: upload coverage reports 83 | command: bash ./codecov.sh -X gcov 84 | - run: 85 | name: collect coredumps 86 | when: on_fail 87 | command: | 88 | . $HOME/.cargo/env 89 | mkdir /tmp/artifacts 90 | for c in tests/core.*; do 91 | coredump-copy $c /tmp/coredumps/`basename $c` 92 | done 93 | tar Jcf /tmp/artifacts/coredumps.tar.xz /tmp/coredumps 94 | - store_artifacts: 95 | path: /tmp/artifacts 96 | 97 | minimal: 98 | executor: e 99 | steps: 100 | - build: 101 | build-config: -Dopengl=false -Ddbus=false -Dregex=false 102 | release: 103 | executor: e 104 | steps: 105 | - build: 106 | build-config: --buildtype=release 107 | release-clang: 108 | executor: e 109 | steps: 110 | - build: 111 | cc: clang 112 | build-config: --buildtype=release 113 | nogl: 114 | executor: e 115 | steps: 116 | - build: 117 | build-config: -Dopengl=false 118 | noregex: 119 | executor: e 120 | steps: 121 | - build: 122 | build-config: -Dregex=false 123 | clang_basic: 124 | executor: e 125 | steps: 126 | - build: 127 | cc: clang 128 | clang_minimal: 129 | executor: e 130 | steps: 131 | - build: 132 | cc: clang 133 | build-config: -Dopengl=false -Ddbus=false -Dregex=false 134 | clang_nogl: 135 | executor: e 136 | steps: 137 | - build: 138 | cc: clang 139 | build-config: -Dopengl=false 140 | clang_noregex: 141 | executor: e 142 | steps: 143 | - build: 144 | cc: clang 145 | build-config: -Dregex=false 146 | 147 | workflows: 148 | all_builds: 149 | jobs: 150 | - basic 151 | - clang_basic 152 | - minimal 153 | - clang_minimal 154 | - nogl 155 | - clang_nogl 156 | - release 157 | - release-clang 158 | - test: 159 | requires: 160 | - basic 161 | # vim: set sw=2 ts=8 et: 162 | -------------------------------------------------------------------------------- /.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 | FormatStyle: file 18 | CheckOptions: 19 | - key: readability-magic-numbers.IgnoredIntegerValues 20 | value: 4;8;16;24;32;1;2;3;4096;65536; 21 | - key: readability-magic-numbers.IgnoredFloatingPointValues 22 | value: 255.0;1.0; 23 | - key: readability-function-cognitive-complexity.IgnoreMacros 24 | value: true 25 | - key: readability-function-cognitive-complexity.Threshold 26 | value: 50 27 | - key: readability-function-cognitive-complexity.DescribeBasicIncrements 28 | value: true 29 | - key: bugprone-signed-char-misuse.CharTypdefsToIgnore 30 | value: int8_t 31 | - key: bugprone-suspicious-string-compare.WarnOnLogicalNotComparison 32 | value: true 33 | -------------------------------------------------------------------------------- /.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/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: yshui 2 | patreon: 3 | open_collective: 4 | ko_fi: 5 | tidelift: 6 | community_bridge: 7 | liberapay: 8 | issuehunt: 9 | lfx_crowdfunding: 10 | polar: 11 | buy_me_a_coffee: 12 | custom: 13 | -------------------------------------------------------------------------------- /.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 | ## Summary 2 | 3 | 6 | 7 | ## Checklist 8 | 9 | - [ ] Enable "Allow edits from maintainers". (So we can make necessary changes). 10 | - [ ] Add changelog entries to commit messages when appropriate. These will be included in release announcements. 11 |
12 | Format the changelog entries like this: 13 | 14 | `Changelog: BugFix: Stop picom from summoning the Great Old Ones.` 15 | 16 | The entry starts with `Changelog: `, followed by a category name (`BugFix: ` in this case), then a short description of the changes in the commit. 17 | How to format changelog entries 18 |
19 | -------------------------------------------------------------------------------- /.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 | - name: Install libconfig 1.7 23 | run: | 24 | wget https://hyperrealm.github.io/libconfig/dist/libconfig-1.7.3.tar.gz 25 | tar -xvf libconfig-1.7.3.tar.gz 26 | cd libconfig-1.7.3 27 | ./configure --prefix=/usr --libdir=/usr/lib/x86_64-linux-gnu/ 28 | sudo make install 29 | 30 | # Initializes the CodeQL tools for scanning. 31 | - name: Initialize CodeQL 32 | uses: github/codeql-action/init@v3 33 | with: 34 | languages: ${{ matrix.language }} 35 | 36 | # Install dependencies 37 | - run: sudo apt update && sudo apt install 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-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 meson ninja-build uthash-dev libepoxy-dev 38 | if: ${{ matrix.language == 'cpp' }} 39 | 40 | # Autobuild 41 | - name: Autobuild 42 | uses: github/codeql-action/autobuild@v3 43 | 44 | - name: Perform CodeQL Analysis 45 | uses: github/codeql-action/analyze@v3 46 | -------------------------------------------------------------------------------- /.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.17 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.17 13 | with: 14 | base: ${{ github.event.ref }}~1 15 | -------------------------------------------------------------------------------- /.github/workflows/cross.yml: -------------------------------------------------------------------------------- 1 | name: cross 2 | on: push 3 | 4 | jobs: 5 | check: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | with: 10 | fetch-depth: 1 11 | - uses: cachix/install-nix-action@v27 12 | with: 13 | nix_path: nixpkgs=channel:nixpkgs-unstable 14 | - uses: DeterminateSystems/magic-nix-cache-action@main 15 | - run: nix build '.#picom-cross.merged' -L 16 | 17 | -------------------------------------------------------------------------------- /.github/workflows/freebsd.yml: -------------------------------------------------------------------------------- 1 | name: freebsd 2 | on: push 3 | 4 | jobs: 5 | check: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | with: 10 | fetch-depth: 1 11 | - uses: cross-platform-actions/action@6acac3ca1b632aa762721d537dea32398ba0f2b1 12 | with: 13 | operating_system: freebsd 14 | version: '14.1' 15 | shell: bash 16 | run: | 17 | sudo pkg install -y libev libxcb meson pkgconf cmake xcb-util-renderutil xcb-util-image pixman uthash libconfig libglvnd libepoxy dbus pcre2 18 | CPPFLAGS="-I/usr/local/include" meson setup -Dunittest=true --werror build 19 | ninja -C build 20 | ninja -C build test 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/openbsd.yml: -------------------------------------------------------------------------------- 1 | name: openbsd 2 | on: push 3 | 4 | jobs: 5 | check: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | with: 10 | fetch-depth: 1 11 | - uses: cross-platform-actions/action@v0.24.0 12 | with: 13 | operating_system: openbsd 14 | version: '7.5' 15 | shell: bash 16 | run: | 17 | sudo pkg_add libev xcb meson pkgconf cmake uthash libconfig dbus pcre2 18 | CPPFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib" meson setup -Dunittest=true --werror build 19 | ninja -C build 20 | ninja -C build test 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/page.yml: -------------------------------------------------------------------------------- 1 | 2 | name: GitHub Pages 3 | 4 | on: 5 | push: 6 | pull_request: 7 | 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-24.04 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | submodules: false 20 | fetch-depth: 0 21 | 22 | - name: Setup Pages 23 | id: pages 24 | uses: actions/configure-pages@v5 25 | - name: Install asciidoctor 26 | run: sudo apt install -y asciidoctor 27 | 28 | - name: Build 29 | run: | 30 | asciidoctor -a env-web -a doctype=article -a stylesheet=../assets/next.css -b html man/picom.1.adoc -D _site 31 | asciidoctor -a doctype=article -a stylesheet=../assets/next.css -b html man/picom-inspect.1.adoc -D _site 32 | asciidoctor -a doctype=article -a stylesheet=../assets/next.css -b html man/picom-trans.1.adoc -D _site 33 | cp -r assets _site/ 34 | cp _site/picom.1.html _site/index.html 35 | - name: Copy .well-known 36 | run: cp -r .well-known _site 37 | 38 | - name: Upload 39 | uses: actions/upload-pages-artifact@v3 40 | 41 | deploy: 42 | concurrency: 43 | group: "pages" 44 | cancel-in-progress: true 45 | if: github.ref == 'refs/heads/next' 46 | environment: 47 | name: github-pages 48 | url: ${{ steps.deployment.outputs.page_url }} 49 | runs-on: ubuntu-latest 50 | needs: build 51 | steps: 52 | - name: Deploy to GitHub Pages 53 | id: deployment 54 | uses: actions/deploy-pages@v4 55 | 56 | -------------------------------------------------------------------------------- /.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 | 20 | # language servers 21 | .ccls-cache 22 | .clangd 23 | .cache 24 | 25 | # CMake files 26 | compton-*.deb 27 | compton-*.rpm 28 | compton-*.tar.bz2 29 | release/ 30 | _CPack_Packages/ 31 | CMakeCache.txt 32 | CMakeFiles/ 33 | cmake_install.cmake 34 | CPackSourceConfig.cmake 35 | install_manifest.txt 36 | 37 | # apitrace 38 | *.trace 39 | 40 | # Vim files 41 | .sw[a-z] 42 | .*.sw[a-z] 43 | *~ 44 | 45 | # Rust 46 | target 47 | 48 | # Python 49 | *.pyc 50 | *.py 51 | !/tools/*.py 52 | !/tests/testcases/*.py 53 | 54 | # Misc files 55 | .vscode 56 | *.conf 57 | !/tests/configs/*.conf 58 | !/data/*.conf 59 | perf.data 60 | perf.data.old 61 | core.* 62 | vgcore.* 63 | .gdb_history 64 | oprofile_data/ 65 | compton.plist 66 | callgrind.out.* 67 | man/*.html 68 | man/*.1 69 | doxygen/ 70 | .clang_complete 71 | .ycm_extra_conf.py 72 | .ycm_extra_conf.pyc 73 | /src/backtrace-symbols.[ch] 74 | /compton*.trace 75 | *.orig 76 | /tests/log 77 | /tests/testcases/__pycache__/ 78 | *.profraw 79 | 80 | # Subproject files 81 | subprojects/libconfig 82 | -------------------------------------------------------------------------------- /.well-known/funding-manifest-urls: -------------------------------------------------------------------------------- 1 | https://yshui.github.io/funding.json 2 | -------------------------------------------------------------------------------- /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 | Lenivaya 53 | Lukas Schmelzeisen 54 | Mark Tiefenbruck 55 | Matthew Allum 56 | Maxim Solovyov 57 | Michael Reed 58 | Michele Lambertucci 59 | mæp 60 | Namkhai Bourquin 61 | Nate Hart 62 | nia 63 | Nikolay Borodin 64 | notfoss 65 | Omar Polo 66 | oofsauce 67 | orbea 68 | Paradigm0001 69 | Patrick Collins 70 | Peter Mattern 71 | Phil Blundell 72 | Que Quotion 73 | Rafael Kitover 74 | Reith 75 | Richard Grenville 76 | Rytis Karpuska 77 | Samuel Hand 78 | Scott Leggett 79 | scrouthtv 80 | Sebastien Waegeneire 81 | Stefan Radziuk 82 | Subhaditya Nath 83 | Suyooo 84 | Tasos Sahanidis 85 | Thiago Kenji Okada 86 | Tilman Sauerbeck 87 | Tim Siegel 88 | Tim van Dalen 89 | tokyoneon78 90 | Tom Dörr 91 | Tomas Janousek 92 | Toni Jarjour 93 | triallax 94 | Tuomas Kinnunen 95 | Uli Schlachter 96 | Walter Lapchynski 97 | Will Dietz 98 | XeCycle 99 | Yuxuan Shui 100 | zilrich 101 | ಠ_ಠ 102 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | picom - a compositor for X11 2 | 3 | Based on xcompmgr, originally written by Keith Packard, with modifications 4 | from several contributors (according to the xcompmgr man page): Matthew Allum, 5 | Eric Anholt, Dan Doel, Thomas Luebking, Matthew Hawn, Ely Levy, Phil Blundell, 6 | and Carl Worth. Menu transparency was implemented by Dana Jansens. 7 | 8 | Numerous contributions to picom from Richard Grenville. 9 | 10 | See the CONTRIBUTORS file for a complete list of contributors 11 | 12 | This source code is provided under: 13 | 14 | SPDX-License-Identifier: MPL-2.0 AND MIT 15 | 16 | And the preferred license for new source files in this project is: 17 | 18 | SPDX-License-Identifier: MPL-2.0 19 | 20 | You can find the text of the licenses in the LICENSES directory. 21 | -------------------------------------------------------------------------------- /LICENSE.spdx: -------------------------------------------------------------------------------- 1 | SPDXVersion: SPDX-2.1 2 | DataLicense: CC0-1.0 3 | PackageName: picom 4 | PackageLicenseDeclared: MPL-2.0 AND MIT 5 | -------------------------------------------------------------------------------- /LICENSES/MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 2 | 3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 6 | -------------------------------------------------------------------------------- /assets/appear.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pijulius/picom/e9834631b85da58d1f9cb258a0e020eedda58100/assets/appear.mp4 -------------------------------------------------------------------------------- /assets/fly.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pijulius/picom/e9834631b85da58d1f9cb258a0e020eedda58100/assets/fly.mp4 -------------------------------------------------------------------------------- /assets/geometry-change.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pijulius/picom/e9834631b85da58d1f9cb258a0e020eedda58100/assets/geometry-change.mp4 -------------------------------------------------------------------------------- /assets/slide.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pijulius/picom/e9834631b85da58d1f9cb258a0e020eedda58100/assets/slide.mp4 -------------------------------------------------------------------------------- /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": 1710146030, 9 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 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": 1709087332, 29 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 30 | "owner": "hercules-ci", 31 | "repo": "gitignore.nix", 32 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 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": 1721287717, 45 | "narHash": "sha256-i5F24BL4FaJCOE0twnIPaltgDNeA44CqLsj/TqBAIsQ=", 46 | "owner": "nixos", 47 | "repo": "nixpkgs", 48 | "rev": "56375296f413158b095ce493799cc8d237d70739", 49 | "type": "github" 50 | }, 51 | "original": { 52 | "owner": "nixos", 53 | "repo": "nixpkgs", 54 | "type": "github" 55 | } 56 | }, 57 | "root": { 58 | "inputs": { 59 | "flake-utils": "flake-utils", 60 | "git-ignore-nix": "git-ignore-nix", 61 | "nixpkgs": "nixpkgs" 62 | } 63 | }, 64 | "systems": { 65 | "locked": { 66 | "lastModified": 1681028828, 67 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 68 | "owner": "nix-systems", 69 | "repo": "default", 70 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 71 | "type": "github" 72 | }, 73 | "original": { 74 | "owner": "nix-systems", 75 | "repo": "default", 76 | "type": "github" 77 | } 78 | } 79 | }, 80 | "root": "root", 81 | "version": 7 82 | } 83 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | flake-utils.url = "github:numtide/flake-utils"; 4 | nixpkgs.url = "github:nixos/nixpkgs"; 5 | git-ignore-nix = { 6 | url = "github:hercules-ci/gitignore.nix/master"; 7 | inputs.nixpkgs.follows = "nixpkgs"; 8 | }; 9 | }; 10 | outputs = 11 | { self 12 | , flake-utils 13 | , nixpkgs 14 | , git-ignore-nix 15 | , ... 16 | }: 17 | flake-utils.lib.eachDefaultSystem (system: 18 | let 19 | # like lib.lists.remove, but takes a list of elements to remove 20 | removeFromList = toRemove: list: pkgs.lib.foldl (l: e: pkgs.lib.remove e l) list toRemove; 21 | picomOverlay = final: prev: { picom = prev.callPackage ./package.nix { inherit git-ignore-nix; }; }; 22 | overlays = [ 23 | picomOverlay 24 | ]; 25 | pkgs = import nixpkgs { 26 | inherit system overlays; 27 | config.allowBroken = true; 28 | }; 29 | profilePkgs = import nixpkgs { 30 | inherit system; 31 | overlays = overlays ++ [ 32 | (final: prev: { 33 | stdenv = prev.withCFlags "-fno-omit-frame-pointer" prev.stdenv; 34 | }) 35 | (final: prev: { 36 | llvmPackages_18 = prev.llvmPackages_18 // { 37 | stdenv = final.withCFlags "-fno-omit-frame-pointer" prev.llvmPackages_18.stdenv; 38 | }; 39 | }) 40 | ]; 41 | }; 42 | 43 | mkDevShell = p: p.overrideAttrs (o: { 44 | hardeningDisable = [ "fortify" ]; 45 | shellHook = '' 46 | # Workaround a NixOS limitation on sanitizers: 47 | # See: https://github.com/NixOS/nixpkgs/issues/287763 48 | export LD_LIBRARY_PATH+=":/run/opengl-driver/lib" 49 | export UBSAN_OPTIONS="disable_coredump=0:unmap_shadow_on_exit=1:print_stacktrace=1" 50 | export ASAN_OPTIONS="disable_coredump=0:unmap_shadow_on_exit=1:abort_on_error=1" 51 | ''; 52 | }); 53 | in 54 | rec { 55 | overlay = picomOverlay; 56 | packages = { 57 | picom = pkgs.picom; 58 | default = pkgs.picom; 59 | } // (nixpkgs.lib.optionalAttrs (system == "x86_64-linux") rec { 60 | picom-cross = { 61 | armv7l = pkgs.pkgsCross.armv7l-hf-multiplatform.picom; 62 | aarch64 = pkgs.pkgsCross.aarch64-multiplatform.picom; 63 | i686 = pkgs.pkgsi686Linux.picom; 64 | merged = pkgs.runCommand "picom-merged" {} '' 65 | mkdir $out 66 | ln -s ${picom-cross.armv7l} $out/armv7l 67 | ln -s ${picom-cross.aarch64} $out/aarch64 68 | ln -s ${picom-cross.i686} $out/i686 69 | ''; 70 | }; 71 | }); 72 | devShells.default = mkDevShell (packages.default.override { devShell = true; }); 73 | devShells.useClang = devShells.default.override { 74 | inherit (pkgs.llvmPackages_18) stdenv; 75 | }; 76 | # build picom and all dependencies with frame pointer, making profiling/debugging easier. 77 | # WARNING! many many rebuilds 78 | devShells.useClangProfile = (mkDevShell (profilePkgs.picom.override { devShell = true; })).override { 79 | stdenv = profilePkgs.withCFlags "-fno-omit-frame-pointer" profilePkgs.llvmPackages_18.stdenv; 80 | }; 81 | }); 82 | } 83 | -------------------------------------------------------------------------------- /include/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MPL-2.0 2 | # Copyright (c) Yuxuan Shui 3 | subdir('picom') 4 | -------------------------------------------------------------------------------- /include/picom/api.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #define PICOM_API_MAJOR (0UL) 10 | #define PICOM_API_MINOR (1UL) 11 | 12 | struct backend_base; 13 | 14 | /// The entry point of a backend plugin. Called after the backend is initialized. 15 | typedef void (*picom_backend_plugin_entrypoint)(struct backend_base *backend, void *user_data); 16 | struct picom_api { 17 | /// Add a plugin for a specific backend. The plugin's entry point will be called 18 | /// when the specified backend is initialized. 19 | /// 20 | /// @param backend_name The name of the backend to add the plugin to. 21 | /// @param major The major version of the backend API interface this plugin 22 | /// is compatible with. 23 | /// @param minor The minor version of the backend API interface this plugin 24 | /// is compatible with. 25 | /// @param entrypoint The entry point of the plugin. 26 | /// @param user_data The user data to pass to the plugin's entry point. 27 | bool (*add_backend_plugin)(const char *backend_name, uint64_t major, uint64_t minor, 28 | picom_backend_plugin_entrypoint entrypoint, 29 | void *user_data); 30 | }; 31 | 32 | const struct picom_api * 33 | picom_api_get_interfaces(uint64_t major, uint64_t minor, const char *context); 34 | -------------------------------------------------------------------------------- /include/picom/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MPL-2.0 2 | # Copyright (c) Yuxuan Shui 3 | 4 | api_headers = [ 5 | 'api.h', 6 | 'backend.h' 7 | ] 8 | install_headers(api_headers, subdir: 'picom') 9 | -------------------------------------------------------------------------------- /include/picom/types.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | /// Some common types 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | enum blur_method { 14 | BLUR_METHOD_NONE = 0, 15 | BLUR_METHOD_KERNEL, 16 | BLUR_METHOD_BOX, 17 | BLUR_METHOD_GAUSSIAN, 18 | BLUR_METHOD_DUAL_KAWASE, 19 | BLUR_METHOD_INVALID, 20 | }; 21 | 22 | /// Enumeration type to represent switches. 23 | typedef enum { 24 | OFF = 0, // false 25 | ON, // true 26 | UNSET 27 | } switch_t; 28 | 29 | enum tristate { TRI_FALSE = -1, TRI_UNKNOWN = 0, TRI_TRUE = 1 }; 30 | 31 | /// Return value if it's not TRI_UNKNOWN, otherwise return fallback. 32 | static inline enum tristate tri_or(enum tristate value, enum tristate fallback) { 33 | return value ?: fallback; 34 | } 35 | 36 | static inline bool tri_or_bool(enum tristate value, bool fallback) { 37 | return value == TRI_UNKNOWN ? fallback : value == TRI_TRUE; 38 | } 39 | 40 | static inline enum tristate tri_from_bool(bool value) { 41 | return value ? TRI_TRUE : TRI_FALSE; 42 | } 43 | 44 | /// A structure representing margins around a rectangle. 45 | typedef struct { 46 | int top; 47 | int left; 48 | int bottom; 49 | int right; 50 | } margin_t; 51 | 52 | struct color { 53 | double red, green, blue, alpha; 54 | }; 55 | 56 | typedef uint32_t opacity_t; 57 | 58 | typedef struct vec2 { 59 | union { 60 | double x; 61 | double width; 62 | }; 63 | union { 64 | double y; 65 | double height; 66 | }; 67 | } vec2; 68 | 69 | typedef struct ivec2 { 70 | union { 71 | int x; 72 | int width; 73 | }; 74 | union { 75 | int y; 76 | int height; 77 | }; 78 | } ivec2; 79 | 80 | struct ibox { 81 | ivec2 origin; 82 | ivec2 size; 83 | }; 84 | 85 | static const vec2 SCALE_IDENTITY = {1.0, 1.0}; 86 | 87 | static inline vec2 ivec2_as(ivec2 a) { 88 | return (vec2){ 89 | .x = a.x, 90 | .y = a.y, 91 | }; 92 | } 93 | 94 | static inline ivec2 ivec2_add(ivec2 a, ivec2 b) { 95 | return (ivec2){ 96 | .x = a.x + b.x, 97 | .y = a.y + b.y, 98 | }; 99 | } 100 | 101 | static inline ivec2 ivec2_sub(ivec2 a, ivec2 b) { 102 | return (ivec2){ 103 | .x = a.x - b.x, 104 | .y = a.y - b.y, 105 | }; 106 | } 107 | 108 | static inline bool ivec2_eq(ivec2 a, ivec2 b) { 109 | return a.x == b.x && a.y == b.y; 110 | } 111 | 112 | static inline ivec2 ivec2_neg(ivec2 a) { 113 | return (ivec2){ 114 | .x = -a.x, 115 | .y = -a.y, 116 | }; 117 | } 118 | 119 | /// Saturating cast from a vec2 to a ivec2 120 | static inline ivec2 vec2_as(vec2 a) { 121 | return (ivec2){ 122 | .x = (int)fmin(fmax(a.x, INT_MIN), INT_MAX), 123 | .y = (int)fmin(fmax(a.y, INT_MIN), INT_MAX), 124 | }; 125 | } 126 | 127 | static inline vec2 vec2_add(vec2 a, vec2 b) { 128 | return (vec2){ 129 | .x = a.x + b.x, 130 | .y = a.y + b.y, 131 | }; 132 | } 133 | 134 | static inline vec2 vec2_ceil(vec2 a) { 135 | return (vec2){ 136 | .x = ceil(a.x), 137 | .y = ceil(a.y), 138 | }; 139 | } 140 | 141 | static inline vec2 vec2_floor(vec2 a) { 142 | return (vec2){ 143 | .x = floor(a.x), 144 | .y = floor(a.y), 145 | }; 146 | } 147 | 148 | static inline bool vec2_eq(vec2 a, vec2 b) { 149 | return a.x == b.x && a.y == b.y; 150 | } 151 | 152 | static inline vec2 vec2_scale(vec2 a, vec2 scale) { 153 | return (vec2){ 154 | .x = a.x * scale.x, 155 | .y = a.y * scale.y, 156 | }; 157 | } 158 | 159 | /// Check if two boxes have a non-zero intersection area. 160 | static inline bool ibox_overlap(struct ibox a, struct ibox b) { 161 | if (a.size.width <= 0 || a.size.height <= 0 || b.size.width <= 0 || b.size.height <= 0) { 162 | return false; 163 | } 164 | if (a.origin.x <= INT_MAX - a.size.width && a.origin.y <= INT_MAX - a.size.height && 165 | (a.origin.x + a.size.width <= b.origin.x || 166 | a.origin.y + a.size.height <= b.origin.y)) { 167 | return false; 168 | } 169 | if (b.origin.x <= INT_MAX - b.size.width && b.origin.y <= INT_MAX - b.size.height && 170 | (b.origin.x + b.size.width <= a.origin.x || 171 | b.origin.y + b.size.height <= a.origin.y)) { 172 | return false; 173 | } 174 | return true; 175 | } 176 | 177 | static inline bool ibox_eq(struct ibox a, struct ibox b) { 178 | return ivec2_eq(a.origin, b.origin) && ivec2_eq(a.size, b.size); 179 | } 180 | 181 | static inline ivec2 ivec2_scale_ceil(ivec2 a, vec2 scale) { 182 | vec2 scaled = vec2_scale(ivec2_as(a), scale); 183 | return vec2_as(vec2_ceil(scaled)); 184 | } 185 | 186 | static inline ivec2 ivec2_scale_floor(ivec2 a, vec2 scale) { 187 | vec2 scaled = vec2_scale(ivec2_as(a), scale); 188 | return vec2_as(vec2_floor(scaled)); 189 | } 190 | 191 | #define MARGIN_INIT \ 192 | { 0, 0, 0, 0 } 193 | -------------------------------------------------------------------------------- /man/meson.build: -------------------------------------------------------------------------------- 1 | mans = ['picom.1', 'picom-inspect.1', 'picom-trans.1'] 2 | if get_option('with_docs') 3 | a2x = find_program('asciidoctor') 4 | foreach m : mans 5 | custom_target( 6 | m, 7 | output: [m], 8 | input: [m + '.adoc'], 9 | command: [ 10 | a2x, 11 | '-a', 'picom-version=v' + meson.project_version(), 12 | '--backend', 'manpage', 13 | '@INPUT@', 14 | '-D', meson.current_build_dir(), 15 | ], 16 | install: true, 17 | install_dir: join_paths(get_option('mandir'), 'man1'), 18 | ) 19 | custom_target( 20 | m + '.html', 21 | output: [m + '.html'], 22 | input: [m + '.adoc'], 23 | command: [ 24 | a2x, 25 | '-a', 'picom-version=v' + meson.project_version(), 26 | '--backend', 'html', 27 | '@INPUT@', 28 | '-D', meson.current_build_dir(), 29 | ], 30 | install_dir: get_option('datadir') / 'doc' / 'picom', 31 | ) 32 | endforeach 33 | endif 34 | -------------------------------------------------------------------------------- /man/picom-inspect.1.adoc: -------------------------------------------------------------------------------- 1 | = picom-inspect(1) 2 | Yuxuan Shui 3 | :doctype: manpage 4 | :mansource: picom-inspect 5 | :manversion: {picom-version} 6 | :manmanual: User Commands 7 | 8 | NAME 9 | ---- 10 | picom-inspect - easily test your picom rules 11 | 12 | SYNOPSIS 13 | -------- 14 | *picom-inspect* [_OPTIONS_] 15 | 16 | DESCRIPTION 17 | ----------- 18 | *picom-inspect* matches your picom rules against a window of your choosing. It helps you test your rules, and shows you which ones of your rules (don't) work. 19 | 20 | OPTIONS 21 | ------- 22 | *picom-inspect* accepts all options that *picom* does. Naturally, most of those options will not be relevant. 23 | 24 | These are some of the options you might find useful (See *picom*(1) for descriptions of what they do): 25 | 26 | *--config*, *--log-level*, *--log-file*, all the options related to rules. 27 | 28 | *picom-inspect* also accepts some extra options: ::: 29 | 30 | *--monitor*:: Keep *picom-inspect* running in a loop, and dump information every time something changed about a window. 31 | 32 | NOTES 33 | ----- 34 | *picom-inspect* is prototype right now. If you find any bug, for example, if rules are matched differently compared to *picom*, please submit bug reports to: 35 | 36 | 37 | 38 | RESOURCES 39 | --------- 40 | Homepage: 41 | 42 | SEE ALSO 43 | -------- 44 | *xcompmgr*(1), xref:picom.1.adoc[*picom*(1)] 45 | -------------------------------------------------------------------------------- /man/picom-trans.1.adoc: -------------------------------------------------------------------------------- 1 | = picom-trans(1) 2 | Yuxuan Shui 3 | :doctype: manpage 4 | :mansource: picom 5 | :manversion: {picom-version} 6 | :manmanual: 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 | xref:picom.1.adoc[*picom*(1)], *xprop*(1), *xwininfo*(1) 115 | -------------------------------------------------------------------------------- /media/icons/48x48/compton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pijulius/picom/e9834631b85da58d1f9cb258a0e020eedda58100/media/icons/48x48/compton.png -------------------------------------------------------------------------------- /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('regex', type: 'boolean', value: true, description: 'Enable regex support in window conditions') 3 | 4 | option('opengl', type: 'boolean', value: true, description: 'Enable features that require opengl (opengl backend, and opengl vsync methods)') 5 | option('dbus', type: 'boolean', value: true, description: 'Enable support for D-Bus remote control') 6 | 7 | option('xrescheck', type: 'boolean', value: false, description: 'Enable X resource leak checker (for debug only)') 8 | 9 | option('compton', type: 'boolean', value: true, description: 'Install backwards compat with compton') 10 | 11 | option('with_docs', type: 'boolean', value: false, description: 'Build documentation and man pages') 12 | 13 | option('unittest', type: 'boolean', value: false, description: 'Enable unittests in the code') 14 | 15 | # Experimental options 16 | 17 | option('modularize', type: 'boolean', value: false, description: 'Build with clang\'s module system (experimental)') 18 | option('llvm_coverage', type: 'boolean', value: false, description: 'Use LLVM source-based code coverage, instead of --coverage. Do not use with b_coverage.') 19 | -------------------------------------------------------------------------------- /package.nix: -------------------------------------------------------------------------------- 1 | { asciidoctor 2 | , dbus 3 | , docbook_xml_dtd_45 4 | , docbook_xsl 5 | , fetchFromGitHub 6 | , clang-tools_18 7 | , llvmPackages_18 8 | , lib 9 | , libconfig 10 | , libdrm 11 | , libev 12 | , libGL 13 | , libepoxy 14 | , libX11 15 | , libxcb 16 | , libxdg_basedir 17 | , libXext 18 | , libxml2 19 | , libxslt 20 | , makeWrapper 21 | , meson 22 | , ninja 23 | , pcre2 24 | , pixman 25 | , pkg-config 26 | , python3 27 | , stdenv 28 | , uthash 29 | , xcbutil 30 | , xcbutilimage 31 | , xcbutilrenderutil 32 | , xorgproto 33 | , xwininfo 34 | , withDebug ? false 35 | , git-ignore-nix 36 | , devShell ? false 37 | }: 38 | 39 | let 40 | versionFromMeson = s: builtins.head (builtins.match "project\\('picom',.*version: *'([0-9.]*)'.*" s); 41 | in 42 | stdenv.mkDerivation (finalAttrs: { 43 | pname = "picom"; 44 | version = versionFromMeson (builtins.readFile ./meson.build); 45 | 46 | src = git-ignore-nix.lib.gitignoreSource ./.; 47 | 48 | strictDeps = true; 49 | 50 | 51 | nativeBuildInputs = [ 52 | asciidoctor 53 | docbook_xml_dtd_45 54 | docbook_xsl 55 | makeWrapper 56 | meson 57 | ninja 58 | pkg-config 59 | ] ++ (lib.optional devShell [ 60 | clang-tools_18 61 | llvmPackages_18.clang-unwrapped.python 62 | llvmPackages_18.libllvm 63 | (python3.withPackages (ps: with ps; [ 64 | xcffib pip dbus-next pygit2 65 | ])) 66 | ]); 67 | 68 | buildInputs = [ 69 | dbus 70 | libconfig 71 | libdrm 72 | libev 73 | libGL 74 | libepoxy 75 | libX11 76 | libxcb 77 | libxdg_basedir 78 | libXext 79 | libxml2 80 | libxslt 81 | pcre2 82 | pixman 83 | uthash 84 | xcbutil 85 | xcbutilimage 86 | xcbutilrenderutil 87 | xorgproto 88 | ]; 89 | 90 | # Use "debugoptimized" instead of "debug" so perhaps picom works better in 91 | # normal usage too, not just temporary debugging. 92 | mesonBuildType = if withDebug then "debugoptimized" else "release"; 93 | dontStrip = withDebug; 94 | 95 | mesonFlags = [ 96 | "-Dwith_docs=true" 97 | ]; 98 | 99 | installFlags = [ "PREFIX=$(out)" ]; 100 | 101 | # In debug mode, also copy src directory to store. If you then run `gdb picom` 102 | # in the bin directory of picom store path, gdb finds the source files. 103 | postInstall = '' 104 | wrapProgram $out/bin/picom-trans \ 105 | --prefix PATH : ${lib.makeBinPath [ xwininfo ]} 106 | '' + lib.optionalString withDebug '' 107 | cp -r ../src $out/ 108 | ''; 109 | }) 110 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /screenshot.pijulius.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pijulius/picom/e9834631b85da58d1f9cb258a0e020eedda58100/screenshot.pijulius.png -------------------------------------------------------------------------------- /src/api.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "compiler.h" 12 | #include "log.h" 13 | #include "utils/list.h" 14 | #include "utils/misc.h" 15 | 16 | struct backend_plugins { 17 | UT_hash_handle hh; 18 | const char *backend_name; 19 | struct list_node plugins; 20 | } *backend_plugins; 21 | 22 | struct backend_plugin { 23 | const char *backend_name; 24 | picom_backend_plugin_entrypoint entrypoint; 25 | void *user_data; 26 | struct list_node siblings; 27 | }; 28 | 29 | static bool add_backend_plugin(const char *backend_name, uint64_t major, uint64_t minor, 30 | picom_backend_plugin_entrypoint entrypoint, void *user_data) { 31 | if (major != PICOM_BACKEND_MAJOR || minor > PICOM_BACKEND_MINOR) { 32 | log_error("Cannot add plugin for backend %s, because the requested " 33 | "version %" PRIu64 ".%" PRIu64 " is incompatible with the our " 34 | "%lu.%lu", 35 | backend_name, major, minor, PICOM_BACKEND_MAJOR, 36 | PICOM_BACKEND_MINOR); 37 | return false; 38 | } 39 | 40 | auto plugin = ccalloc(1, struct backend_plugin); 41 | plugin->backend_name = backend_name; 42 | plugin->entrypoint = entrypoint; 43 | plugin->user_data = user_data; 44 | 45 | struct backend_plugins *plugins = NULL; 46 | HASH_FIND_STR(backend_plugins, backend_name, plugins); 47 | if (!plugins) { 48 | plugins = ccalloc(1, struct backend_plugins); 49 | plugins->backend_name = strdup(backend_name); 50 | list_init_head(&plugins->plugins); 51 | HASH_ADD_STR(backend_plugins, backend_name, plugins); 52 | } 53 | list_insert_after(&plugins->plugins, &plugin->siblings); 54 | return true; 55 | } 56 | 57 | void api_backend_plugins_invoke(const char *backend_name, struct backend_base *backend) { 58 | struct backend_plugins *plugins = NULL; 59 | HASH_FIND_STR(backend_plugins, backend_name, plugins); 60 | if (!plugins) { 61 | return; 62 | } 63 | 64 | list_foreach(struct backend_plugin, plugin, &plugins->plugins, siblings) { 65 | plugin->entrypoint(backend, plugin->user_data); 66 | } 67 | } 68 | 69 | static struct picom_api picom_api = { 70 | .add_backend_plugin = add_backend_plugin, 71 | }; 72 | 73 | PICOM_PUBLIC_API const struct picom_api * 74 | picom_api_get_interfaces(uint64_t major, uint64_t minor, const char *context) { 75 | if (major != PICOM_API_MAJOR || minor > PICOM_API_MINOR) { 76 | log_error("Cannot provide API interfaces to %s, because the requested" 77 | "version %" PRIu64 ".%" PRIu64 " is incompatible with our " 78 | "%lu.%lu", 79 | context, major, minor, PICOM_API_MAJOR, PICOM_API_MINOR); 80 | return NULL; 81 | } 82 | return &picom_api; 83 | } 84 | -------------------------------------------------------------------------------- /src/api_internal.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | struct backend_base; 5 | 6 | /// Invoke all backend plugins for the specified backend. 7 | void api_backend_plugins_invoke(const char *backend_name, struct backend_base *backend); 8 | -------------------------------------------------------------------------------- /src/atom.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | #include 7 | 8 | #include "utils/meta.h" 9 | 10 | // clang-format off 11 | // Splitted into 2 lists because of the limitation of our macros 12 | #define ATOM_LIST1 \ 13 | _NET_WM_WINDOW_OPACITY, \ 14 | _NET_FRAME_EXTENTS, \ 15 | WM_STATE, \ 16 | _NET_WM_NAME, \ 17 | _NET_WM_PID, \ 18 | WM_NAME, \ 19 | WM_CLASS, \ 20 | WM_ICON_NAME, \ 21 | WM_TRANSIENT_FOR, \ 22 | WM_WINDOW_ROLE, \ 23 | WM_CLIENT_LEADER, \ 24 | WM_CLIENT_MACHINE, \ 25 | _NET_ACTIVE_WINDOW, \ 26 | _COMPTON_SHADOW, \ 27 | COMPTON_VERSION, \ 28 | _NET_WM_WINDOW_TYPE, \ 29 | _XROOTPMAP_ID, \ 30 | ESETROOT_PMAP_ID, \ 31 | _XSETROOT_ID, \ 32 | _NET_CURRENT_DESKTOP 33 | 34 | #define ATOM_LIST2 \ 35 | _NET_WM_WINDOW_TYPE_DESKTOP, \ 36 | _NET_WM_WINDOW_TYPE_DOCK, \ 37 | _NET_WM_WINDOW_TYPE_TOOLBAR, \ 38 | _NET_WM_WINDOW_TYPE_MENU, \ 39 | _NET_WM_WINDOW_TYPE_UTILITY, \ 40 | _NET_WM_WINDOW_TYPE_SPLASH, \ 41 | _NET_WM_WINDOW_TYPE_DIALOG, \ 42 | _NET_WM_WINDOW_TYPE_NORMAL, \ 43 | _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, \ 44 | _NET_WM_WINDOW_TYPE_POPUP_MENU, \ 45 | _NET_WM_WINDOW_TYPE_TOOLTIP, \ 46 | _NET_WM_WINDOW_TYPE_NOTIFICATION, \ 47 | _NET_WM_WINDOW_TYPE_COMBO, \ 48 | _NET_WM_WINDOW_TYPE_DND, \ 49 | _NET_WM_STATE, \ 50 | _NET_WM_STATE_FULLSCREEN, \ 51 | _NET_WM_BYPASS_COMPOSITOR, \ 52 | UTF8_STRING, \ 53 | C_STRING 54 | // clang-format on 55 | 56 | #define ATOM_DEF(x) xcb_atom_t a##x 57 | 58 | struct atom_entry; 59 | struct atom { 60 | LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST1); 61 | LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST2); 62 | }; 63 | 64 | /// Create a new atom object with a xcb connection. `struct atom` does not hold 65 | /// a reference to the connection. 66 | struct atom *init_atoms(xcb_connection_t *c); 67 | 68 | xcb_atom_t get_atom(struct atom *a, const char *key, size_t keylen, xcb_connection_t *c); 69 | static inline xcb_atom_t 70 | get_atom_with_nul(struct atom *a, const char *key, xcb_connection_t *c) { 71 | return get_atom(a, key, strlen(key), c); 72 | } 73 | xcb_atom_t get_atom_cached(struct atom *a, const char *key, size_t keylen); 74 | static inline xcb_atom_t get_atom_cached_with_nul(struct atom *a, const char *key) { 75 | return get_atom_cached(a, key, strlen(key)); 76 | } 77 | const char *get_atom_name(struct atom *a, xcb_atom_t, xcb_connection_t *c); 78 | const char *get_atom_name_cached(struct atom *a, xcb_atom_t atom); 79 | 80 | void destroy_atoms(struct atom *a); 81 | 82 | /// A mock atom object for unit testing. Successive calls to get_atom will return 83 | /// consecutive integers as atoms, starting from 1. Calling get_atom_name with atoms 84 | /// previously seen will result in the string that was used to create the atom; if 85 | /// the atom was never returned by get_atom, it will abort. 86 | struct atom *init_mock_atoms(void); 87 | -------------------------------------------------------------------------------- /src/backend/backend.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2018, Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include "log.h" 11 | 12 | enum backend_command_op { 13 | BACKEND_COMMAND_INVALID = -1, 14 | BACKEND_COMMAND_BLIT, 15 | BACKEND_COMMAND_BLUR, 16 | BACKEND_COMMAND_COPY_AREA, 17 | }; 18 | 19 | /// Symbolic references used as render command source images. The actual `image_handle` 20 | /// will later be filled in by the renderer using this symbolic reference. 21 | enum backend_command_source { 22 | BACKEND_COMMAND_SOURCE_WINDOW, 23 | BACKEND_COMMAND_SOURCE_WINDOW_SAVED, 24 | BACKEND_COMMAND_SOURCE_SHADOW, 25 | BACKEND_COMMAND_SOURCE_BACKGROUND, 26 | }; 27 | 28 | // TODO(yshui) might need better names 29 | 30 | struct backend_command { 31 | enum backend_command_op op; 32 | ivec2 origin; 33 | enum backend_command_source source; 34 | union { 35 | struct { 36 | struct backend_blit_args blit; 37 | /// Region of the screen that will be covered by this blit 38 | /// operations, in screen coordinates. 39 | region_t opaque_region; 40 | }; 41 | struct { 42 | image_handle source_image; 43 | const region_t *region; 44 | } copy_area; 45 | struct backend_blur_args blur; 46 | }; 47 | /// Source mask for the operation. 48 | /// If the `source_mask` of the operation's argument points to this, a mask image 49 | /// will be created for the operation for the renderer. 50 | struct backend_mask_image source_mask; 51 | /// Target mask for the operation. 52 | region_t target_mask; 53 | }; 54 | 55 | bool backend_execute(struct backend_base *backend, image_handle target, unsigned ncmds, 56 | const struct backend_command cmds[ncmds]); 57 | 58 | struct backend_info *backend_find(const char *name); 59 | struct backend_base * 60 | backend_init(struct backend_info *info, session_t *ps, xcb_window_t target); 61 | struct backend_info *backend_iter(void); 62 | struct backend_info *backend_iter_next(struct backend_info *info); 63 | const char *backend_name(struct backend_info *info); 64 | bool backend_can_present(struct backend_info *info); 65 | void log_backend_command_(enum log_level level, const char *func, 66 | const struct backend_command *cmd); 67 | #define log_backend_command(level, cmd) \ 68 | log_backend_command_(LOG_LEVEL_##level, __func__, &(cmd)); 69 | 70 | /// Define a backend entry point. (Note constructor priority 202 is used here because 1xx 71 | /// is reversed by test.h, and 201 is used for logging initialization.) 72 | #define BACKEND_ENTRYPOINT(func) static void __attribute__((constructor(202))) func(void) 73 | -------------------------------------------------------------------------------- /src/backend/backend_common.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "config.h" 11 | 12 | struct session; 13 | struct win; 14 | struct conv; 15 | struct backend_base; 16 | struct backend_operations; 17 | struct x_connection; 18 | 19 | struct dual_kawase_params { 20 | /// Number of downsample passes 21 | int iterations; 22 | /// Pixel offset for down- and upsample 23 | float offset; 24 | /// Save area around blur target (@ref resize_width, @ref resize_height) 25 | int expand; 26 | }; 27 | 28 | xcb_image_t *make_shadow(struct x_connection *c, const conv *kernel, double opacity, 29 | int width, int height); 30 | bool build_shadow(struct x_connection *, double opacity, int width, int height, 31 | const conv *kernel, xcb_render_picture_t shadow_pixel, xcb_pixmap_t *pixmap); 32 | 33 | xcb_render_picture_t 34 | solid_picture(struct x_connection *, bool argb, double a, double r, double g, double b); 35 | 36 | void init_backend_base(struct backend_base *base, session_t *ps); 37 | 38 | struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count); 39 | struct dual_kawase_params *generate_dual_kawase_params(void *args); 40 | 41 | uint32_t backend_no_quirks(struct backend_base *base attr_unused); 42 | -------------------------------------------------------------------------------- /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 "common.h" 10 | #include "compiler.h" 11 | #include "log.h" 12 | 13 | #include "driver.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/misc.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/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 | struct eglext_info { 11 | bool initialized; 12 | bool has_EGL_MESA_query_driver; 13 | bool has_EGL_EXT_buffer_age; 14 | bool has_EGL_EXT_create_context_robustness; 15 | bool has_EGL_KHR_image_pixmap; 16 | }; 17 | 18 | extern struct eglext_info eglext; 19 | 20 | void eglext_init(EGLDisplay); 21 | -------------------------------------------------------------------------------- /src/backend/gl/glx.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | #pragma once 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "x.h" 12 | 13 | struct glx_fbconfig_info { 14 | GLXFBConfig cfg; 15 | int texture_tgts; 16 | int texture_fmt; 17 | int y_inverted; 18 | }; 19 | 20 | bool glx_find_fbconfig(struct x_connection *c, struct xvisual_info m, 21 | struct glx_fbconfig_info *info); 22 | 23 | struct glxext_info { 24 | bool initialized; 25 | bool has_GLX_SGI_video_sync; 26 | bool has_GLX_SGI_swap_control; 27 | bool has_GLX_OML_sync_control; 28 | bool has_GLX_MESA_swap_control; 29 | bool has_GLX_EXT_swap_control; 30 | bool has_GLX_EXT_texture_from_pixmap; 31 | bool has_GLX_ARB_create_context; 32 | bool has_GLX_EXT_buffer_age; 33 | bool has_GLX_MESA_query_renderer; 34 | bool has_GLX_ARB_create_context_robustness; 35 | }; 36 | 37 | extern struct glxext_info glxext; 38 | 39 | void glxext_init(Display *, int screen); 40 | -------------------------------------------------------------------------------- /src/backend/gl/meson.build: -------------------------------------------------------------------------------- 1 | srcs += [ files('blur.c', 'egl.c', 'gl_common.c', 'glx.c', 'shaders.c') ] 2 | -------------------------------------------------------------------------------- /src/backend/meson.build: -------------------------------------------------------------------------------- 1 | # enable xrender 2 | srcs += [ 3 | files( 4 | 'dummy/dummy.c', 5 | 'backend.c', 6 | 'backend_common.c', 7 | 'driver.c', 8 | ), 9 | ] 10 | subdir('xrender') 11 | # enable opengl 12 | if get_option('opengl') 13 | subdir('gl') 14 | endif 15 | -------------------------------------------------------------------------------- /src/backend/xrender/meson.build: -------------------------------------------------------------------------------- 1 | srcs += [ files('xrender.c') ] 2 | -------------------------------------------------------------------------------- /src/c2.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2011-2013, Christopher Jeffrey 3 | // Copyright (c) 2018 Yuxuan Shui 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "utils/list.h" 12 | 13 | typedef struct c2_condition c2_condition; 14 | typedef struct session session_t; 15 | struct c2_state; 16 | /// Per-window state used for c2 condition matching. 17 | struct c2_window_state { 18 | /// An array of window properties. Exact how many 19 | /// properties there are is stored inside `struct c2_state`. 20 | struct c2_property_value *values; 21 | }; 22 | struct atom; 23 | struct win; 24 | struct list_node; 25 | 26 | typedef void (*c2_userdata_free)(void *); 27 | struct c2_condition * 28 | c2_parse(struct list_node *list, const char *pattern, void *data, bool *deprecated); 29 | 30 | /// Parse a condition that has a prefix. The prefix is parsed by `parse_prefix`. If 31 | /// `free_value` is not NULL, it will be called to free the value returned by 32 | /// `parse_prefix` when error occurs. 33 | c2_condition * 34 | c2_parse_with_prefix(struct list_node *list, const char *pattern, 35 | void *(*parse_prefix)(const char *input, const char **end, void *), 36 | void (*free_value)(void *), void *user_data, bool *deprecated); 37 | 38 | void c2_free_condition(c2_condition *lp, c2_userdata_free f); 39 | 40 | /// Create a new c2_state object. This is used for maintaining the internal state 41 | /// used for c2 condition matching. This state object holds a reference to the 42 | /// pass atom object, thus the atom object should be kept alive as long as the 43 | /// state object is alive. 44 | struct c2_state *c2_state_new(struct atom *atoms); 45 | void c2_state_free(struct c2_state *state); 46 | /// Returns true if value of the property is used in any condition. 47 | bool c2_state_is_property_tracked(struct c2_state *state, xcb_atom_t property); 48 | void c2_window_state_init(const struct c2_state *state, struct c2_window_state *window_state); 49 | void c2_window_state_destroy(const struct c2_state *state, struct c2_window_state *window_state); 50 | void c2_window_state_mark_dirty(const struct c2_state *state, 51 | struct c2_window_state *window_state, xcb_atom_t property, 52 | bool is_on_client); 53 | void c2_window_state_update(struct c2_state *state, struct c2_window_state *window_state, 54 | xcb_connection_t *c, xcb_window_t client_win, 55 | xcb_window_t frame_win); 56 | 57 | bool c2_match(struct c2_state *state, const struct win *w, 58 | const struct list_node *conditions, void **pdata); 59 | bool c2_match_one(const struct c2_state *state, const struct win *w, 60 | const c2_condition *condlst, void **pdata); 61 | 62 | bool c2_list_postprocess(struct c2_state *state, xcb_connection_t *c, struct list_node *list); 63 | /// Return user data stored in a condition. 64 | void *c2_condition_get_data(const c2_condition *condition); 65 | /// Set user data stored in a condition. Return the old user data. 66 | void *c2_condition_set_data(c2_condition *condlist, void *data); 67 | /// Convert a c2_condition to string. The returned string is only valid until the 68 | /// next call to this function, and should not be freed. 69 | const char *c2_condition_to_str(const c2_condition *); 70 | c2_condition *c2_condition_list_next(struct list_node *list, c2_condition *condition); 71 | c2_condition *c2_condition_list_prev(struct list_node *list, c2_condition *condition); 72 | c2_condition *c2_condition_list_entry(struct list_node *list); 73 | /// Create a new condition list with a single condition that is always true. 74 | c2_condition *c2_new_true(struct list_node *list); 75 | 76 | // NOLINTBEGIN(bugprone-macro-parentheses) 77 | #define c2_condition_list_foreach(list, i) \ 78 | for (c2_condition *i = \ 79 | list_is_empty((list)) ? NULL : c2_condition_list_entry((list)->next); \ 80 | i; i = c2_condition_list_next(list, i)) 81 | #define c2_condition_list_foreach_rev(list, i) \ 82 | for (c2_condition *i = \ 83 | list_is_empty((list)) ? NULL : c2_condition_list_entry((list)->prev); \ 84 | i; i = c2_condition_list_prev(list, i)) 85 | 86 | #define c2_condition_list_foreach_safe(list, i, n) \ 87 | for (c2_condition *i = \ 88 | list_is_empty((list)) ? NULL : c2_condition_list_entry((list)->next), \ 89 | *n = c2_condition_list_next(list, i); \ 90 | i; i = n, n = c2_condition_list_next(list, i)) 91 | // NOLINTEND(bugprone-macro-parentheses) 92 | 93 | /** 94 | * Destroy a condition list. 95 | */ 96 | static inline void c2_list_free(struct list_node *list, c2_userdata_free f) { 97 | c2_condition_list_foreach_safe(list, i, ni) { 98 | c2_free_condition(i, f); 99 | } 100 | list_init_head(list); 101 | } 102 | -------------------------------------------------------------------------------- /src/compiler.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2018 Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | #ifdef HAS_STDC_PREDEF_H 7 | #include 8 | #endif 9 | 10 | // clang-format off 11 | #if __STDC_VERSION__ <= 201710L 12 | // Polyfill for C23's `auto` and `typeof` 13 | # define auto __auto_type 14 | # define typeof __typeof__ 15 | #endif 16 | #define likely(x) __builtin_expect(!!(x), 1) 17 | #define unlikely(x) __builtin_expect(!!(x), 0) 18 | #define likely_if(x) if (likely(x)) 19 | #define unlikely_if(x) if (unlikely(x)) 20 | 21 | #ifndef __has_attribute 22 | # if __GNUC__ >= 4 23 | # define __has_attribute(x) 1 24 | # else 25 | # define __has_attribute(x) 0 26 | # endif 27 | #endif 28 | 29 | #if __has_attribute(const) 30 | # define attr_const __attribute__((const)) 31 | #else 32 | # define attr_const 33 | #endif 34 | 35 | #if __has_attribute(format) 36 | # define attr_printf(a, b) __attribute__((format(printf, a, b))) 37 | #else 38 | # define attr_printf(a, b) 39 | #endif 40 | 41 | #if __has_attribute(pure) 42 | # define attr_pure __attribute__((pure)) 43 | #else 44 | # define attr_pure 45 | #endif 46 | 47 | #if __has_attribute(unused) 48 | # define attr_unused __attribute__((unused)) 49 | #else 50 | # define attr_unused 51 | #endif 52 | 53 | #if __has_attribute(warn_unused_result) 54 | # define attr_warn_unused_result __attribute__((warn_unused_result)) 55 | #else 56 | # define attr_warn_unused_result 57 | #endif 58 | // An alias for convenience 59 | #define must_use attr_warn_unused_result 60 | 61 | #if __has_attribute(nonnull) 62 | # define attr_nonnull(...) __attribute__((nonnull(__VA_ARGS__))) 63 | # define attr_nonnull_all __attribute__((nonnull)) 64 | #else 65 | # define attr_nonnull(...) 66 | # define attr_nonnull_all 67 | #endif 68 | 69 | #if __has_attribute(returns_nonnull) 70 | # define attr_ret_nonnull __attribute__((returns_nonnull)) 71 | #else 72 | # define attr_ret_nonnull 73 | #endif 74 | 75 | #if __has_attribute(deprecated) 76 | # define attr_deprecated __attribute__((deprecated)) 77 | #else 78 | # define attr_deprecated 79 | #endif 80 | 81 | #if __has_attribute(malloc) 82 | # define attr_malloc __attribute__((malloc)) 83 | #else 84 | # define attr_malloc 85 | #endif 86 | 87 | #if __has_attribute(fallthrough) 88 | # define fallthrough() __attribute__((fallthrough)) 89 | #else 90 | # define fallthrough() 91 | #endif 92 | 93 | #if __has_attribute(cleanup) 94 | # define cleanup(func) __attribute__((cleanup(func))) 95 | #else 96 | # error "Compiler is missing cleanup attribute" 97 | #endif 98 | 99 | #if __STDC_VERSION__ >= 201112L 100 | # define attr_noret _Noreturn 101 | #else 102 | # if __has_attribute(noreturn) 103 | # define attr_noret __attribute__((noreturn)) 104 | # else 105 | # define attr_noret 106 | # endif 107 | #endif 108 | 109 | #ifndef unreachable 110 | # if defined(__GNUC__) || defined(__clang__) 111 | # define unreachable() assert(false); __builtin_unreachable() 112 | # else 113 | # define unreachable() assert(false); do {} while(0) 114 | # endif 115 | #endif 116 | 117 | #ifndef __has_include 118 | # define __has_include(x) 0 119 | #endif 120 | 121 | #ifndef __has_builtin 122 | # define __has_builtin(x) 0 123 | #endif 124 | 125 | #if !defined(__STDC_NO_THREADS__) && __has_include() 126 | # include 127 | #elif __STDC_VERSION__ >= 201112L 128 | # define thread_local _Thread_local 129 | #elif defined(__GNUC__) || defined(__clang__) 130 | # define thread_local __thread 131 | #else 132 | # define thread_local _Pragma("GCC error \"No thread local storage support\"") __error__ 133 | #endif 134 | 135 | #define PICOM_PUBLIC_API __attribute__((visibility("default"))) 136 | // clang-format on 137 | 138 | typedef unsigned long ulong; 139 | typedef unsigned int uint; 140 | 141 | static inline int attr_const popcntul(unsigned long a) { 142 | return __builtin_popcountl(a); 143 | } 144 | 145 | /// Get the index of the lowest bit set in a number. The result is undefined if 146 | /// `a` is 0. 147 | static inline int attr_const index_of_lowest_one(unsigned a) { 148 | #if __has_builtin(__builtin_ctz) 149 | return __builtin_ctz(a); 150 | #else 151 | auto lowbit = (a & -a); 152 | int r = (lowbit & 0xAAAAAAAA) != 0; 153 | r |= ((lowbit & 0xCCCCCCCC) != 0) << 1; 154 | r |= ((lowbit & 0xF0F0F0F0) != 0) << 2; 155 | r |= ((lowbit & 0xFF00FF00) != 0) << 3; 156 | r |= ((lowbit & 0xFFFF0000) != 0) << 4; 157 | return r; 158 | #endif 159 | } 160 | -------------------------------------------------------------------------------- /src/dbus.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2011-2013, Christopher Jeffrey 3 | // Copyright (c) 2018 Yuxuan Shui 4 | 5 | #include 6 | 7 | typedef struct session session_t; 8 | struct win; 9 | struct cdbus_data; 10 | 11 | #ifdef CONFIG_DBUS 12 | #include 13 | /** 14 | * Return a string representation of a D-Bus message type. 15 | */ 16 | static inline const char *cdbus_repr_msgtype(DBusMessage *msg) { 17 | return dbus_message_type_to_string(dbus_message_get_type(msg)); 18 | } 19 | 20 | /** 21 | * Initialize D-Bus connection. 22 | */ 23 | struct cdbus_data *cdbus_init(session_t *ps, const char *uniq_name); 24 | 25 | /** 26 | * Destroy D-Bus connection. 27 | */ 28 | void cdbus_destroy(struct cdbus_data *cd); 29 | 30 | /// Generate dbus win_added signal 31 | void cdbus_ev_win_added(struct cdbus_data *cd, struct win *w); 32 | 33 | /// Generate dbus win_destroyed signal 34 | void cdbus_ev_win_destroyed(struct cdbus_data *cd, struct win *w); 35 | 36 | /// Generate dbus win_mapped signal 37 | void cdbus_ev_win_mapped(struct cdbus_data *cd, struct win *w); 38 | 39 | /// Generate dbus win_unmapped signal 40 | void cdbus_ev_win_unmapped(struct cdbus_data *cd, struct win *w); 41 | 42 | /// Generate dbus win_focusout signal 43 | void cdbus_ev_win_focusout(struct cdbus_data *cd, struct win *w); 44 | 45 | /// Generate dbus win_focusin signal 46 | void cdbus_ev_win_focusin(struct cdbus_data *cd, struct win *w); 47 | 48 | #else 49 | 50 | static inline void 51 | cdbus_ev_win_unmapped(struct cdbus_data *cd attr_unused, struct win *w attr_unused) { 52 | } 53 | 54 | static inline void 55 | cdbus_ev_win_mapped(struct cdbus_data *cd attr_unused, struct win *w attr_unused) { 56 | } 57 | 58 | static inline void 59 | cdbus_ev_win_destroyed(struct cdbus_data *cd attr_unused, struct win *w attr_unused) { 60 | } 61 | 62 | static inline void 63 | cdbus_ev_win_added(struct cdbus_data *cd attr_unused, struct win *w attr_unused) { 64 | } 65 | 66 | static inline void 67 | cdbus_ev_win_focusout(struct cdbus_data *cd attr_unused, struct win *w attr_unused) { 68 | } 69 | 70 | static inline void 71 | cdbus_ev_win_focusin(struct cdbus_data *cd attr_unused, struct win *w attr_unused) { 72 | } 73 | 74 | #endif 75 | 76 | // vim: set noet sw=8 ts=8 : 77 | -------------------------------------------------------------------------------- /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/backend.h" 9 | #include "backend/driver.h" 10 | #include "common.h" 11 | #include "config.h" 12 | #include "diagnostic.h" 13 | #include "picom.h" 14 | 15 | void print_diagnostics(session_t *ps, const char *config_file, bool compositor_running) { 16 | printf("**Version:** " PICOM_FULL_VERSION "\n"); 17 | // printf("**CFLAGS:** %s\n", "??"); 18 | printf("\n### X extensions:\n\n"); 19 | printf("* GLX: %s\n", ps->c.e.has_glx ? "present" : "absent"); 20 | printf("* Present: %s\n", ps->c.e.has_present ? "present" : "absent"); 21 | printf("* RandR: %s\n", ps->c.e.has_randr ? "present" : "absent"); 22 | printf("* Shape: %s\n", ps->c.e.has_shape ? "present" : "absent"); 23 | printf("* Sync: %s\n", ps->c.e.has_sync ? "present" : "absent"); 24 | printf("\n### Misc:\n\n"); 25 | printf("* Use Overlay: %s\n", ps->overlay != XCB_NONE ? "Yes" : "No"); 26 | if (ps->overlay == XCB_NONE) { 27 | if (compositor_running) { 28 | printf(" (Another compositor is already running)\n"); 29 | } else if (session_redirection_mode(ps) != XCB_COMPOSITE_REDIRECT_MANUAL) { 30 | printf(" (Not in manual redirection mode)\n"); 31 | } else { 32 | printf("\n"); 33 | } 34 | } 35 | #ifdef __FAST_MATH__ 36 | printf("* Fast Math: Yes\n"); 37 | #endif 38 | printf("* Config file specified: %s\n", config_file ?: "None"); 39 | printf("* Config file used: %s\n", ps->o.config_file_path ?: "None"); 40 | if (!list_is_empty(&ps->o.included_config_files)) { 41 | printf("* Included config files:\n"); 42 | list_foreach(struct included_config_file, i, &ps->o.included_config_files, 43 | siblings) { 44 | printf(" - %s\n", i->path); 45 | } 46 | } 47 | printf("\n### Drivers (inaccurate):\n\n"); 48 | print_drivers(ps->drivers); 49 | 50 | for (auto i = backend_iter(); i; i = backend_iter_next(i)) { 51 | auto backend_data = backend_init(i, ps, session_get_target_window(ps)); 52 | if (!backend_data) { 53 | printf(" Cannot initialize backend %s\n", backend_name(i)); 54 | continue; 55 | } 56 | if (backend_data->ops.diagnostics) { 57 | printf("\n### Backend: %s\n\n", backend_name(i)); 58 | backend_data->ops.diagnostics(backend_data); 59 | } 60 | backend_data->ops.deinit(backend_data); 61 | } 62 | } 63 | 64 | // vim: set noet sw=8 ts=8 : 65 | -------------------------------------------------------------------------------- /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/event.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2019, Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | 7 | #include "common.h" 8 | 9 | void ev_handle(session_t *ps, xcb_generic_event_t *ev); 10 | void ev_update_focused(struct session *ps); 11 | -------------------------------------------------------------------------------- /src/fuzzer/c2.c: -------------------------------------------------------------------------------- 1 | 2 | #include "c2.h" 3 | #include 4 | #include 5 | #include "config.h" 6 | #include "log.h" 7 | 8 | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 9 | log_init_tls(); 10 | if (size == 0) { 11 | return 0; 12 | } 13 | if (data[size - 1] != 0) { 14 | return 0; 15 | } 16 | c2_lptr_t *cond = c2_parse(NULL, (char *)data, NULL); 17 | (void)cond; 18 | (void)size; 19 | return 0; // Values other than 0 and -1 are reserved for future use. 20 | } -------------------------------------------------------------------------------- /src/inspect.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2024 Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | #include "wm/win.h" 7 | 8 | struct x_connection; 9 | struct c2_state; 10 | struct options; 11 | 12 | int inspect_main(int argc, char **argv, const char *config_file); 13 | xcb_window_t inspect_select_window(struct x_connection *c); 14 | void inspect_dump_window(const struct c2_state *state, const struct options *opts, 15 | const struct win *w); 16 | void inspect_dump_window_maybe_options(struct window_maybe_options wopts); 17 | -------------------------------------------------------------------------------- /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 | #include 8 | 9 | #include "compiler.h" 10 | 11 | enum log_level { 12 | LOG_LEVEL_INVALID = -1, 13 | /// Very noisy debug messages, many lines per frame. 14 | LOG_LEVEL_TRACE = 0, 15 | /// Frequent debug messages, a few lines per frame. 16 | LOG_LEVEL_VERBOSE, 17 | /// Less frequent debug messages. 18 | LOG_LEVEL_DEBUG, 19 | /// Informational messages. 20 | LOG_LEVEL_INFO, 21 | /// Warnings. 22 | LOG_LEVEL_WARN, 23 | /// Errors. 24 | LOG_LEVEL_ERROR, 25 | /// Fatal errors. 26 | LOG_LEVEL_FATAL, 27 | }; 28 | 29 | #define LOG_UNLIKELY(level, x, ...) \ 30 | do { \ 31 | if (unlikely(LOG_LEVEL_##level >= log_get_level_tls())) { \ 32 | log_printf(tls_logger, LOG_LEVEL_##level, __func__, x, ##__VA_ARGS__); \ 33 | } \ 34 | } while (0) 35 | 36 | #define LOG_(level, x, ...) \ 37 | do { \ 38 | if (level >= log_get_level_tls()) { \ 39 | log_printf(tls_logger, level, __func__, x, ##__VA_ARGS__); \ 40 | } \ 41 | } while (0) 42 | #define LOG(level, x, ...) LOG_(LOG_LEVEL_##level, x, ##__VA_ARGS__) 43 | #define log_trace(x, ...) LOG_UNLIKELY(TRACE, x, ##__VA_ARGS__) 44 | #define log_verbose(x, ...) LOG_UNLIKELY(VERBOSE, x, ##__VA_ARGS__) 45 | #define log_debug(x, ...) LOG_UNLIKELY(DEBUG, x, ##__VA_ARGS__) 46 | #define log_info(x, ...) LOG(INFO, x, ##__VA_ARGS__) 47 | #define log_warn(x, ...) LOG(WARN, x, ##__VA_ARGS__) 48 | #define log_error(x, ...) LOG(ERROR, x, ##__VA_ARGS__) 49 | #define log_fatal(x, ...) LOG(FATAL, x, ##__VA_ARGS__) 50 | 51 | #define log_error_errno(x, ...) LOG(ERROR, x ": %s", ##__VA_ARGS__, strerror(errno)) 52 | 53 | struct log; 54 | struct log_target; 55 | 56 | attr_printf(4, 5) void log_printf(struct log *, int level, const char *func, 57 | const char *fmt, ...); 58 | 59 | attr_malloc struct log *log_new(void); 60 | /// Destroy a log struct and every log target added to it 61 | attr_nonnull_all void log_destroy(struct log *); 62 | attr_nonnull(1) void log_set_level(struct log *l, int level); 63 | attr_pure enum log_level log_get_level(const struct log *l); 64 | attr_nonnull_all void log_add_target(struct log *, struct log_target *); 65 | attr_pure int string_to_log_level(const char *); 66 | /// Remove a previously added log target for a log struct, and destroy it. If the log 67 | /// target was never added, nothing happens. 68 | void log_remove_target(struct log *l, struct log_target *tgt); 69 | 70 | extern thread_local struct log *tls_logger; 71 | 72 | /// Create a thread local logger 73 | static inline void log_init_tls(void) { 74 | tls_logger = log_new(); 75 | } 76 | /// Set thread local logger log level 77 | static inline void log_set_level_tls(int level) { 78 | assert(tls_logger); 79 | log_set_level(tls_logger, level); 80 | } 81 | static inline attr_nonnull_all void log_add_target_tls(struct log_target *tgt) { 82 | assert(tls_logger); 83 | log_add_target(tls_logger, tgt); 84 | } 85 | 86 | static inline attr_nonnull_all void log_remove_target_tls(struct log_target *tgt) { 87 | assert(tls_logger); 88 | log_remove_target(tls_logger, tgt); 89 | } 90 | 91 | static inline attr_pure enum log_level log_get_level_tls(void) { 92 | assert(tls_logger); 93 | return log_get_level(tls_logger); 94 | } 95 | 96 | static inline void log_deinit_tls(void) { 97 | assert(tls_logger); 98 | log_destroy(tls_logger); 99 | tls_logger = NULL; 100 | } 101 | 102 | attr_malloc struct log_target *stderr_logger_new(void); 103 | attr_malloc struct log_target *file_logger_new(const char *file); 104 | attr_malloc struct log_target *null_logger_new(void); 105 | attr_malloc struct log_target *gl_string_marker_logger_new(void); 106 | 107 | // vim: set noet sw=8 ts=8: 108 | -------------------------------------------------------------------------------- /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 = [cc.find_library('m'), libev] 6 | 7 | srcs = [ 8 | files( 9 | 'api.c', 10 | 'atom.c', 11 | 'c2.c', 12 | 'config.c', 13 | 'config_libconfig.c', 14 | 'diagnostic.c', 15 | 'event.c', 16 | 'inspect.c', 17 | 'log.c', 18 | 'options.c', 19 | 'picom.c', 20 | 'vblank.c', 21 | 'x.c', 22 | ), 23 | ] 24 | picom_inc = include_directories(['.', '../include']) 25 | 26 | cflags = [] 27 | 28 | required_xcb_packages = [ 29 | 'xcb', 30 | 'xcb-composite', 31 | 'xcb-damage', 32 | 'xcb-glx', 33 | 'xcb-present', 34 | 'xcb-randr', 35 | 'xcb-render', 36 | 'xcb-shape', 37 | 'xcb-sync', 38 | 'xcb-xfixes', 39 | ] 40 | 41 | # Some XCB packages are here because their versioning differs (see check below). 42 | required_packages = [ 43 | 'pixman-1', 44 | 'x11', 45 | 'x11-xcb', 46 | 'xcb-image', 47 | 'xcb-renderutil', 48 | 'xcb-util', 49 | 'threads', 50 | ] 51 | 52 | foreach i : required_packages 53 | base_deps += [dependency(i, required: true)] 54 | endforeach 55 | 56 | foreach i : required_xcb_packages 57 | base_deps += [dependency(i, version: '>=1.12.0', required: true)] 58 | endforeach 59 | 60 | libconfig_dep = dependency('libconfig', version: '>=1.7', required: false) 61 | 62 | if not libconfig_dep.found() 63 | warning('Trying to clone and build libconfig as a subproject.') 64 | 65 | cmake = import('cmake') 66 | sub_libconfig_opts = cmake.subproject_options() 67 | sub_libconfig_opts.add_cmake_defines( 68 | { 69 | 'BUILD_SHARED_LIBS': false, 70 | }, 71 | ) 72 | sub_libconfig_opts.set_install(false) 73 | sub_libconfig = cmake.subproject('libconfig', options: sub_libconfig_opts) 74 | base_deps += [sub_libconfig.dependency('config')] 75 | else 76 | base_deps += [libconfig_dep] 77 | endif 78 | 79 | if not cc.has_header('uthash.h') 80 | error('Dependency uthash not found') 81 | endif 82 | 83 | deps = [] 84 | 85 | if get_option('regex') 86 | pcre = dependency('libpcre2-8', required: true) 87 | cflags += ['-DCONFIG_REGEX_PCRE'] 88 | deps += [pcre] 89 | endif 90 | 91 | if get_option('opengl') 92 | cflags += ['-DCONFIG_OPENGL'] 93 | deps += [dependency('epoxy', required: true)] 94 | endif 95 | 96 | if get_option('dbus') 97 | cflags += ['-DCONFIG_DBUS'] 98 | deps += [dependency('dbus-1', required: true)] 99 | srcs += ['dbus.c', 'rtkit.c'] 100 | endif 101 | 102 | if get_option('xrescheck') 103 | cflags += ['-DDEBUG_XRC'] 104 | srcs += ['xrescheck.c'] 105 | endif 106 | 107 | if get_option('unittest') 108 | cflags += ['-DUNIT_TEST'] 109 | endif 110 | 111 | host_system = host_machine.system() 112 | if host_system == 'linux' 113 | cflags += ['-DHAS_INOTIFY'] 114 | elif ( 115 | host_system == 'freebsd' 116 | or host_system == 'netbsd' 117 | or host_system == 'dragonfly' 118 | or host_system == 'openbsd' 119 | ) 120 | cflags += ['-DHAS_KQUEUE'] 121 | endif 122 | 123 | subdir('backend') 124 | subdir('wm') 125 | subdir('renderer') 126 | subdir('transition') 127 | subdir('utils') 128 | 129 | dl_dep = [] 130 | if not cc.has_function('dlopen') 131 | dl_dep = [cc.find_library('dl', required: true)] 132 | endif 133 | 134 | libtools = static_library( 135 | 'libtools', 136 | [ 137 | 'log.c', 138 | 'utils/dynarr.c', 139 | 'utils/misc.c', 140 | 'utils/str.c', 141 | 'transition/curve.c', 142 | 'transition/script.c', 143 | ], 144 | include_directories: picom_inc, 145 | dependencies: [test_h_dep], 146 | install: false, 147 | build_by_default: false, 148 | ) 149 | 150 | picom = executable( 151 | 'picom', 152 | srcs, 153 | c_args: cflags, 154 | dependencies: [base_deps, deps, test_h_dep] + dl_dep, 155 | install: true, 156 | include_directories: picom_inc, 157 | export_dynamic: true, 158 | gnu_symbol_visibility: 'hidden', 159 | ) 160 | 161 | if get_option('unittest') 162 | test('picom unittest', picom, args: ['--unittest']) 163 | endif 164 | 165 | install_symlink('picom-inspect', install_dir: 'bin', pointing_to: 'picom') 166 | 167 | if cc.has_argument('-fsanitize=fuzzer') 168 | c2_fuzz = executable( 169 | 'c2_fuzz', 170 | srcs + ['fuzzer/c2.c'], 171 | c_args: cflags + ['-fsanitize=fuzzer', '-DCONFIG_FUZZER'], 172 | link_args: ['-fsanitize=fuzzer'], 173 | dependencies: [base_deps, deps, test_h_dep], 174 | build_by_default: false, 175 | install: false, 176 | include_directories: picom_inc, 177 | ) 178 | endif 179 | -------------------------------------------------------------------------------- /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 11 | 12 | #include "compiler.h" 13 | #include "config.h" 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); 34 | void options_postprocess_c2_lists(struct c2_state *state, struct x_connection *c, 35 | struct options *option); 36 | void options_destroy(struct options *options); 37 | 38 | // vim: set noet sw=8 ts=8: 39 | -------------------------------------------------------------------------------- /src/picom.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2011-2013, Christopher Jeffrey 3 | // Copyright (c) 2018 Yuxuan Shui 4 | 5 | // Throw everything in here. 6 | // !!! DON'T !!! 7 | 8 | // === Includes === 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | #include "c2.h" 19 | #include "common.h" 20 | #include "config.h" 21 | #include "log.h" // XXX clean up 22 | #include "wm/win.h" 23 | #include "x.h" 24 | 25 | // == Functions == 26 | // TODO(yshui) move static inline functions that are only used in picom.c, into picom.c 27 | 28 | void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce); 29 | 30 | void root_damaged(session_t *ps); 31 | 32 | void queue_redraw(session_t *ps); 33 | 34 | void discard_pending(session_t *ps, uint32_t sequence); 35 | 36 | void configure_root(session_t *ps); 37 | 38 | void quit(session_t *ps); 39 | 40 | xcb_window_t session_get_target_window(session_t *); 41 | 42 | uint8_t session_redirection_mode(session_t *ps); 43 | 44 | #ifdef CONFIG_DBUS 45 | struct cdbus_data *session_get_cdbus(struct session *); 46 | #else 47 | static inline struct cdbus_data *session_get_cdbus(session_t *ps attr_unused) { 48 | return NULL; 49 | } 50 | #endif 51 | 52 | /** 53 | * Set a switch_t array of all unset wintypes to true. 54 | */ 55 | static inline void wintype_arr_enable_unset(switch_t arr[]) { 56 | wintype_t i; 57 | 58 | for (i = 0; i < NUM_WINTYPES; ++i) { 59 | if (UNSET == arr[i]) { 60 | arr[i] = ON; 61 | } 62 | } 63 | } 64 | 65 | /** 66 | * Check if a window ID exists in an array of window IDs. 67 | * 68 | * @param arr the array of window IDs 69 | * @param count amount of elements in the array 70 | * @param wid window ID to search for 71 | */ 72 | static inline bool array_wid_exists(const xcb_window_t *arr, int count, xcb_window_t wid) { 73 | while (count--) { 74 | if (arr[count] == wid) { 75 | return true; 76 | } 77 | } 78 | 79 | return false; 80 | } 81 | 82 | /** 83 | * Dump an drawable's info. 84 | */ 85 | static inline void dump_drawable(session_t *ps, xcb_drawable_t drawable) { 86 | auto r = xcb_get_geometry_reply(ps->c.c, xcb_get_geometry(ps->c.c, drawable), NULL); 87 | if (!r) { 88 | log_trace("Drawable %#010x: Failed", drawable); 89 | return; 90 | } 91 | log_trace("Drawable %#010x: x = %u, y = %u, wid = %u, hei = %d, b = %u, d = %u", 92 | drawable, r->x, r->y, r->width, r->height, r->border_width, r->depth); 93 | free(r); 94 | } 95 | -------------------------------------------------------------------------------- /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/renderer/command_builder.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | struct command_builder; 9 | struct backend_command; 10 | struct layout; 11 | struct x_monitors; 12 | struct win_option; 13 | struct shader_info; 14 | 15 | struct command_builder *command_builder_new(void); 16 | void command_builder_free(struct command_builder *); 17 | 18 | void command_builder_command_list_free(struct backend_command *cmds); 19 | 20 | /// Generate render commands that need to be executed to render the current layout. 21 | /// This function updates `layout->commands` with the list of generated commands, and also 22 | /// the `number_of_commands` field of each of the layers in `layout`. The list of 23 | /// commands must later be freed with `command_builder_command_list_free` 24 | /// It is guaranteed that each of the command's region of operation (e.g. the mask.region 25 | /// argument of blit), will be store in `struct backend_command::mask`. This might not 26 | /// stay true after further passes. 27 | void command_builder_build(struct command_builder *cb, struct layout *layout, 28 | bool force_blend, bool blur_frame, bool inactive_dim_fixed, 29 | double max_brightness, const struct x_monitors *monitors, 30 | const struct shader_info *shaders); 31 | -------------------------------------------------------------------------------- /src/renderer/damage.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | typedef struct pixman_region32 region_t; 9 | struct layout; 10 | struct layout_manager; 11 | struct backend_mask; 12 | /// Remove unnecessary parts of the render commands. 13 | /// 14 | /// After this call, the commands' regions of operations no longer point to their `mask` 15 | /// fields. they point to `culled_mask` instead. The values of their `mask` fields are 16 | /// retained, so later the commands can be "un-culled". 17 | /// 18 | /// @param culled_mask use to stored the culled masks, must be have space to store at 19 | /// least `layout->number_of_commands` elements. They MUST be 20 | /// initialized before calling this function. These masks MUST NOT be 21 | /// freed until you call `commands_uncull`. 22 | void commands_cull_with_damage(struct layout *layout, const region_t *damage, 23 | ivec2 blur_size, region_t *culled_mask); 24 | 25 | /// Un-do the effect of `commands_cull_with_damage` 26 | void commands_uncull(struct layout *layout); 27 | 28 | /// Calculate damage of the screen for the last `buffer_age` layouts. Assuming the 29 | /// current, yet to be rendered frame is numbered frame 0, the previous frame is numbered 30 | /// frame -1, and so on. This function returns the region of the screen that will be 31 | /// different between frame `-buffer_age` and frame 0. The region is in screen 32 | /// coordinates. `buffer_age` is at least 1, and must be less than the `max_buffer_age` 33 | /// passed to the `layout_manager_new` that was used to create `lm`. 34 | /// 35 | /// The layouts you want to calculate damage for must already have commands built for 36 | /// them. `blur_size` is the size of the background blur, and is assumed to not change 37 | /// over time. 38 | /// 39 | /// Note `layout_manager_damage` cannot take desktop background change into 40 | /// account. 41 | void layout_manager_damage(struct layout_manager *lm, unsigned buffer_age, 42 | ivec2 blur_size, region_t *damage); 43 | -------------------------------------------------------------------------------- /src/renderer/layout.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "config.h" 12 | #include "region.h" 13 | #include "wm/wm.h" 14 | 15 | /// A layer to be rendered in a render layout 16 | struct layer { 17 | /// Window that will be rendered in this layer 18 | wm_treeid key; 19 | /// The window, this is only valid for the current layout. Once 20 | /// a frame has passed, windows could have been freed. 21 | struct win *win; 22 | struct window_options options; 23 | /// Damaged region of this layer, in screen coordinates 24 | region_t damaged; 25 | /// Window rectangle in screen coordinates, before it's scaled. 26 | struct ibox window; 27 | /// Shadow rectangle in screen coordinates, before it's scaled. 28 | struct ibox shadow; 29 | /// Scale of the window. The origin of scaling is the top left corner of the 30 | /// window. 31 | vec2 scale; 32 | /// Scale of the shadow. The origin of scaling is the top left corner of the 33 | /// shadow. 34 | vec2 shadow_scale; 35 | /// Opacity of this window 36 | float opacity; 37 | /// Opacity of the background blur of this window 38 | float blur_opacity; 39 | /// Opacity of this window's shadow 40 | float shadow_opacity; 41 | /// How much the image of this window should be blended with the saved image 42 | float saved_image_blend; 43 | /// Crop the content of this layer to this box, in screen coordinates. 44 | struct ibox crop; 45 | 46 | /// How many commands are needed to render this layer 47 | unsigned number_of_commands; 48 | 49 | /// Rank of this layer in the previous frame, -1 if this window 50 | /// appears in this frame for the first time 51 | int prev_rank; 52 | /// Rank of this layer in the next frame, -1 if this window is 53 | /// removed in the next frame 54 | int next_rank; 55 | 56 | // TODO(yshui) make opaqueness/blur finer grained maybe? to support 57 | // things like blur-background-frame 58 | // region_t opaque_region; 59 | // region_t blur_region; 60 | }; 61 | 62 | /// Layout of windows at a specific frame 63 | struct layout { 64 | ivec2 size; 65 | /// The root image generation, see `struct session::root_image_generation` 66 | uint64_t root_image_generation; 67 | /// Layers as a flat array, from bottom to top in stack order. This is a dynarr. 68 | struct layer *layers; 69 | /// Number of commands in `commands` 70 | unsigned number_of_commands; 71 | /// Where does the commands for the bottom most layer start. 72 | /// Any commands before that is for the desktop background. 73 | unsigned first_layer_start; 74 | /// Commands that are needed to render this layout. Commands 75 | /// are recorded in the same order as the layers they correspond to. Each layer 76 | /// can have 0 or more commands associated with it. 77 | struct backend_command *commands; 78 | }; 79 | 80 | struct wm; 81 | struct layout_manager; 82 | 83 | /// Compute the layout of windows to be rendered in the current frame, and append it to 84 | /// the end of layout manager's ring buffer. The layout manager has a ring buffer of 85 | /// layouts, with its size chosen at creation time. Calling this will push at new layout 86 | /// at the end of the ring buffer, and remove the oldest layout if the buffer is full. 87 | void layout_manager_append_layout(struct layout_manager *lm, struct wm *wm, 88 | uint64_t root_pixmap_generation, ivec2 size); 89 | /// Get the layout `age` frames into the past. Age `0` is the most recently appended 90 | /// layout. 91 | struct layout *layout_manager_layout(struct layout_manager *lm, unsigned age); 92 | void layout_manager_free(struct layout_manager *lm); 93 | /// Clear all layouts in the layout manager, so that the next render will start from 94 | /// scratch instead of incremental based on damage information. 95 | void layout_manager_clear(struct layout_manager *lm); 96 | /// Create a new render lm with a ring buffer for `max_buffer_age` layouts. 97 | struct layout_manager *layout_manager_new(unsigned max_buffer_age); 98 | /// Collect damage from the window for the past `buffer_age` frames. 99 | void layout_manager_collect_window_damage(const struct layout_manager *lm, unsigned index, 100 | unsigned buffer_age, region_t *damage); 101 | /// Find where layer at `index` was `buffer_age` frames ago. 102 | int layer_prev_rank(struct layout_manager *lm, unsigned buffer_age, unsigned index_); 103 | /// Find layer that was at `index` `buffer_age` aga in the current layout. 104 | int layer_next_rank(struct layout_manager *lm, unsigned buffer_age, unsigned index_); 105 | unsigned layout_manager_max_buffer_age(const struct layout_manager *lm); 106 | -------------------------------------------------------------------------------- /src/renderer/meson.build: -------------------------------------------------------------------------------- 1 | srcs += [ files('command_builder.c', 'damage.c', 'layout.c', 'renderer.c') ] 2 | -------------------------------------------------------------------------------- /src/renderer/renderer.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | struct renderer; 11 | struct layout_manager; 12 | struct backend_base; 13 | struct command_builder; 14 | struct shader_info; 15 | typedef struct image_handle *image_handle; 16 | struct x_monitors; 17 | struct wm; 18 | struct win_option; 19 | typedef struct pixman_region32 region_t; 20 | 21 | void renderer_free(struct backend_base *backend, struct renderer *r); 22 | struct renderer *renderer_new(struct backend_base *backend, double shadow_radius, 23 | struct color shadow_color, bool dithered_present); 24 | bool renderer_render(struct renderer *r, struct backend_base *backend, 25 | image_handle root_image, struct layout_manager *lm, 26 | struct command_builder *cb, void *blur_context, uint64_t render_start_us, 27 | xcb_sync_fence_t xsync_fence, bool use_damage, bool monitor_repaint, 28 | bool force_blend, bool blur_frame, bool inactive_dim_fixed, 29 | double max_brightness, const struct x_monitors *monitors, 30 | const struct shader_info *shaders, uint64_t *after_damage_us); 31 | -------------------------------------------------------------------------------- /src/rtkit.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | #include 7 | 8 | #include "compiler.h" 9 | 10 | #ifdef CONFIG_DBUS 11 | 12 | #include 13 | 14 | bool rtkit_make_realtime(long thread, int priority); 15 | 16 | #else 17 | 18 | static inline bool rtkit_make_realtime(pid_t thread attr_unused, int priority attr_unused) { 19 | return false; 20 | } 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/transition/curve.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | #include 7 | 8 | enum curve_type { 9 | CURVE_LINEAR, 10 | CURVE_CUBIC_BEZIER, 11 | CURVE_STEP, 12 | CURVE_INVALID, 13 | }; 14 | 15 | struct curve { 16 | enum curve_type type; 17 | union { 18 | struct curve_cubic_bezier { 19 | double ax, bx, cx; 20 | double ay, by, cy; 21 | } bezier; 22 | struct curve_step { 23 | int steps; 24 | bool jump_start, jump_end; 25 | } step; 26 | }; 27 | }; 28 | 29 | static const struct curve CURVE_LINEAR_INIT = {.type = CURVE_LINEAR}; 30 | static const struct curve CURVE_INVALID_INIT = {.type = CURVE_INVALID}; 31 | 32 | static inline struct curve curve_new_cubic_bezier(double x1, double y1, double x2, double y2) { 33 | double cx = 3. * x1; 34 | double bx = 3. * (x2 - x1) - cx; 35 | double cy = 3. * y1; 36 | double by = 3. * (y2 - y1) - cy; 37 | return (struct curve){ 38 | .type = CURVE_CUBIC_BEZIER, 39 | .bezier = {.ax = 1. - cx - bx, .bx = bx, .cx = cx, .ay = 1. - cy - by, .by = by, .cy = cy}, 40 | }; 41 | } 42 | static inline struct curve curve_new_step(int steps, bool jump_start, bool jump_end) { 43 | assert(steps > 0); 44 | return (struct curve){ 45 | .type = CURVE_STEP, 46 | .step = {.steps = steps, .jump_start = jump_start, .jump_end = jump_end}, 47 | }; 48 | } 49 | struct curve curve_parse(const char *str, const char **end, char **err); 50 | /// Calculate the value of the curve at `progress`. 51 | double curve_sample(const struct curve *curve, double progress); 52 | char *curve_to_c(const struct curve *curve); 53 | -------------------------------------------------------------------------------- /src/transition/meson.build: -------------------------------------------------------------------------------- 1 | srcs += [files('generated/script_templates.c', 'curve.c', 'preset.c', 'script.c')] 2 | -------------------------------------------------------------------------------- /src/transition/preset.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #include 5 | #include 6 | 7 | #include "config.h" 8 | #include "preset.h" 9 | #include "script.h" 10 | 11 | extern struct { 12 | const char *name; 13 | bool (*func)(struct win_script *output, config_setting_t *setting); 14 | } win_script_presets[]; 15 | 16 | bool win_script_parse_preset(struct win_script *output, config_setting_t *setting) { 17 | const char *preset = NULL; 18 | if (!config_setting_lookup_string(setting, "preset", &preset)) { 19 | log_error("Missing preset name in script"); 20 | return false; 21 | } 22 | for (unsigned i = 0; win_script_presets[i].name; i++) { 23 | if (strcmp(preset, win_script_presets[i].name) == 0) { 24 | log_debug("Using animation preset: %s", preset); 25 | return win_script_presets[i].func(output, setting); 26 | } 27 | } 28 | log_error("Unknown preset: %s", preset); 29 | return false; 30 | } 31 | -------------------------------------------------------------------------------- /src/transition/preset.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | typedef struct config_setting_t config_setting_t; 9 | struct win_script; 10 | 11 | /// Parse a animation preset definition into a win_script. 12 | bool win_script_parse_preset(struct win_script *output, config_setting_t *setting); 13 | -------------------------------------------------------------------------------- /src/transition/script.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | struct script_context_info { 13 | const char *name; 14 | ptrdiff_t offset; 15 | }; 16 | 17 | struct script_specialization_context { 18 | ptrdiff_t offset; 19 | double value; 20 | }; 21 | 22 | struct script_output_info { 23 | const char *name; 24 | /// Slot for this variable, -1 if this variable doesn't exist. 25 | int slot; 26 | }; 27 | 28 | struct script_parse_config { 29 | const struct script_context_info *context_info; 30 | /// Set the output variables of this script, also used to receive the slot number 31 | /// for those variables. 32 | struct script_output_info *output_info; 33 | }; 34 | struct script; 35 | struct script_instance { 36 | const struct script *script; 37 | double memory[]; 38 | }; 39 | enum script_evaluation_result { 40 | /// +/-inf in results 41 | SCRIPT_EVAL_ERROR_INF, 42 | /// NaN in results 43 | SCRIPT_EVAL_ERROR_NAN, 44 | /// OK 45 | SCRIPT_EVAL_OK, 46 | }; 47 | typedef struct config_setting_t config_setting_t; 48 | 49 | #define SCRIPT_CTX_PLACEHOLDER_BASE (0x40000000) 50 | 51 | struct script * 52 | script_compile(config_setting_t *setting, struct script_parse_config cfg, char **out_err); 53 | void script_free(struct script *script); 54 | enum script_evaluation_result 55 | script_instance_evaluate(struct script_instance *instance, void *context); 56 | /// Resume the script instance from another script instance that's currently running. 57 | /// The script doesn't have to be the same. For resumable (explained later) transitions, 58 | /// if matching variables exist in the `old` script, their starting point will be 59 | /// overridden with the current value of matching variables from `old`. A resumable 60 | /// transition is a transition that will "resume" from wherever its current value is. 61 | /// Imagine a window flying off the screen, for some reason you decided to bring it back 62 | /// when it's just halfway cross. It would be jarring if the window jumps, so you would 63 | /// want it to fly back in from where it currently is, instead from out of the screen. 64 | /// This resuming behavior can be turned off by setting `reset = true;` in the transition 65 | /// configuration, in which case the user defined `start` value will always be used. 66 | void script_instance_resume_from(struct script_instance *old, struct script_instance *new_); 67 | struct script_instance *script_instance_new(const struct script *script); 68 | /// Get the total duration slot of a script. 69 | unsigned script_total_duration_slot(const struct script *script); 70 | unsigned script_elapsed_slot(const struct script *script); 71 | 72 | /// Specialize a script instance with a context. During evaluation of the resulting 73 | /// script, what would have been read from the context will be replaced with the hardcoded 74 | /// value in the specialization context. 75 | void script_specialize(struct script *script, 76 | const struct script_specialization_context *context, 77 | unsigned n_context); 78 | 79 | /// Check if a script instance has finished. The script instance must have been evaluated 80 | /// at least once. 81 | static inline bool script_instance_is_finished(const struct script_instance *instance) { 82 | return instance->memory[script_elapsed_slot(instance->script)] >= 83 | instance->memory[script_total_duration_slot(instance->script)]; 84 | } 85 | 86 | /// Generate code for a C function that will return a script identical to `script` when 87 | /// called. The generated function will take a `int *output_slots` parameter, which it 88 | /// will fill in, based on `outputs` passed to this function. Specifically, the generated 89 | /// function will fill in `output_slots[i]` with the slot number of the output variable 90 | /// named `outputs[i].name`. The generated function will return a pointer to the script. 91 | /// This function only generates the function body, you need to provide the function 92 | /// signature and the function name yourself. 93 | char *script_to_c(const struct script *script, const struct script_output_info *outputs); 94 | -------------------------------------------------------------------------------- /src/transition/script_internal.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include "curve.h" 9 | 10 | #define OPERATORS \ 11 | X(OP_ADD) \ 12 | X(OP_SUB) \ 13 | X(OP_MUL) \ 14 | X(OP_DIV) \ 15 | /* Exponent */ \ 16 | X(OP_EXP) \ 17 | /* Negation */ \ 18 | X(OP_NEG) \ 19 | X(OP_MAX) 20 | 21 | #define X(x) x, 22 | enum op { OPERATORS }; 23 | #undef X 24 | 25 | enum instruction_type { 26 | /// Push an immediate value to the top of the stack 27 | INST_IMM = 0, 28 | /// Pop two values from the top of the stack, apply operator, 29 | /// and push the result to the top of the stack 30 | INST_OP, 31 | /// Load a memory slot and push its value to the top of the stack. 32 | INST_LOAD, 33 | /// Load from evaluation context and push the value to the top of the stack. 34 | INST_LOAD_CTX, 35 | /// Pop one value from the top of the stack, and store it into a memory slot. 36 | INST_STORE, 37 | /// Pop one value from the top of the stack, if the memory slot contains NaN, 38 | /// store it into the memory slot; otherwise discard the value. 39 | INST_STORE_OVER_NAN, 40 | /// Pop a value from the top of the stack, clamp its value to [0, 1], then 41 | /// evaluate a curve at that point, push the result to the top of the stack. 42 | INST_CURVE, 43 | /// Jump to the branch target only when the script is evaluated for the first 44 | /// time. Used to perform initialization and such. 45 | INST_BRANCH_ONCE, 46 | /// Unconditional branch 47 | INST_BRANCH, 48 | INST_HALT, 49 | }; 50 | 51 | /// Store metadata about where the result of a variable is stored 52 | struct variable_allocation { 53 | UT_hash_handle hh; 54 | char *name; 55 | unsigned index; 56 | /// The memory slot for variable named `name` 57 | unsigned slot; 58 | }; 59 | 60 | struct instruction { 61 | enum instruction_type type; 62 | union { 63 | double imm; 64 | enum op op; 65 | /// Memory slot for load and store 66 | unsigned slot; 67 | /// Context offset for load_ctx 68 | ptrdiff_t ctx; 69 | /// Relative PC change for branching 70 | int rel; 71 | /// The curve 72 | struct curve curve; 73 | }; 74 | }; 75 | 76 | /// When interrupting an already executing script and starting a new script, 77 | /// we might want to inherit some of the existing values of variables as starting points, 78 | /// i.e. we want to "resume" animation for the current state. This is configurable, and 79 | /// can be disabled by enabling the `reset` property on a transition. This struct store 80 | /// where the `start` variables of those "resumable" transition variables, which can be 81 | /// overridden at the start of execution for this use case. 82 | struct overridable_slot { 83 | UT_hash_handle hh; 84 | char *name; 85 | unsigned slot; 86 | }; 87 | 88 | struct script { 89 | unsigned len; 90 | unsigned n_slots; 91 | /// The memory slot for storing the elapsed time. 92 | /// The next slot after this is used for storing the total duration of the script. 93 | unsigned elapsed_slot; 94 | unsigned stack_size; 95 | struct variable_allocation *vars; 96 | struct overridable_slot *overrides; 97 | struct instruction instrs[]; 98 | }; 99 | -------------------------------------------------------------------------------- /src/utils/cache.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #include 5 | #include 6 | 7 | #include "cache.h" 8 | #include "misc.h" 9 | 10 | struct cache_handle *cache_get(struct cache *c, const char *key, size_t keylen) { 11 | struct cache_handle *e; 12 | HASH_FIND(hh, c->entries, key, keylen, e); 13 | return e; 14 | } 15 | 16 | int cache_get_or_fetch(struct cache *c, const char *key, size_t keylen, 17 | struct cache_handle **value, void *user_data, cache_getter_t getter) { 18 | *value = cache_get(c, key, keylen); 19 | if (*value) { 20 | return 0; 21 | } 22 | 23 | int err = getter(c, key, keylen, value, user_data); 24 | assert(err <= 0); 25 | if (err < 0) { 26 | return err; 27 | } 28 | // Add a NUL terminator to make things easier 29 | (*value)->key = ccalloc(keylen + 1, char); 30 | memcpy((*value)->key, key, keylen); 31 | 32 | HASH_ADD_KEYPTR(hh, c->entries, (*value)->key, keylen, *value); 33 | return 1; 34 | } 35 | 36 | static inline void 37 | cache_invalidate_impl(struct cache *c, struct cache_handle *e, cache_free_t free_fn) { 38 | free(e->key); 39 | HASH_DEL(c->entries, e); 40 | if (free_fn) { 41 | free_fn(c, e); 42 | } 43 | } 44 | 45 | void cache_invalidate_all(struct cache *c, cache_free_t free_fn) { 46 | struct cache_handle *e, *tmpe; 47 | HASH_ITER(hh, c->entries, e, tmpe) { 48 | cache_invalidate_impl(c, e, free_fn); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/utils/cache.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #define cache_entry(ptr, type, member) container_of(ptr, type, member) 9 | 10 | struct cache; 11 | struct cache_handle; 12 | 13 | /// User-provided function to fetch a value for the cache, when it's not present. 14 | /// Should return 0 if the value is fetched successfully, and a negative number if the 15 | /// value cannot be fetched. Getter doesn't need to initialize fields of `struct 16 | /// cache_handle`. 17 | typedef int (*cache_getter_t)(struct cache *, const char *key, size_t keylen, 18 | struct cache_handle **value, void *user_data); 19 | typedef void (*cache_free_t)(struct cache *, struct cache_handle *value); 20 | 21 | struct cache { 22 | struct cache_handle *entries; 23 | }; 24 | 25 | static const struct cache CACHE_INIT = {NULL}; 26 | 27 | struct cache_handle { 28 | char *key; 29 | UT_hash_handle hh; 30 | }; 31 | 32 | /// Get a value from the cache. If the value doesn't present in the cache yet, the 33 | /// getter will be called, and the returned value will be stored into the cache. 34 | /// Returns 0 if the value is already present in the cache, 1 if the value is fetched 35 | /// successfully, and a negative number if the value cannot be fetched. 36 | int cache_get_or_fetch(struct cache *, const char *key, size_t keylen, 37 | struct cache_handle **value, void *user_data, cache_getter_t getter); 38 | 39 | /// Get a value from the cache. If the value doesn't present in the cache, NULL will be 40 | /// returned. 41 | struct cache_handle *cache_get(struct cache *, const char *key, size_t keylen); 42 | 43 | /// Invalidate all values in the cache. After this call, `struct cache` holds no allocated 44 | /// memory, and can be discarded. 45 | void cache_invalidate_all(struct cache *, cache_free_t free_fn); 46 | -------------------------------------------------------------------------------- /src/utils/console.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// Generate ANSI escape code 4 | #define ANSI(x) "\033[" x "m" 5 | /// Create a string that will print `str` in bold when output to terminal 6 | #define BOLD(str) "\033[1m" str "\033[0m" 7 | -------------------------------------------------------------------------------- /src/utils/dynarr.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #include "dynarr.h" 5 | char *dynarr_join(char **arr, const char *sep) { 6 | size_t total_len = 0; 7 | dynarr_foreach(arr, i) { 8 | total_len += strlen(*i); 9 | } 10 | 11 | char *ret = malloc(total_len + strlen(sep) * (dynarr_len(arr) - 1) + 1); 12 | size_t pos = 0; 13 | allocchk(ret); 14 | dynarr_foreach(arr, i) { 15 | if (i != arr) { 16 | strcpy(ret + pos, sep); 17 | pos += strlen(sep); 18 | } 19 | strcpy(ret + pos, *i); 20 | pos += strlen(*i); 21 | free(*i); 22 | } 23 | dynarr_free_pod(arr); 24 | ret[pos] = '\0'; 25 | return ret; 26 | } 27 | -------------------------------------------------------------------------------- /src/utils/file_watch.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #include 5 | #include 6 | #ifdef HAS_INOTIFY 7 | #include 8 | #elif HAS_KQUEUE 9 | // clang-format off 10 | #include 11 | // clang-format on 12 | #include 13 | #undef EV_ERROR // Avoid clashing with libev's EV_ERROR 14 | #include // For O_RDONLY 15 | #include // For struct timespec 16 | #include // For open 17 | #endif 18 | 19 | #include 20 | #include 21 | 22 | #include "file_watch.h" 23 | #include "log.h" 24 | #include "misc.h" 25 | 26 | struct watched_file { 27 | int wd; 28 | void *ud; 29 | file_watch_cb_t cb; 30 | 31 | UT_hash_handle hh; 32 | }; 33 | 34 | struct file_watch_registry { 35 | struct ev_io w; 36 | 37 | struct watched_file *reg; 38 | }; 39 | 40 | static void file_watch_ev_cb(EV_P attr_unused, struct ev_io *w, int revent attr_unused) { 41 | auto fwr = (struct file_watch_registry *)w; 42 | 43 | while (true) { 44 | int wd = -1; 45 | #ifdef HAS_INOTIFY 46 | struct inotify_event inotify_event; 47 | auto ret = read(w->fd, &inotify_event, sizeof(struct inotify_event)); 48 | if (ret < 0) { 49 | if (errno != EAGAIN) { 50 | log_error_errno("Failed to read from inotify fd"); 51 | } 52 | break; 53 | } 54 | wd = inotify_event.wd; 55 | #elif HAS_KQUEUE 56 | struct kevent ev; 57 | struct timespec timeout = {0}; 58 | int ret = kevent(fwr->w.fd, NULL, 0, &ev, 1, &timeout); 59 | if (ret <= 0) { 60 | if (ret < 0) { 61 | log_error_errno("Failed to get kevent"); 62 | } 63 | break; 64 | } 65 | wd = (int)ev.ident; 66 | #else 67 | assert(false); 68 | #endif 69 | 70 | struct watched_file *wf = NULL; 71 | HASH_FIND_INT(fwr->reg, &wd, wf); 72 | if (!wf) { 73 | log_warn("Got notification for a file I didn't watch."); 74 | continue; 75 | } 76 | wf->cb(wf->ud); 77 | } 78 | } 79 | 80 | void *file_watch_init(EV_P) { 81 | log_debug("Starting watching for file changes"); 82 | int fd = -1; 83 | #ifdef HAS_INOTIFY 84 | fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 85 | if (fd < 0) { 86 | log_error_errno("inotify_init1 failed"); 87 | return NULL; 88 | } 89 | #elif HAS_KQUEUE 90 | fd = kqueue(); 91 | if (fd < 0) { 92 | log_error_errno("Failed to create kqueue"); 93 | return NULL; 94 | } 95 | #else 96 | log_info("No file watching support found on the host system."); 97 | return NULL; 98 | #endif 99 | auto fwr = ccalloc(1, struct file_watch_registry); 100 | ev_io_init(&fwr->w, file_watch_ev_cb, fd, EV_READ); 101 | ev_io_start(EV_A_ & fwr->w); 102 | 103 | return fwr; 104 | } 105 | 106 | void file_watch_destroy(EV_P_ void *_fwr) { 107 | log_debug("Stopping watching for file changes"); 108 | auto fwr = (struct file_watch_registry *)_fwr; 109 | struct watched_file *i, *tmp; 110 | 111 | HASH_ITER(hh, fwr->reg, i, tmp) { 112 | HASH_DEL(fwr->reg, i); 113 | #ifdef HAS_KQUEUE 114 | // kqueue watch descriptors are file descriptors of 115 | // the files we are watching, so we need to close 116 | // them 117 | close(i->wd); 118 | #endif 119 | free(i); 120 | } 121 | 122 | ev_io_stop(EV_A_ & fwr->w); 123 | close(fwr->w.fd); 124 | free(fwr); 125 | } 126 | 127 | bool file_watch_add(void *_fwr, const char *filename, file_watch_cb_t cb, void *ud) { 128 | log_debug("Adding \"%s\" to watched files", filename); 129 | auto fwr = (struct file_watch_registry *)_fwr; 130 | int wd = -1; 131 | 132 | struct stat statbuf; 133 | int ret = stat(filename, &statbuf); 134 | if (ret < 0) { 135 | log_error_errno("Failed to retrieve information about file \"%s\"", filename); 136 | return false; 137 | } 138 | if (!S_ISREG(statbuf.st_mode)) { 139 | log_info("\"%s\" is not a regular file, not watching it.", filename); 140 | return false; 141 | } 142 | 143 | #ifdef HAS_INOTIFY 144 | wd = inotify_add_watch(fwr->w.fd, filename, 145 | IN_CLOSE_WRITE | IN_MOVE_SELF | IN_DELETE_SELF); 146 | if (wd < 0) { 147 | log_error_errno("Failed to watch file \"%s\"", filename); 148 | return false; 149 | } 150 | #elif HAS_KQUEUE 151 | wd = open(filename, O_RDONLY); 152 | if (wd < 0) { 153 | log_error_errno("Cannot open file \"%s\" for watching", filename); 154 | return false; 155 | } 156 | 157 | uint32_t fflags = NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE | NOTE_ATTRIB; 158 | // NOTE_CLOSE_WRITE is relatively new, so we cannot just use it 159 | #ifdef NOTE_CLOSE_WRITE 160 | fflags |= NOTE_CLOSE_WRITE; 161 | #else 162 | // NOTE_WRITE will receive notification more frequent than necessary, so is less 163 | // preferable 164 | fflags |= NOTE_WRITE; 165 | #endif 166 | struct kevent ev = { 167 | .ident = (unsigned int)wd, // the wd < 0 case is checked above 168 | .filter = EVFILT_VNODE, 169 | .flags = EV_ADD | EV_CLEAR, 170 | .fflags = fflags, 171 | .data = 0, 172 | .udata = NULL, 173 | }; 174 | if (kevent(fwr->w.fd, &ev, 1, NULL, 0, NULL) < 0) { 175 | log_error_errno("Failed to register kevent"); 176 | close(wd); 177 | return false; 178 | } 179 | #else 180 | assert(false); 181 | #endif // HAS_KQUEUE 182 | 183 | auto w = ccalloc(1, struct watched_file); 184 | w->wd = wd; 185 | w->cb = cb; 186 | w->ud = ud; 187 | 188 | HASH_ADD_INT(fwr->reg, wd, w); 189 | return true; 190 | } 191 | -------------------------------------------------------------------------------- /src/utils/file_watch.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | 7 | #include 8 | 9 | typedef void (*file_watch_cb_t)(void *); 10 | 11 | void *file_watch_init(EV_P); 12 | bool file_watch_add(void *, const char *, file_watch_cb_t, void *); 13 | void file_watch_destroy(EV_P_ void *); 14 | -------------------------------------------------------------------------------- /src/utils/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/utils/list.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | #include 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 | /// Splice a list of nodes from `from` to into the beginning of list `to`. 78 | static inline void list_splice(struct list_node *from, struct list_node *to) { 79 | if (list_is_empty(from)) { 80 | return; 81 | } 82 | __list_link(from->prev, to->next); 83 | __list_link(to, from->next); 84 | list_init_head(from); 85 | } 86 | 87 | /// Return true if `to_check` is the first node in list headed by `head` 88 | static inline bool 89 | list_node_is_first(const struct list_node *head, const struct list_node *to_check) { 90 | return head->next == to_check; 91 | } 92 | 93 | /// Return true if `to_check` is the last node in list headed by `head` 94 | static inline bool 95 | list_node_is_last(const struct list_node *head, const struct list_node *to_check) { 96 | return head->prev == to_check; 97 | } 98 | 99 | #define list_foreach(type, i, head, member) \ 100 | for (type *i = list_entry((head)->next, type, member); &i->member != (head); \ 101 | i = list_next_entry(i, member)) 102 | 103 | /// Like list_for_each, but it's safe to remove the current list node from the list 104 | #define list_foreach_safe(type, i, head, member) \ 105 | for (type *i = list_entry((head)->next, type, member), \ 106 | *__tmp = list_next_entry(i, member); \ 107 | &i->member != (head); i = __tmp, __tmp = list_next_entry(i, member)) 108 | -------------------------------------------------------------------------------- /src/utils/meson.build: -------------------------------------------------------------------------------- 1 | srcs += [ 2 | files( 3 | 'cache.c', 4 | 'dynarr.c', 5 | 'file_watch.c', 6 | 'kernel.c', 7 | 'misc.c', 8 | 'statistics.c', 9 | 'str.c', 10 | 'ui.c', 11 | 'process.c', 12 | ), 13 | ] 14 | -------------------------------------------------------------------------------- /src/utils/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 | // clang-format off 38 | #define LIST_APPLY_000000(fn, sep, ...) 39 | #define LIST_APPLY_000001(fn, sep, x, ...) fn(x) 40 | #define LIST_APPLY_000010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000001(fn, sep, __VA_ARGS__) 41 | #define LIST_APPLY_000011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000010(fn, sep, __VA_ARGS__) 42 | #define LIST_APPLY_000100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000011(fn, sep, __VA_ARGS__) 43 | #define LIST_APPLY_000101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000100(fn, sep, __VA_ARGS__) 44 | #define LIST_APPLY_000110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000101(fn, sep, __VA_ARGS__) 45 | #define LIST_APPLY_000111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000110(fn, sep, __VA_ARGS__) 46 | #define LIST_APPLY_001000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000111(fn, sep, __VA_ARGS__) 47 | #define LIST_APPLY_001001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001000(fn, sep, __VA_ARGS__) 48 | #define LIST_APPLY_001010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001001(fn, sep, __VA_ARGS__) 49 | #define LIST_APPLY_001011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001010(fn, sep, __VA_ARGS__) 50 | #define LIST_APPLY_001100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001011(fn, sep, __VA_ARGS__) 51 | #define LIST_APPLY_001101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001100(fn, sep, __VA_ARGS__) 52 | #define LIST_APPLY_001110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001101(fn, sep, __VA_ARGS__) 53 | #define LIST_APPLY_001111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001110(fn, sep, __VA_ARGS__) 54 | #define LIST_APPLY_010000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001111(fn, sep, __VA_ARGS__) 55 | #define LIST_APPLY_010001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010000(fn, sep, __VA_ARGS__) 56 | #define LIST_APPLY_010010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010001(fn, sep, __VA_ARGS__) 57 | #define LIST_APPLY_010011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010010(fn, sep, __VA_ARGS__) 58 | #define LIST_APPLY_010100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010011(fn, sep, __VA_ARGS__) 59 | #define LIST_APPLY_010101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010100(fn, sep, __VA_ARGS__) 60 | #define LIST_APPLY_010110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010101(fn, sep, __VA_ARGS__) 61 | #define LIST_APPLY_010111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010110(fn, sep, __VA_ARGS__) 62 | #define LIST_APPLY_011000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010111(fn, sep, __VA_ARGS__) 63 | #define LIST_APPLY_011001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011000(fn, sep, __VA_ARGS__) 64 | #define LIST_APPLY_011010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011001(fn, sep, __VA_ARGS__) 65 | #define LIST_APPLY_011011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011010(fn, sep, __VA_ARGS__) 66 | #define LIST_APPLY_011100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011011(fn, sep, __VA_ARGS__) 67 | #define LIST_APPLY_011101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011100(fn, sep, __VA_ARGS__) 68 | #define LIST_APPLY_011110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011101(fn, sep, __VA_ARGS__) 69 | #define LIST_APPLY_011111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011110(fn, sep, __VA_ARGS__) 70 | #define LIST_APPLY_(N, fn, sep, ...) CONCAT(LIST_APPLY_, N)(fn, sep, __VA_ARGS__) 71 | #define LIST_APPLY(fn, sep, ...) LIST_APPLY_(VA_ARGS_LENGTH(__VA_ARGS__), fn, sep, __VA_ARGS__) 72 | // clang-format on 73 | 74 | #define SEP_COMMA() , 75 | #define SEP_COLON() ; 76 | #define SEP_NONE() 77 | -------------------------------------------------------------------------------- /src/utils/misc.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "compiler.h" 13 | #include "misc.h" 14 | #include "rtkit.h" 15 | #include "str.h" 16 | 17 | /// Report allocation failure without allocating memory 18 | void report_allocation_failure(const char *func, const char *file, unsigned int line) { 19 | // Since memory allocation failed, we try to print this error message without any 20 | // memory allocation. Since logging framework allocates memory (and might even 21 | // have not been initialized yet), so we can't use it. 22 | char buf[11]; 23 | int llen = uitostr(line, buf); 24 | const char msg1[] = " has failed to allocate memory, "; 25 | const char msg2[] = ". Aborting...\n"; 26 | const struct iovec v[] = { 27 | {.iov_base = (void *)func, .iov_len = strlen(func)}, 28 | {.iov_base = "()", .iov_len = 2}, 29 | {.iov_base = (void *)msg1, .iov_len = sizeof(msg1) - 1}, 30 | {.iov_base = "at ", .iov_len = 3}, 31 | {.iov_base = (void *)file, .iov_len = strlen(file)}, 32 | {.iov_base = ":", .iov_len = 1}, 33 | {.iov_base = buf, .iov_len = (size_t)llen}, 34 | {.iov_base = (void *)msg2, .iov_len = sizeof(msg2) - 1}, 35 | }; 36 | 37 | ssize_t _ attr_unused = writev(STDERR_FILENO, v, ARR_SIZE(v)); 38 | abort(); 39 | 40 | unreachable(); 41 | } 42 | 43 | /// 44 | /// Calculates next closest power of two of 32bit integer n 45 | /// ref: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 46 | /// 47 | int next_power_of_two(int n) { 48 | n--; 49 | n |= n >> 1; 50 | n |= n >> 2; 51 | n |= n >> 4; 52 | n |= n >> 8; 53 | n |= n >> 16; 54 | n++; 55 | return n; 56 | } 57 | // Find the k-th smallest element in an array. 58 | int quickselect(int *elems, int nelem, int k) { 59 | int l = 0, r = nelem; // [l, r) is the range of candidates 60 | while (l != r) { 61 | int pivot = elems[l]; 62 | int i = l, j = r; 63 | while (i < j) { 64 | while (i < j && elems[--j] >= pivot) { 65 | } 66 | elems[i] = elems[j]; 67 | while (i < j && elems[++i] <= pivot) { 68 | } 69 | elems[j] = elems[i]; 70 | } 71 | elems[i] = pivot; 72 | 73 | if (i == k) { 74 | break; 75 | } 76 | 77 | if (i < k) { 78 | l = i + 1; 79 | } else { 80 | r = i; 81 | } 82 | } 83 | return elems[k]; 84 | } 85 | 86 | /// Switch to real-time scheduling policy (SCHED_RR) if possible 87 | /// 88 | /// Make picom realtime to reduce latency, and make rendering times more predictable to 89 | /// help pacing. 90 | /// 91 | /// This requires the user to set up permissions for the real-time scheduling. e.g. by 92 | /// setting `ulimit -r`, or giving us the CAP_SYS_NICE capability. 93 | void set_rr_scheduling(void) { 94 | static thread_local bool already_tried = false; 95 | if (already_tried) { 96 | return; 97 | } 98 | already_tried = true; 99 | 100 | int priority = sched_get_priority_min(SCHED_RR); 101 | 102 | if (rtkit_make_realtime(0, priority)) { 103 | log_info("Set realtime priority to %d with rtkit.", priority); 104 | return; 105 | } 106 | 107 | // Fallback to use pthread_setschedparam 108 | struct sched_param param; 109 | int old_policy; 110 | int ret = pthread_getschedparam(pthread_self(), &old_policy, ¶m); 111 | if (ret != 0) { 112 | log_info("Couldn't get old scheduling priority."); 113 | return; 114 | } 115 | 116 | param.sched_priority = priority; 117 | 118 | ret = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); 119 | if (ret != 0) { 120 | log_info("Couldn't set real-time scheduling priority to %d.", priority); 121 | return; 122 | } 123 | 124 | log_info("Set real-time scheduling priority to %d.", priority); 125 | } 126 | 127 | // vim: set noet sw=8 ts=8 : 128 | -------------------------------------------------------------------------------- /src/utils/process.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "process.h" 7 | #include "x.h" 8 | 9 | extern struct session *ps_g; 10 | 11 | int spawn_picomling(struct x_connection *c) { 12 | int dev_null = open("/dev/null", O_RDWR); 13 | if (dev_null < 0) { 14 | log_error("Failed to open /dev/null"); 15 | return -1; 16 | } 17 | int screen = 0; 18 | auto new_c = xcb_connect(NULL, &screen); 19 | if (xcb_connection_has_error(new_c)) { 20 | log_error("Failed to open new connection"); 21 | close(dev_null); 22 | xcb_disconnect(new_c); 23 | return -1; 24 | } 25 | 26 | pid_t pid = fork(); 27 | if (pid == -1) { 28 | log_error("Failed to fork"); 29 | return -1; 30 | } 31 | 32 | if (pid != 0) { 33 | close(dev_null); 34 | // Close the connection on the parent's side so `xcb_disconnect` won't 35 | // shut it down. 36 | close(xcb_get_file_descriptor(new_c)); 37 | xcb_disconnect(new_c); 38 | return 1; 39 | } 40 | 41 | // Prevent child from using parent's X connection 42 | ps_g = NULL; 43 | 44 | close(STDIN_FILENO); 45 | close(STDOUT_FILENO); 46 | close(STDERR_FILENO); 47 | dup2(dev_null, STDIN_FILENO); 48 | dup2(dev_null, STDOUT_FILENO); 49 | dup2(dev_null, STDERR_FILENO); 50 | 51 | setsid(); 52 | 53 | x_connection_init_xcb(c, new_c, screen); 54 | if (!x_extensions_init(c)) { 55 | return -1; 56 | } 57 | 58 | if (!c->e.has_randr) { 59 | log_error("The X server doesn't have the X RandR extension."); 60 | 61 | return -1; 62 | } 63 | 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /src/utils/process.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2024 Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | struct x_connection; 7 | 8 | /// Fork the process to create a detached underling process. A new connection to the X 9 | /// server is created for the underling. 10 | /// @return 0 in the child process, 1 in the parent process, -1 on failure 11 | int spawn_picomling(struct x_connection *); 12 | -------------------------------------------------------------------------------- /src/utils/statistics.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 "compiler.h" 11 | 12 | #define NTIERS (3) 13 | 14 | struct rolling_window { 15 | int *elem; 16 | int elem_head, nelem; 17 | int window_size; 18 | }; 19 | 20 | void rolling_window_destroy(struct rolling_window *rw); 21 | void rolling_window_reset(struct rolling_window *rw); 22 | void rolling_window_init(struct rolling_window *rw, int size); 23 | int rolling_window_pop_front(struct rolling_window *rw); 24 | bool rolling_window_push_back(struct rolling_window *rw, int val, int *front); 25 | 26 | /// Copy the contents of the rolling window to an array. The array is assumed to 27 | /// have enough space to hold the contents of the rolling window. 28 | static inline void attr_unused rolling_window_copy_to_array(struct rolling_window *rw, 29 | int *arr) { 30 | // The length from head to the end of the array 31 | auto head_len = (size_t)(rw->window_size - rw->elem_head); 32 | if (head_len >= (size_t)rw->nelem) { 33 | memcpy(arr, rw->elem + rw->elem_head, sizeof(int) * (size_t)rw->nelem); 34 | } else { 35 | auto tail_len = (size_t)((size_t)rw->nelem - head_len); 36 | memcpy(arr, rw->elem + rw->elem_head, sizeof(int) * head_len); 37 | memcpy(arr + head_len, rw->elem, sizeof(int) * tail_len); 38 | } 39 | } 40 | 41 | struct rolling_max; 42 | 43 | struct rolling_max *rolling_max_new(int capacity); 44 | void rolling_max_destroy(struct rolling_max *rm); 45 | void rolling_max_reset(struct rolling_max *rm); 46 | void rolling_max_pop_front(struct rolling_max *rm, int front); 47 | void rolling_max_push_back(struct rolling_max *rm, int val); 48 | int rolling_max_get_max(struct rolling_max *rm); 49 | 50 | /// Estimate the mean and variance of random variable X using Welford's online 51 | /// algorithm. 52 | struct cumulative_mean_and_var { 53 | double mean; 54 | double m2; 55 | unsigned int n; 56 | }; 57 | 58 | static inline attr_unused void 59 | cumulative_mean_and_var_init(struct cumulative_mean_and_var *cmv) { 60 | *cmv = (struct cumulative_mean_and_var){0}; 61 | } 62 | 63 | static inline attr_unused void 64 | cumulative_mean_and_var_update(struct cumulative_mean_and_var *cmv, double x) { 65 | if (cmv->n == UINT_MAX) { 66 | // We have too many elements, let's keep the mean and variance. 67 | return; 68 | } 69 | cmv->n++; 70 | double delta = x - cmv->mean; 71 | cmv->mean += delta / (double)cmv->n; 72 | cmv->m2 += delta * (x - cmv->mean); 73 | } 74 | 75 | static inline attr_unused double 76 | cumulative_mean_and_var_get_var(struct cumulative_mean_and_var *cmv) { 77 | if (cmv->n < 2) { 78 | return 0; 79 | } 80 | return cmv->m2 / (double)(cmv->n - 1); 81 | } 82 | 83 | /// A naive quantile estimator. 84 | /// 85 | /// Estimates the N-th percentile of a random variable X in a sliding window. 86 | struct rolling_quantile { 87 | int current_rank; 88 | int min_target_rank, max_target_rank; 89 | int estimate; 90 | int capacity; 91 | int *tmp_buffer; 92 | }; 93 | 94 | void rolling_quantile_init(struct rolling_quantile *rq, int capacity, int mink, int maxk); 95 | void rolling_quantile_init_with_tolerance(struct rolling_quantile *rq, int window_size, 96 | double target, double tolerance); 97 | void rolling_quantile_reset(struct rolling_quantile *rq); 98 | void rolling_quantile_destroy(struct rolling_quantile *rq); 99 | int rolling_quantile_estimate(struct rolling_quantile *rq, struct rolling_window *elements); 100 | void rolling_quantile_push_back(struct rolling_quantile *rq, int x); 101 | void rolling_quantile_pop_front(struct rolling_quantile *rq, int x); 102 | 103 | struct render_statistics { 104 | /// Rolling window of rendering times (in us) and the tiers they belong to. 105 | /// We keep track of the tiers because the vblank time estimate can change over 106 | /// time. 107 | struct rolling_window render_times; 108 | /// Estimate the 95-th percentile of rendering times 109 | struct rolling_quantile render_time_quantile; 110 | /// Time between each vblanks 111 | struct cumulative_mean_and_var vblank_time_us; 112 | }; 113 | 114 | void render_statistics_init(struct render_statistics *rs, int window_size); 115 | void render_statistics_reset(struct render_statistics *rs); 116 | void render_statistics_destroy(struct render_statistics *rs); 117 | 118 | void render_statistics_add_vblank_time_sample(struct render_statistics *rs, int time_us); 119 | void render_statistics_add_render_time_sample(struct render_statistics *rs, int time_us); 120 | 121 | /// How much time budget we should give to the backend for rendering, in microseconds. 122 | unsigned int render_statistics_get_budget(struct render_statistics *rs); 123 | 124 | /// Return the measured vblank interval in microseconds. Returns 0 if not enough 125 | /// samples have been collected yet. 126 | unsigned int render_statistics_get_vblank_time(struct render_statistics *rs); 127 | -------------------------------------------------------------------------------- /src/utils/str.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "str.h" 10 | 11 | #pragma GCC diagnostic push 12 | 13 | // gcc warns about legitimate strncpy in mstrjoin and mstrextend 14 | // strncpy(str, src1, len1) intentional truncates the null byte from src1. 15 | // strncpy(str+len1, src2, len2) uses bound depends on the source argument, 16 | // but str is allocated with len1+len2+1, so this strncpy can't overflow 17 | #pragma GCC diagnostic ignored "-Wpragmas" 18 | #pragma GCC diagnostic ignored "-Wstringop-truncation" 19 | #pragma GCC diagnostic ignored "-Wstringop-overflow" 20 | 21 | /** 22 | * Allocate the space and join two strings. 23 | */ 24 | char *mstrjoin(const char *src1, const char *src2) { 25 | auto len1 = strlen(src1); 26 | auto len2 = strlen(src2); 27 | auto len = len1 + len2 + 1; 28 | auto str = ccalloc(len, char); 29 | 30 | strncpy(str, src1, len1); 31 | strncpy(str + len1, src2, len2); 32 | str[len - 1] = '\0'; 33 | 34 | return str; 35 | } 36 | 37 | TEST_CASE(mstrjoin) { 38 | char *str = mstrjoin("asdf", "qwer"); 39 | TEST_STREQUAL(str, "asdfqwer"); 40 | free(str); 41 | 42 | str = mstrjoin("", "qwer"); 43 | TEST_STREQUAL(str, "qwer"); 44 | free(str); 45 | 46 | str = mstrjoin("asdf", ""); 47 | TEST_STREQUAL(str, "asdf"); 48 | free(str); 49 | } 50 | 51 | /** 52 | * Concatenate a string on heap with another string. 53 | */ 54 | void mstrextend(char **psrc1, const char *src2) { 55 | if (!*psrc1) { 56 | *psrc1 = strdup(src2); 57 | return; 58 | } 59 | 60 | auto len1 = strlen(*psrc1); 61 | auto len2 = strlen(src2); 62 | auto len = len1 + len2 + 1; 63 | *psrc1 = crealloc(*psrc1, len); 64 | 65 | strncpy(*psrc1 + len1, src2, len2); 66 | (*psrc1)[len - 1] = '\0'; 67 | } 68 | 69 | TEST_CASE(mstrextend) { 70 | char *str1 = NULL; 71 | mstrextend(&str1, "asdf"); 72 | TEST_STREQUAL(str1, "asdf"); 73 | 74 | mstrextend(&str1, "asd"); 75 | TEST_STREQUAL(str1, "asdfasd"); 76 | 77 | mstrextend(&str1, ""); 78 | TEST_STREQUAL(str1, "asdfasd"); 79 | free(str1); 80 | } 81 | 82 | #pragma GCC diagnostic pop 83 | 84 | /// Parse a floating point number of form (+|-)?[0-9]*(\.[0-9]*) 85 | double strtod_simple(const char *src, const char **end) { 86 | double neg = 1; 87 | bool succeeded = false; 88 | *end = src; 89 | if (*src == '-') { 90 | neg = -1; 91 | src++; 92 | } else if (*src == '+') { 93 | src++; 94 | } 95 | 96 | double ret = 0; 97 | while (*src >= '0' && *src <= '9') { 98 | ret = ret * 10 + (*src - '0'); 99 | succeeded = true; 100 | src++; 101 | } 102 | 103 | if (*src == '.') { 104 | double frac = 0, mult = 0.1; 105 | src++; 106 | while (*src >= '0' && *src <= '9') { 107 | frac += mult * (*src - '0'); 108 | mult *= 0.1; 109 | succeeded = true; 110 | src++; 111 | } 112 | ret += frac; 113 | } 114 | 115 | if (succeeded) { 116 | *end = src; 117 | return ret * neg; 118 | } 119 | return NAN; 120 | } 121 | 122 | TEST_CASE(strtod_simple) { 123 | const char *end; 124 | double result = strtod_simple("1.0", &end); 125 | TEST_EQUAL(result, 1); 126 | TEST_EQUAL(*end, '\0'); 127 | 128 | result = strtod_simple("-1.0", &end); 129 | TEST_EQUAL(result, -1); 130 | TEST_EQUAL(*end, '\0'); 131 | 132 | result = strtod_simple("+.5", &end); 133 | TEST_EQUAL(result, 0.5); 134 | TEST_EQUAL(*end, '\0'); 135 | 136 | result = strtod_simple("+.", &end); 137 | TEST_TRUE(safe_isnan(result)); 138 | TEST_EQUAL(*end, '+'); 139 | } 140 | 141 | const char *trim_both(const char *src, size_t *length) { 142 | size_t i = 0; 143 | while (isspace(src[i])) { 144 | i++; 145 | } 146 | size_t j = strlen(src) - 1; 147 | while (j > i && isspace(src[j])) { 148 | j--; 149 | } 150 | *length = j - i + 1; 151 | return src + i; 152 | } 153 | 154 | TEST_CASE(trim_both) { 155 | size_t length; 156 | const char *str = trim_both(" \t\n\r\f", &length); 157 | TEST_EQUAL(length, 0); 158 | TEST_EQUAL(*str, '\0'); 159 | 160 | str = trim_both(" asdfas ", &length); 161 | TEST_EQUAL(length, 6); 162 | TEST_STRNEQUAL(str, "asdfas", length); 163 | 164 | str = trim_both(" asdf asdf ", &length); 165 | TEST_EQUAL(length, 9); 166 | TEST_STRNEQUAL(str, "asdf asdf", length); 167 | } 168 | 169 | static int vasnprintf(char **strp, size_t *capacity, const char *fmt, va_list args) { 170 | va_list copy; 171 | va_copy(copy, args); 172 | int needed = vsnprintf(*strp, *capacity, fmt, copy); 173 | va_end(copy); 174 | 175 | if ((size_t)needed + 1 > *capacity) { 176 | char *new_str = malloc((size_t)needed + 1); 177 | allocchk(new_str); 178 | free(*strp); 179 | *strp = new_str; 180 | *capacity = (size_t)needed + 1; 181 | } else { 182 | return needed; 183 | } 184 | 185 | return vsnprintf(*strp, *capacity, fmt, args); 186 | } 187 | 188 | int asnprintf(char **strp, size_t *capacity, const char *fmt, ...) { 189 | va_list args; 190 | va_start(args, fmt); 191 | int ret = vasnprintf(strp, capacity, fmt, args); 192 | va_end(args); 193 | return ret; 194 | } 195 | -------------------------------------------------------------------------------- /src/utils/str.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "misc.h" 14 | 15 | #define mstrncmp(s1, s2) strncmp((s1), (s2), strlen(s1)) 16 | 17 | char *mstrjoin(const char *src1, const char *src2); 18 | char *mstrjoin3(const char *src1, const char *src2, const char *src3); 19 | void mstrextend(char **psrc1, const char *src2); 20 | const char *trim_both(const char *src, size_t *length); 21 | 22 | /// Parse a floating point number of form (+|-)?[0-9]*(\.[0-9]*) 23 | double strtod_simple(const char *, const char **); 24 | 25 | static inline int uitostr(unsigned int n, char *buf) { 26 | int ret = 0; 27 | unsigned int tmp = n; 28 | while (tmp > 0) { 29 | tmp /= 10; 30 | ret++; 31 | } 32 | 33 | if (ret == 0) { 34 | ret = 1; 35 | } 36 | 37 | int pos = ret; 38 | while (pos--) { 39 | buf[pos] = (char)(n % 10 + '0'); 40 | n /= 10; 41 | } 42 | return ret; 43 | } 44 | 45 | /// Like `asprintf`, but it aborts the program if memory allocation fails. 46 | static inline size_t __attribute__((format(printf, 2, 3))) 47 | casprintf(char **strp, const char *fmt, ...) { 48 | va_list ap; 49 | va_start(ap, fmt); 50 | int ret = vasprintf(strp, fmt, ap); 51 | va_end(ap); 52 | 53 | BUG_ON(ret < 0); 54 | return (size_t)ret; 55 | } 56 | 57 | /// Convert a double into a string. Avoid using *printf functions to print floating points 58 | /// directly because they are locale dependent. 59 | static inline void dtostr(double n, char **buf) { 60 | BUG_ON(safe_isnan(n)); 61 | BUG_ON(safe_isinf(n)); 62 | if (fabs(n) > 1e9) { 63 | // The number is so big that it's not meaningful to keep decimal places. 64 | casprintf(buf, "%.0f", n); 65 | return; 66 | } 67 | 68 | if (n > 0) { 69 | casprintf(buf, "%.0f.%03d", floor(n), (int)(fmod(n, 1) * 1000)); 70 | } else { 71 | casprintf(buf, "-%.0f.%03d", floor(-n), (int)(fmod(-n, 1) * 1000)); 72 | } 73 | } 74 | 75 | static inline const char *skip_space_const(const char *src) { 76 | if (!src) { 77 | return NULL; 78 | } 79 | while (*src && isspace((unsigned char)*src)) { 80 | src++; 81 | } 82 | return src; 83 | } 84 | 85 | static inline char *skip_space_mut(char *src) { 86 | if (!src) { 87 | return NULL; 88 | } 89 | while (*src && isspace((unsigned char)*src)) { 90 | src++; 91 | } 92 | return src; 93 | } 94 | 95 | #define skip_space(x) \ 96 | _Generic((x), char *: skip_space_mut, const char *: skip_space_const)(x) 97 | 98 | static inline bool starts_with(const char *str, const char *needle, bool ignore_case) { 99 | if (ignore_case) { 100 | return strncasecmp(str, needle, strlen(needle)) == 0; 101 | } 102 | return strncmp(str, needle, strlen(needle)) == 0; 103 | } 104 | 105 | /// Similar to `asprintf`, but it reuses the allocated memory pointed to by `*strp`, and 106 | /// reallocates it if it's not big enough. 107 | int asnprintf(char **strp, size_t *capacity, const char *fmt, ...) 108 | __attribute__((format(printf, 3, 4))); 109 | -------------------------------------------------------------------------------- /src/utils/ui.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) 2024 Yuxuan Shui 3 | 4 | #include 5 | 6 | struct x_connection; 7 | struct ui; 8 | 9 | enum ui_colors { 10 | UI_COLOR_WHITE, 11 | UI_COLOR_YELLOW, 12 | UI_COLOR_RED, 13 | }; 14 | 15 | enum ui_style { UI_STYLE_NORMAL, UI_STYLE_BOLD }; 16 | 17 | enum ui_justify { UI_JUSTIFY_LEFT, UI_JUSTIFY_CENTER, UI_JUSTIFY_RIGHT }; 18 | 19 | struct ui_message_box_line { 20 | enum ui_colors color; 21 | enum ui_style style; 22 | enum ui_justify justify; 23 | ivec2 position; 24 | ivec2 size; 25 | unsigned pad_bottom; 26 | const char *text; 27 | }; 28 | 29 | struct ui_message_box_content { 30 | unsigned num_lines; 31 | ivec2 size; 32 | unsigned margin; 33 | double scale; 34 | struct ui_message_box_line lines[]; 35 | }; 36 | 37 | /// Layout the content of a message box. 38 | /// @return true if the layout is successful, false if an error occurred. 39 | bool ui_message_box_content_plan(struct ui *ui, struct x_connection *c, 40 | struct ui_message_box_content *content); 41 | bool ui_message_box_show(struct ui *ui, struct x_connection *c, 42 | struct ui_message_box_content *content, unsigned timeout); 43 | /// Initialize necessary resources for displaying UI. 44 | struct ui *ui_new(struct x_connection *c); 45 | void ui_destroy(struct ui *ui, struct x_connection *c); 46 | -------------------------------------------------------------------------------- /src/utils/uthash_extra.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | #define HASH_ITER2(head, el) \ 7 | for (__typeof__(head) el = (head), __tmp = el != NULL ? el->hh.next : NULL; \ 8 | el != NULL; el = __tmp, __tmp = el != NULL ? el->hh.next : NULL) 9 | -------------------------------------------------------------------------------- /src/vblank.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 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "config.h" 15 | #include "x.h" 16 | 17 | /// An object that schedule vblank events. 18 | struct vblank_scheduler; 19 | 20 | struct vblank_event { 21 | uint64_t msc; 22 | uint64_t ust; 23 | }; 24 | 25 | enum vblank_callback_action { 26 | /// The callback should be called again in the next vblank. 27 | VBLANK_CALLBACK_AGAIN, 28 | /// The callback is done and should not be called again. 29 | VBLANK_CALLBACK_DONE, 30 | }; 31 | 32 | typedef enum vblank_callback_action (*vblank_callback_t)(struct vblank_event *event, 33 | void *user_data); 34 | 35 | /// Schedule a vblank event. 36 | /// 37 | /// Schedule for `cb` to be called when the current vblank ends. If this is called 38 | /// from a callback function for the current vblank, the newly scheduled callback 39 | /// will be called in the next vblank. 40 | /// 41 | /// Returns whether the scheduling is successful. Scheduling can fail if there 42 | /// is not enough memory. 43 | bool vblank_scheduler_schedule(struct vblank_scheduler *self, vblank_callback_t cb, 44 | void *user_data); 45 | struct vblank_scheduler * 46 | vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c, xcb_window_t target_window, 47 | enum vblank_scheduler_type type, bool use_realtime_scheduling); 48 | void vblank_scheduler_free(struct vblank_scheduler *); 49 | 50 | bool vblank_handle_x_events(struct vblank_scheduler *self); 51 | -------------------------------------------------------------------------------- /src/wm/defs.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MPL-2.0 2 | // Copyright (c) Yuxuan Shui 3 | 4 | #pragma once 5 | 6 | typedef enum { 7 | WINTYPE_UNKNOWN = 0, 8 | WINTYPE_DESKTOP, 9 | WINTYPE_DOCK, 10 | WINTYPE_TOOLBAR, 11 | WINTYPE_MENU, 12 | WINTYPE_UTILITY, 13 | WINTYPE_SPLASH, 14 | WINTYPE_DIALOG, 15 | WINTYPE_NORMAL, 16 | WINTYPE_DROPDOWN_MENU, 17 | WINTYPE_POPUP_MENU, 18 | WINTYPE_TOOLTIP, 19 | WINTYPE_NOTIFICATION, 20 | WINTYPE_COMBO, 21 | WINTYPE_DND, 22 | NUM_WINTYPES 23 | } wintype_t; 24 | 25 | /// Enumeration type of window painting mode. 26 | typedef enum { 27 | WMODE_TRANS, // The window body is (potentially) transparent 28 | WMODE_FRAME_TRANS, // The window body is opaque, but the frame is not 29 | WMODE_SOLID, // The window is opaque including the frame 30 | } winmode_t; 31 | 32 | /// The state of a window from Xserver's perspective 33 | typedef enum { 34 | /// The window is unmapped. Equivalent to map-state == XCB_MAP_STATE_UNMAPPED 35 | WSTATE_UNMAPPED, 36 | /// The window no longer exists on the X server. 37 | WSTATE_DESTROYED, 38 | /// The window is mapped and viewable. Equivalent to map-state == 39 | /// XCB_MAP_STATE_VIEWABLE 40 | WSTATE_MAPPED, 41 | // XCB_MAP_STATE_UNVIEWABLE is not represented here because it should not be 42 | // possible for top-level windows. 43 | } winstate_t; 44 | 45 | #define NUM_OF_WSTATES (WSTATE_MAPPED + 1) 46 | 47 | enum win_flags { 48 | // Note: *_NONE flags are mostly redundant and meant for detecting logical errors 49 | // in the code 50 | 51 | /// pixmap is out of date, will be update in win_process_flags 52 | WIN_FLAGS_PIXMAP_STALE = 1, 53 | /// there was an error binding the window pixmap 54 | WIN_FLAGS_PIXMAP_ERROR = 4, 55 | /// the client window needs to be updated 56 | WIN_FLAGS_CLIENT_STALE = 32, 57 | /// the window is mapped by X, we need to call map_win_start for it 58 | WIN_FLAGS_MAPPED = 64, 59 | /// this window has properties which needs to be updated 60 | WIN_FLAGS_PROPERTY_STALE = 128, 61 | // TODO(yshui) _maybe_ split SIZE_STALE into SIZE_STALE and SHAPE_STALE 62 | /// this window has an unhandled size/shape change 63 | WIN_FLAGS_SIZE_STALE = 256, 64 | /// this window has an unhandled position (i.e. x and y) change 65 | WIN_FLAGS_POSITION_STALE = 512, 66 | /// need better name for this, is set when some aspects of the window changed 67 | WIN_FLAGS_FACTOR_CHANGED = 1024, 68 | }; 69 | 70 | enum win_script_output { 71 | /// Additional X offset of the window. 72 | WIN_SCRIPT_OFFSET_X = 0, 73 | /// Additional Y offset of the window. 74 | WIN_SCRIPT_OFFSET_Y, 75 | /// Additional X offset of the shadow. 76 | WIN_SCRIPT_SHADOW_OFFSET_X, 77 | /// Additional Y offset of the shadow. 78 | WIN_SCRIPT_SHADOW_OFFSET_Y, 79 | /// Opacity of the window. 80 | WIN_SCRIPT_OPACITY, 81 | /// Opacity of the blurred background of the window. 82 | WIN_SCRIPT_BLUR_OPACITY, 83 | /// Opacity of the shadow. 84 | WIN_SCRIPT_SHADOW_OPACITY, 85 | /// Horizontal scale 86 | WIN_SCRIPT_SCALE_X, 87 | /// Vertical scale 88 | WIN_SCRIPT_SCALE_Y, 89 | /// Horizontal scale of the shadow 90 | WIN_SCRIPT_SHADOW_SCALE_X, 91 | /// Vertical scale of the shadow 92 | WIN_SCRIPT_SHADOW_SCALE_Y, 93 | /// X coordinate of the origin of the crop box 94 | WIN_SCRIPT_CROP_X, 95 | /// Y coordinate of the origin of the crop box 96 | WIN_SCRIPT_CROP_Y, 97 | /// Width of the crop box 98 | WIN_SCRIPT_CROP_WIDTH, 99 | /// Height of the crop box 100 | WIN_SCRIPT_CROP_HEIGHT, 101 | /// How much to blend in the saved window image 102 | WIN_SCRIPT_SAVED_IMAGE_BLEND, 103 | 104 | NUM_OF_WIN_SCRIPT_OUTPUTS, 105 | }; 106 | -------------------------------------------------------------------------------- /src/wm/meson.build: -------------------------------------------------------------------------------- 1 | srcs += [ files('win.c', 'wm.c', 'tree.c') ] 2 | -------------------------------------------------------------------------------- /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/libconfig.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/hyperrealm/libconfig 3 | revision = f9404f60a435aa06321f4ccd8357364dcb216d46 4 | depth = 1 5 | -------------------------------------------------------------------------------- /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/pijulius/picom/e9834631b85da58d1f9cb258a0e020eedda58100/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/issue239_universal.conf: -------------------------------------------------------------------------------- 1 | fading = true; 2 | fade-in-step = 1; 3 | fade-out-step = 0.01; 4 | shadow = true; 5 | rules = ({ 6 | match = "name = 'NoShadow'"; 7 | shadow = false; 8 | }) 9 | -------------------------------------------------------------------------------- /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/pull1091.conf: -------------------------------------------------------------------------------- 1 | unredir-if-possible = true; 2 | -------------------------------------------------------------------------------- /tests/configs/shader.frag: -------------------------------------------------------------------------------- 1 | vec4 window_shader() {} 2 | -------------------------------------------------------------------------------- /tests/run_one_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 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 | picom_exe=$1 10 | config=$2 11 | test_script=$3 12 | 13 | function test_with_backend() { 14 | backend=$1 15 | # TODO keep the log file, and parse it to see if test is successful 16 | ($picom_exe --dbus --backend $backend --log-level=debug --log-file=$PWD/log --config=$config) & 17 | main_pid=$! 18 | $test_script 19 | 20 | kill -INT $main_pid || true 21 | cat log 22 | rm log 23 | wait $main_pid 24 | } 25 | 26 | test_with_backend dummy 27 | test_with_backend xrender 28 | test_with_backend glx 29 | # test_with_backend egl 30 | 31 | -------------------------------------------------------------------------------- /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_universal.conf testcases/issue239.py 12 | ./run_one_test.sh $exe configs/issue239_2.conf testcases/issue239_2.py 13 | ./run_one_test.sh $exe configs/issue239_3.conf testcases/issue239_3.py 14 | ./run_one_test.sh $exe configs/issue239_3.conf testcases/issue239_3_norefresh.py 15 | ./run_one_test.sh $exe configs/issue314.conf testcases/issue314.py 16 | ./run_one_test.sh $exe configs/issue314.conf testcases/issue314_2.py 17 | ./run_one_test.sh $exe configs/issue314.conf testcases/issue314_3.py 18 | ./run_one_test.sh $exe /dev/null testcases/issue299.py 19 | ./run_one_test.sh $exe configs/issue465.conf testcases/issue465.py 20 | ./run_one_test.sh $exe configs/clear_shadow_unredirected.conf testcases/clear_shadow_unredirected.py 21 | ./run_one_test.sh $exe configs/clear_shadow_unredirected.conf testcases/redirect_when_unmapped_window_has_shadow.py 22 | ./run_one_test.sh $exe configs/issue394.conf testcases/issue394.py 23 | ./run_one_test.sh $exe configs/issue239.conf testcases/issue525.py 24 | ./run_one_test.sh $exe configs/pull1091.conf testcases/pull1091.py 25 | -------------------------------------------------------------------------------- /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 set_window_bypass_compositor(conn, wid, value = 1): 37 | prop_name = to_atom(conn, "_NET_WM_BYPASS_COMPOSITOR") 38 | return conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, prop_name, xproto.Atom.CARDINAL, 32, 1, [value]) 39 | 40 | def find_picom_window(conn): 41 | prop_name = to_atom(conn, "WM_NAME") 42 | setup = conn.get_setup() 43 | root = setup.roots[0].root 44 | windows = conn.core.QueryTree(root).reply() 45 | 46 | ext = xproto.xprotoExtension(conn) 47 | for w in windows.children: 48 | name = ext.GetProperty(False, w, prop_name, xproto.GetPropertyType.Any, 0, (2 ** 32) - 1).reply() 49 | if name.value.buf() == b"picom": 50 | return w 51 | 52 | def prepare_root_configure(conn, size = 1000): 53 | setup = conn.get_setup() 54 | root = setup.roots[0].root 55 | # Xorg sends root ConfigureNotify when we add a new mode to an output 56 | rr = conn(randr.key) 57 | name = ''.join([random.choice(string.ascii_letters + string.digits) for n in range(0, 32)]) 58 | mode_info = randr.ModeInfo.synthetic(id = 0, width = size, height = size, dot_clock = 0, 59 | hsync_start = 0, hsync_end = 0, htotal = 0, hskew = 0, vsync_start = 0, vsync_end = 0, 60 | vtotal = 0, name_len = len(name), mode_flags = 0) 61 | 62 | reply = rr.CreateMode(root, mode_info, len(name), name).reply() 63 | mode = reply.mode 64 | reply = rr.GetScreenResourcesCurrent(root).reply() 65 | # our xvfb is setup to only have 1 output 66 | output = reply.outputs[0] 67 | rr.AddOutputModeChecked(output, mode).check() 68 | return reply, mode, output 69 | 70 | def trigger_root_configure(conn, reply, mode, output): 71 | rr = conn(randr.key) 72 | return rr.SetCrtcConfig(reply.crtcs[0], reply.timestamp, reply.config_timestamp, 0, 0, mode, randr.Rotation.Rotate_0, 1, [output]) 73 | 74 | def find_32bit_visual(conn): 75 | setup = conn.get_setup() 76 | render = conn(xcffib.render.key) 77 | r = render.QueryPictFormats().reply() 78 | pictfmt_ids = set() 79 | for pictform in r.formats: 80 | if (pictform.depth == 32 and 81 | pictform.type == xcffib.render.PictType.Direct and 82 | pictform.direct.alpha_mask != 0): 83 | pictfmt_ids.add(pictform.id) 84 | print(pictfmt_ids) 85 | for screen in r.screens: 86 | for depth in screen.depths: 87 | for pv in depth.visuals: 88 | if pv.format in pictfmt_ids: 89 | return pv.visual 90 | -------------------------------------------------------------------------------- /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 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/pull1091.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import xcffib.xproto as xproto 4 | import xcffib 5 | from common import * 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 | # assertion failure mentioned in 1091 happens when a root change happens right after we 14 | # redirected the screen, before we have even rendered a single frame 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 | for i in range(0, 8): 28 | modes = [] 29 | for s in range(0, 10): 30 | reply, mode, output = prepare_root_configure(conn, i * 100 + 100 + s) 31 | modes.append((reply, mode, output)) 32 | 33 | set_window_bypass_compositor(conn, wid).check() 34 | time.sleep(0.1) 35 | 36 | set_window_bypass_compositor(conn, wid, 0) 37 | conn.flush() 38 | for reply, mode, output in modes: 39 | trigger_root_configure(conn, reply, mode, output).reply() 40 | 41 | time.sleep(0.1) 42 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tools/meson.build: -------------------------------------------------------------------------------- 1 | executable( 2 | 'animgen', 3 | 'animgen.c', 4 | dependencies: [ base_deps, libconfig_dep, test_h_dep, cc.find_library('m')], 5 | link_with: [libtools], 6 | build_by_default: false, 7 | include_directories: picom_inc, 8 | ) 9 | --------------------------------------------------------------------------------