├── .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