├── .builds
└── freebsd.yml
├── .circleci
└── config.yml
├── .clang-format
├── .clang-tidy
├── .editorconfig
├── .github
├── issue_template.md
├── pull_request_template.md
└── workflows
│ ├── codeql-analysis.yml
│ ├── coding-style-pr.yml
│ └── coding-style.yml
├── .gitignore
├── .gitmodules
├── CONTRIBUTORS
├── COPYING
├── Doxyfile
├── History.md
├── LICENSE.spdx
├── LICENSES
├── MIT
└── MPL-2.0
├── README.md
├── bin
└── picom-trans
├── compton-default-fshader-win.glsl
├── compton-fake-transparency-fshader-win.glsl
├── compton.desktop
├── dbus-examples
├── cdbus-driver.sh
└── inverter.sh
├── desc.txt
├── flake.lock
├── flake.nix
├── make.sh
├── man
├── meson.build
├── picom-trans.1.asciidoc
└── picom.1.asciidoc
├── media
├── compton.svg
└── icons
│ └── 48x48
│ └── compton.png
├── meson.build
├── meson
└── install.sh
├── meson_options.txt
├── picom-dbus.desktop
├── picom.desktop
├── picom.sample.conf
├── src
├── atom.c
├── atom.h
├── backend
│ ├── backend.c
│ ├── backend.h
│ ├── backend_common.c
│ ├── backend_common.h
│ ├── driver.c
│ ├── driver.h
│ ├── dummy
│ │ └── dummy.c
│ ├── gl
│ │ ├── gl_common.c
│ │ ├── gl_common.h
│ │ ├── glx.c
│ │ └── glx.h
│ ├── meson.build
│ └── xrender
│ │ └── xrender.c
├── c2.c
├── c2.h
├── cache.c
├── cache.h
├── common.h
├── compiler.h
├── config.c
├── config.h
├── config_libconfig.c
├── dbus.c
├── dbus.h
├── diagnostic.c
├── diagnostic.h
├── err.h
├── event.c
├── event.h
├── file_watch.c
├── file_watch.h
├── kernel.c
├── kernel.h
├── list.h
├── log.c
├── log.h
├── meson.build
├── meta.h
├── opengl.c
├── opengl.h
├── options.c
├── options.h
├── picom.c
├── picom.h
├── picom.modulemap
├── region.h
├── render.c
├── render.h
├── string_utils.c
├── string_utils.h
├── types.h
├── uthash_extra.h
├── utils.c
├── utils.h
├── vsync.c
├── vsync.h
├── win.c
├── win.h
├── win_defs.h
├── x.c
├── x.h
├── xrescheck.c
└── xrescheck.h
├── subprojects
└── test.h
│ ├── meson.build
│ └── test.h
└── tests
├── configs
├── clear_shadow_unredirected.conf
├── empty.conf
├── issue239.conf
├── issue239_2.conf
├── issue239_3.conf
├── issue314.conf
├── issue357.conf
├── issue394.conf
└── issue465.conf
├── run_one_test.sh
├── run_tests.sh
└── testcases
├── basic.py
├── clear_shadow_unredirected.py
├── common.py
├── issue239.py
├── issue239_2.py
├── issue239_3.py
├── issue239_3_norefresh.py
├── issue299.py
├── issue314.py
├── issue314_2.py
├── issue314_3.py
├── issue357.py
├── issue394.py
├── issue465.py
├── issue525.py
└── redirect_when_unmapped_window_has_shadow.py
/.builds/freebsd.yml:
--------------------------------------------------------------------------------
1 | image: freebsd/latest
2 | packages:
3 | - libev
4 | - libXext
5 | - libxcb
6 | - meson
7 | - pkgconf
8 | - cmake
9 | - xcb-util-renderutil
10 | - xcb-util-image
11 | - pixman
12 | - uthash
13 | - libconfig
14 | - libglvnd
15 | - dbus
16 | - pcre
17 | sources:
18 | - https://github.com/yshui/picom
19 | tasks:
20 | - setup: |
21 | cd picom
22 | CPPFLAGS="-I/usr/local/include" meson -Dunittest=true build
23 | - build: |
24 | cd picom
25 | ninja -C build
26 | - unittest: |
27 | cd picom
28 | ninja -C build test
29 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | executors:
2 | e:
3 | docker:
4 | - image: yshui/comptonci
5 | working_directory: "/tmp/workspace"
6 | environment:
7 | UBSAN_OPTIONS: "halt_on_error=1"
8 |
9 | version: 2.1
10 | commands:
11 | build:
12 | parameters:
13 | build-config:
14 | type: string
15 | default: ""
16 | cc:
17 | type: string
18 | default: cc
19 | steps:
20 | - restore_cache:
21 | keys:
22 | - source-v1-{{ .Branch }}-{{ .Revision }}
23 | - source-v1-{{ .Branch }}-
24 | - source-v1-
25 | - checkout
26 | - save_cache:
27 | key: source-v1-{{ .Branch }}-{{ .Revision }}
28 | paths:
29 | - ".git"
30 | - run:
31 | name: config
32 | command: CC=<< parameters.cc >> meson << parameters.build-config >> -Dunittest=true --werror . build
33 | - run:
34 | name: build
35 | command: ninja -vC build
36 |
37 | jobs:
38 | basic:
39 | executor: e
40 | steps:
41 | - build:
42 | build-config: -Dwith_docs=true -Db_coverage=true
43 | - persist_to_workspace:
44 | root: .
45 | paths:
46 | - .
47 | test:
48 | executor: e
49 | steps:
50 | - attach_workspace:
51 | at: /tmp/workspace
52 | - run:
53 | name: unit test
54 | command: ninja -vC build test
55 | - run:
56 | name: test config file parsing
57 | command: xvfb-run -s "-screen 0 640x480x24" build/src/picom --config picom.sample.conf --no-vsync --diagnostics
58 | - run:
59 | name: run testsuite
60 | command: tests/run_tests.sh build/src/picom
61 | - run:
62 | name: generate coverage reports
63 | command: cd build; find -name '*.gcno' -exec gcov -pb {} +
64 | - run:
65 | name: download codecov scripts
66 | command: curl -s https://codecov.io/bash > codecov.sh
67 | - run:
68 | name: upload coverage reports
69 | command: bash ./codecov.sh -X gcov
70 |
71 | minimal:
72 | executor: e
73 | steps:
74 | - build:
75 | build-config: -Dopengl=false -Ddbus=false -Dregex=false -Dconfig_file=false
76 | release:
77 | executor: e
78 | steps:
79 | - build:
80 | build-config: --buildtype=release
81 | release-clang:
82 | executor: e
83 | steps:
84 | - build:
85 | cc: clang
86 | build-config: --buildtype=release
87 | nogl:
88 | executor: e
89 | steps:
90 | - build:
91 | build-config: -Dopengl=false
92 | noregex:
93 | executor: e
94 | steps:
95 | - build:
96 | build-config: -Dregex=false
97 | clang_basic:
98 | executor: e
99 | steps:
100 | - build:
101 | cc: clang
102 | clang_minimal:
103 | executor: e
104 | steps:
105 | - build:
106 | cc: clang
107 | build-config: -Dopengl=false -Ddbus=false -Dregex=false -Dconfig_file=false
108 | clang_nogl:
109 | executor: e
110 | steps:
111 | - build:
112 | cc: clang
113 | build-config: -Dopengl=false
114 | clang_noregex:
115 | executor: e
116 | steps:
117 | - build:
118 | cc: clang
119 | build-config: -Dregex=false
120 |
121 | workflows:
122 | all_builds:
123 | jobs:
124 | - basic
125 | - clang_basic
126 | - minimal
127 | - clang_minimal
128 | - nogl
129 | - clang_nogl
130 | - release
131 | - release-clang
132 | - test:
133 | requires:
134 | - basic
135 | # vim: set sw=2 ts=8 et:
136 |
--------------------------------------------------------------------------------
/.clang-format:
--------------------------------------------------------------------------------
1 | BasedOnStyle: LLVM
2 | TabWidth: 8
3 | UseTab: ForIndentation
4 | BreakBeforeBraces: Attach
5 | #BreakStringLiterals: true
6 | IndentWidth: 8
7 | AlignAfterOpenBracket: Align
8 | ColumnLimit: 90
9 | #ExperimentalAutoDetectBinPacking: true
10 | BinPackArguments: true
11 | BinPackParameters: true
12 | #ReflowComments: true
13 | AlignTrailingComments: true
14 | SpacesBeforeTrailingComments: 8
15 | SpaceBeforeAssignmentOperators: true
16 | SpaceBeforeParens: ControlStatements
17 | AllowShortIfStatementsOnASingleLine: false
18 | AllowShortCaseLabelsOnASingleLine: true
19 | AllowShortFunctionsOnASingleLine: false
20 | IndentCaseLabels: false
21 | IndentPPDirectives: None
22 | PenaltyReturnTypeOnItsOwnLine: 0
23 | PenaltyBreakAssignment: 0
24 | PenaltyBreakBeforeFirstCallParameter: 1
25 | PenaltyBreakComment: 1
26 | PenaltyBreakString: 36
27 | PenaltyExcessCharacter: 3
28 | PenaltyBreakFirstLessLess: 0
29 | PenaltyBreakTemplateDeclaration: 0
30 | BreakBeforeBinaryOperators: None
31 | IncludeCategories:
32 | - Regex: '<.*\.h>'
33 | Priority: 1
34 | - Regex: '".*\.h"'
35 | Priority: 2
36 | SortIncludes: true
37 | #ForEachMacros: [ list_for_each_entry, list_for_each_entry_safe, HASH_ITER ]
38 | #AlignConsecutiveAssignments: true
39 |
--------------------------------------------------------------------------------
/.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 | AnalyzeTemporaryDtors: false
16 | FormatStyle: file
17 | CheckOptions:
18 | - key: readability-magic-numbers.IgnoredIntegerValues
19 | value: 4;8;16;24;32;1;2;3;4096;65536;
20 | - key: readability-magic-numbers.IgnoredFloatingPointValues
21 | value: 255.0;1.0;
22 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 | [*.{c,h}]
3 | indent_style = tab
4 | indent_size = 8
5 | max_line_length = 90
6 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ### Platform
4 |
5 |
6 | ### GPU, drivers, and screen setup
7 |
12 |
13 | ### Environment
14 |
15 |
16 | ### picom version
17 |
18 |
19 |
20 | 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 | ### Other details
57 |
58 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [next]
6 | pull_request:
7 | branches: [next]
8 |
9 | jobs:
10 | analyze:
11 | name: Analyze
12 | runs-on: ubuntu-latest
13 |
14 | strategy:
15 | fail-fast: false
16 | matrix:
17 | language: ['cpp', 'python']
18 |
19 | steps:
20 | - name: Checkout repository
21 | uses: actions/checkout@v2
22 | with:
23 | # We must fetch at least the immediate parents so that if this is
24 | # a pull request then we can checkout the head.
25 | fetch-depth: 2
26 |
27 | # If this run was triggered by a pull request event, then checkout
28 | # the head of the pull request instead of the merge commit.
29 | - run: git checkout HEAD^2
30 | if: ${{ github.event_name == 'pull_request' }}
31 |
32 | # Initializes the CodeQL tools for scanning.
33 | - name: Initialize CodeQL
34 | uses: github/codeql-action/init@v1
35 | with:
36 | languages: ${{ matrix.language }}
37 |
38 | # Install dependencies
39 | - run: sudo apt update && sudo apt install libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl1-mesa-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ninja-build
40 | if: ${{ matrix.language == 'cpp' }}
41 |
42 | # Autobuild
43 | - name: Autobuild
44 | uses: github/codeql-action/autobuild@v1
45 |
46 | - name: Perform CodeQL Analysis
47 | uses: github/codeql-action/analyze@v1
48 |
--------------------------------------------------------------------------------
/.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@v2
10 | - run: git fetch --depth=1 origin ${{ github.event.pull_request.base.sha }}
11 | - uses: yshui/git-clang-format-lint@v1.11
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@v2
10 | with:
11 | fetch-depth: 2
12 | - uses: yshui/git-clang-format-lint@v1.12
13 | with:
14 | base: ${{ github.event.ref }}~1
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Build files
2 | make.sh
3 | .deps
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 |
38 | # Vim files
39 | .sw[a-z]
40 | .*.sw[a-z]
41 | *~
42 |
43 | # Misc files
44 | core.*
45 | .gdb_history
46 | oprofile_data/
47 | compton.plist
48 | callgrind.out.*
49 | man/*.html
50 | man/*.1
51 | doxygen/
52 | .clang_complete
53 | .ycm_extra_conf.py
54 | .ycm_extra_conf.pyc
55 | /src/backtrace-symbols.[ch]
56 | /compton*.trace
57 | *.orig
58 | /tests/log
59 | /tests/testcases/__pycache__/
60 |
61 | # Nix
62 | result
63 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yaocccc/picom/eddcf51dc10182b50cdbb22f11f155a836a8aa53/.gitmodules
--------------------------------------------------------------------------------
/CONTRIBUTORS:
--------------------------------------------------------------------------------
1 | Sorted in alphabetical order
2 | Open an issue or pull request if you don't want your name listed here.
3 |
4 | Adam Jackson
5 | adelin-b
6 | Alexander Kapshuna
7 | Antonin Décimo
8 | Antonio Vivace
9 | Avi-D-coder
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 | Greg Flynn
30 | Harish Rajagopal
31 | hasufell
32 | Ignacio Taranto
33 | Istvan Petres
34 | James Cloos
35 | Jamey Sharp
36 | Jan Beich
37 | Jarrad
38 | Javeed Shaikh
39 | Jerónimo Navarro
40 | jialeens
41 | Johnny Pribyl
42 | Keith Packard
43 | Kevin Kelley
44 | ktprograms
45 | Lukas Schmelzeisen
46 | mæp
47 | Mark Tiefenbruck
48 | Matthew Allum
49 | Maxim Solovyov
50 | Michael Reed
51 | Michele Lambertucci
52 | Namkhai Bourquin
53 | Nate Hart
54 | nia
55 | notfoss
56 | Omar Polo
57 | orbea
58 | @Paradigm0001
59 | Patrick Collins
60 | Peter Mattern
61 | Phil Blundell
62 | Que Quotion
63 | Rafael Kitover
64 | Richard Grenville
65 | Rytis Karpuska
66 | Samuel Hand
67 | Scott Leggett
68 | scrouthtv
69 | Sebastien Waegeneire
70 | Subhaditya Nath
71 | Tasos Sahanidis
72 | Thiago Kenji Okada
73 | Tilman Sauerbeck
74 | Tim van Dalen
75 | Tomas Janousek
76 | Tom Dörr
77 | Toni Jarjour
78 | Tuomas Kinnunen
79 | Uli Schlachter
80 | Walter Lapchynski
81 | Will Dietz
82 | XeCycle
83 | Yuxuan Shui
84 | zilrich
85 | ಠ_ಠ
86 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | picom - a compositor for X11
2 |
3 | Based on xcompmgr, originally written by Keith Packard, with modifications
4 | from several contributors (according to the xcompmgr man page): Matthew Allum,
5 | Eric Anholt, Dan Doel, Thomas Luebking, Matthew Hawn, Ely Levy, Phil Blundell,
6 | and Carl Worth. Menu transparency was implemented by Dana Jansens.
7 |
8 | Numerous contributions to picom from Richard Grenville.
9 |
10 | See the CONTRIBUTORS file for a complete list of contributors
11 |
12 | This source code is provided under:
13 |
14 | SPDX-License-Identifier: MPL-2.0 AND MIT
15 |
16 | And the preferred license for new source files in this project is:
17 |
18 | SPDX-License-Identifier: MPL-2.0
19 |
20 | You can find the text of the licenses in the LICENSES directory.
21 |
--------------------------------------------------------------------------------
/History.md:
--------------------------------------------------------------------------------
1 | # Picom History
2 |
3 | Picom was forked in 2016 from the original Compton because it seemed to have become unmaintained.
4 |
5 | The battle plan of the fork was to refactor it to make the code _possible_ to maintain, so potential contributors won't be scared away when they take a look at the code.
6 |
7 | And also to try to fix bugs.
8 |
9 | ## Rename
10 |
11 | In 2019 the project name was changed from Compton to picom (git revision 8ddbeb and following).
12 |
13 | ### Rationale
14 |
15 | Since the inception of this fork, the existence of two compton repositories has caused some number of confusions. Mainly, people will report issues of this fork to the original compton, or report issues of the original compton here. Later, when distros started packaging this fork of compton, some wanted to differentiate the newer compton from the older version. They found themselves having no choice but to invent a name for this fork. This is less than ideal since this has the potential to cause more confusions among users.
16 |
17 | Therefore, we decided to move this fork to a new name. Personally, I consider this more than justified since this version of compton has gone through significant changes since it was forked.
18 |
19 | ### The name
20 |
21 | The criteria for a good name were
22 |
23 | 0. Being short, so it's easy to remember.
24 | 1. Pronounceability, again, helps memorability
25 | 2. Searchability, so when people search the name, it's easy for them to find this repository.
26 |
27 | Of course, choosing a name is never easy, and there is no apparent way to objectively evaluate the names. Yet, we have to solve the aforementioned problems as soon as possible.
28 |
29 | In the end, we picked `picom` (a portmanteau of `pico` and `composite`) as our new name. This name might not be perfect, but is what we will move forward with unless there's a compelling reason not to.
30 |
31 |
32 | # Compton
33 |
34 | This is a copy of the README of the [original Compton project](https://github.com/chjj/compton/).
35 |
36 | [](https://gitter.im/chjj/compton?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
37 |
38 | __Compton__ is a compositor for X, and a fork of __xcompmgr-dana__.
39 |
40 | I was frustrated by the low amount of standalone lightweight compositors.
41 | Compton was forked from Dana Jansens' fork of xcompmgr and refactored. I fixed
42 | whatever bug I found, and added features I wanted. Things seem stable, but don't
43 | quote me on it. I will most likely be actively working on this until I get the
44 | features I want. This is also a learning experience for me. That is, I'm
45 | partially doing this out of a desire to learn Xlib.
46 |
47 | ## Changes from xcompmgr:
48 |
49 | * OpenGL backend (`--backend glx`), in addition to the old X Render backend.
50 | * Inactive window transparency (`-i`) / dimming (`--inactive-dim`).
51 | * Titlebar/frame transparency (`-e`).
52 | * Menu transparency (`-m`, thanks to Dana).
53 | * shadows are now enabled for argb windows, e.g. terminals with transparency
54 | * removed serverside shadows (and simple compositing) to clean the code,
55 | the only option that remains is clientside shadows
56 | * configuration files (see the man page for more details)
57 | * colored shadows (`--shadow-[red/green/blue]`)
58 | * a new fade system
59 | * VSync support (not always working)
60 | * Blur of background of transparent windows, window color inversion (bad in performance)
61 | * Some more options...
62 |
63 | ## Fixes from the original xcompmgr:
64 |
65 | * fixed a segfault when opening certain window types
66 | * fixed a memory leak caused by not freeing up shadows (from the freedesktop
67 | repo)
68 | * fixed the conflict with chromium and similar windows
69 | * [many more](https://github.com/chjj/compton/issues)
70 |
71 | ## Building
72 |
73 | ### Dependencies:
74 |
75 | __B__ for build-time
76 |
77 | __R__ for runtime
78 |
79 | * libx11 (B,R)
80 | * libxcomposite (B,R)
81 | * libxdamage (B,R)
82 | * libxfixes (B,R)
83 | * libXext (B,R)
84 | * libxrender (B,R)
85 | * libXrandr (B,R)
86 | * libXinerama (B,R) (Can be disabled with `NO_XINERAMA` at compile time)
87 | * pkg-config (B)
88 | * make (B)
89 | * xproto / x11proto (B)
90 | * sh (R)
91 | * xprop,xwininfo / x11-utils (R)
92 | * libpcre (B,R) (Can be disabled with `NO_REGEX_PCRE` at compile time)
93 | * libconfig (B,R) (Can be disabled with `NO_LIBCONFIG` at compile time)
94 | * libdrm (B) (Can be disabled with `NO_VSYNC_DRM` at compile time)
95 | * libGL (B,R) (Can be disabled with `NO_VSYNC_OPENGL` at compile time)
96 | * libdbus (B,R) (Can be disabled with `NO_DBUS` at compile time)
97 | * asciidoc (B) (and docbook-xml-dtd-4.5, libxml-utils, libxslt, xsltproc, xmlto, etc. if your distro doesn't pull them in)
98 |
99 | ### How to build
100 |
101 | To build, make sure you have the dependencies above:
102 |
103 | ```bash
104 | # Make the main program
105 | $ make
106 | # Make the man pages
107 | $ make docs
108 | # Install
109 | $ make install
110 | ```
111 |
112 | (Compton does include a `_CMakeLists.txt` in the tree, but we haven't decided whether we should switch to CMake yet. The `Makefile` is fully usable right now.)
113 |
114 | ## Known issues
115 |
116 | * Our [FAQ](https://github.com/chjj/compton/wiki/faq) covers some known issues.
117 |
118 | * VSync does not work too well. You may check the [VSync Guide](https://github.com/chjj/compton/wiki/vsync-guide) for how to get (possibly) better effects.
119 |
120 | * If `--unredir-if-possible` is enabled, when compton redirects/unredirects windows, the screen may flicker. Using `--paint-on-overlay` minimizes the problem from my observation, yet I do not know if there's a cure.
121 |
122 | * compton may not track focus correctly in all situations. The focus tracking code is experimental. `--use-ewmh-active-win` might be helpful.
123 |
124 | * The performance of blur under X Render backend might be pretty bad. OpenGL backend could be faster.
125 |
126 | * With `--blur-background` you may sometimes see weird lines around damaged area. `--resize-damage YOUR_BLUR_RADIUS` might be helpful in the case.
127 |
128 | ## Usage
129 |
130 | Please refer to the Asciidoc man pages (`man/compton.1.asciidoc` & `man/compton-trans.1.asciidoc`) for more details and examples.
131 |
132 | Note a sample configuration file `compton.sample.conf` is included in the repository.
133 |
134 | ## Support
135 |
136 | * Bug reports and feature requests should go to the "Issues" section above.
137 |
138 | * Our (semi?) official IRC channel is #compton on FreeNode.
139 |
140 | * Some information is available on the wiki, including [FAQ](https://github.com/chjj/compton/wiki/faq), [VSync Guide](https://github.com/chjj/compton/wiki/vsync-guide), and [Performance Guide](https://github.com/chjj/compton/wiki/perf-guide).
141 |
142 | ## License
143 |
144 | Although compton has kind of taken on a life of its own, it was originally
145 | an xcompmgr fork. xcompmgr has gotten around. As far as I can tell, the lineage
146 | for this particular tree is something like:
147 |
148 | * Keith Packard (original author)
149 | * Matthew Hawn
150 | * ...
151 | * Dana Jansens
152 | * chjj and richardgv
153 |
154 | Not counting the tens of people who forked it in between.
155 |
156 | Compton is distributed under MIT license, as far as I (richardgv) know. See LICENSE for more info.
157 |
--------------------------------------------------------------------------------
/LICENSE.spdx:
--------------------------------------------------------------------------------
1 | SPDXVersion: SPDX-2.1
2 | DataLicense: CC0-1.0
3 | PackageName: picom
4 | PackageLicenseDeclared: MPL-2.0 AND MIT
5 |
--------------------------------------------------------------------------------
/LICENSES/MIT:
--------------------------------------------------------------------------------
1 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
2 |
3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
4 |
5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | picom
2 | =====
3 |
4 | __picom__ is a compositor for X, and a [fork of Compton](History.md).
5 |
6 | **This is a development branch, bugs to be expected**
7 |
8 | You can leave your feedback or thoughts in the [discussion tab](https://github.com/yshui/picom/discussions).
9 |
10 | ## Call for testers
11 |
12 | ### `--experimental-backends`
13 |
14 | This flag enables the refactored/partially rewritten backends.
15 |
16 | Currently, new backends feature better vsync with the xrender backend and improved input lag with the glx backend (for non-NVIDIA users). The performance should be on par with the old backends.
17 |
18 | New backend features will only be implemented on the new backends from now on, and the old backends will eventually be phased out after the new backends stabilize.
19 |
20 | To test the new backends, add the `--experimental-backends` flag to the command you use to run picom. This flag is not available from the configuration file.
21 |
22 | To report issues with the new backends, please state explicitly you are using the new backends in your report.
23 |
24 | ## Change Log
25 |
26 | See [Releases](https://github.com/yshui/picom/releases)
27 |
28 | ## Build
29 |
30 | ### Dependencies
31 |
32 | Assuming you already have all the usual building tools installed (e.g. gcc, python, meson, ninja, etc.), you still need:
33 |
34 | * libx11
35 | * libx11-xcb
36 | * libXext
37 | * xproto
38 | * xcb
39 | * xcb-damage
40 | * xcb-xfixes
41 | * xcb-shape
42 | * xcb-renderutil
43 | * xcb-render
44 | * xcb-randr
45 | * xcb-composite
46 | * xcb-image
47 | * xcb-present
48 | * xcb-xinerama
49 | * xcb-glx
50 | * pixman
51 | * libdbus (optional, disable with the `-Ddbus=false` meson configure flag)
52 | * libconfig (optional, disable with the `-Dconfig_file=false` meson configure flag)
53 | * libGL (optional, disable with the `-Dopengl=false` meson configure flag)
54 | * libpcre (optional, disable with the `-Dregex=false` meson configure flag)
55 | * libev
56 | * uthash
57 |
58 | On Debian based distributions (e.g. Ubuntu), the needed packages are
59 |
60 | ```
61 | libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl1-mesa-dev libpcre2-dev libpcre3-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson
62 | ```
63 |
64 | On Fedora, the needed packages are
65 |
66 | ```
67 | dbus-devel gcc git libconfig-devel libdrm-devel libev-devel libX11-devel libX11-xcb libXext-devel libxcb-devel mesa-libGL-devel meson pcre-devel pixman-devel uthash-devel xcb-util-image-devel xcb-util-renderutil-devel xorg-x11-proto-devel
68 | ```
69 |
70 | To build the documents, you need `asciidoc`
71 |
72 | ### To build
73 |
74 | ```bash
75 | $ git submodule update --init --recursive
76 | $ meson --buildtype=release . build
77 | $ ninja -C build
78 | ```
79 |
80 | Built binary can be found in `build/src`
81 |
82 | If you have libraries and/or headers installed at non-default location (e.g. under `/usr/local/`), you might need to tell meson about them, since meson doesn't look for dependencies there by default.
83 |
84 | You can do that by setting the `CPPFLAGS` and `LDFLAGS` environment variables when running `meson`. Like this:
85 |
86 | ```bash
87 | $ LDFLAGS="-L/path/to/libraries" CPPFLAGS="-I/path/to/headers" meson --buildtype=release . build
88 |
89 | ```
90 |
91 | As an example, on FreeBSD, you might have to run meson with:
92 | ```bash
93 | $ LDFLAGS="-L/usr/local/lib" CPPFLAGS="-I/usr/local/include" meson --buildtype=release . build
94 | $ ninja -C build
95 | ```
96 |
97 | ### To install
98 |
99 | ``` bash
100 | $ ninja -C build install
101 | ```
102 |
103 |
104 | Default install prefix is `/usr/local`, you can change it with `meson configure -Dprefix= build`
105 | ### Nix Flake
106 | Here's an example of using it in a nixos configuration
107 | ```Nix
108 | {
109 | description = "My configuration";
110 |
111 | inputs = {
112 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
113 | picom.url = "github:yaocccc/picom";
114 | };
115 |
116 | outputs = { nixpkgs, picom, ... }:
117 | {
118 | nixosConfigurations = {
119 | hostname = nixpkgs.lib.nixosSystem
120 | {
121 | system = "x86_64-linux";
122 | modules = [
123 | {
124 | nixpkgs.overlays = [ picom.overlays.default ];
125 | environment.systemPackages = with pkgs;[
126 | picom
127 | ];
128 | }
129 | ];
130 | };
131 | };
132 | };
133 | }
134 | ```
135 |
136 | ## How to Contribute
137 |
138 | ### Code
139 |
140 | You can look at the [Projects](https://github.com/yshui/picom/projects) page, and see if there is anything that interests you. Or you can take a look at the [Issues](https://github.com/yshui/picom/issues).
141 |
142 | ### Non-code
143 |
144 | Even if you don't want to contribute code, you can still contribute by compiling and running this branch, and report any issue you can find.
145 |
146 | Contributions to the documents and wiki will also be appreciated.
147 |
148 | ## Contributors
149 |
150 | See [CONTRIBUTORS](CONTRIBUTORS)
151 |
152 | The README for the [original Compton project](https://github.com/chjj/compton/) can be found [here](History.md#Compton).
153 |
154 | ## Licensing
155 |
156 | picom is free software, made available under the [MIT](LICENSES/MIT) and [MPL-2.0](LICENSES/MPL-2.0) software
157 | licenses. See the individual source files for details.
158 |
--------------------------------------------------------------------------------
/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": 1726560853,
9 | "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
10 | "owner": "numtide",
11 | "repo": "flake-utils",
12 | "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
13 | "type": "github"
14 | },
15 | "original": {
16 | "owner": "numtide",
17 | "repo": "flake-utils",
18 | "type": "github"
19 | }
20 | },
21 | "nixpkgs": {
22 | "locked": {
23 | "lastModified": 1726937504,
24 | "narHash": "sha256-bvGoiQBvponpZh8ClUcmJ6QnsNKw0EMrCQJARK3bI1c=",
25 | "owner": "NixOS",
26 | "repo": "nixpkgs",
27 | "rev": "9357f4f23713673f310988025d9dc261c20e70c6",
28 | "type": "github"
29 | },
30 | "original": {
31 | "id": "nixpkgs",
32 | "ref": "nixos-unstable",
33 | "type": "indirect"
34 | }
35 | },
36 | "root": {
37 | "inputs": {
38 | "flake-utils": "flake-utils",
39 | "nixpkgs": "nixpkgs"
40 | }
41 | },
42 | "systems": {
43 | "locked": {
44 | "lastModified": 1681028828,
45 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
46 | "owner": "nix-systems",
47 | "repo": "default",
48 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
49 | "type": "github"
50 | },
51 | "original": {
52 | "owner": "nix-systems",
53 | "repo": "default",
54 | "type": "github"
55 | }
56 | }
57 | },
58 | "root": "root",
59 | "version": 7
60 | }
61 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | inputs = {
3 | nixpkgs.url = "nixpkgs/nixos-unstable";
4 | flake-utils.url = "github:numtide/flake-utils";
5 | };
6 |
7 | outputs =
8 | { self
9 | , nixpkgs
10 | , flake-utils
11 | ,
12 | }:
13 | let
14 | overlay =
15 | final: prev: {
16 | picom = prev.picom.overrideAttrs (oldAttrs: rec {
17 | version = "master";
18 | src = ./.;
19 |
20 | buildInputs = (oldAttrs.buildInputs or [ ]) ++ [ prev.pcre ];
21 | });
22 | };
23 | in
24 | flake-utils.lib.eachDefaultSystem
25 | (
26 | system:
27 | let
28 | pkgs = import nixpkgs {
29 | inherit system;
30 | overlays = [
31 | self.overlays.default
32 | ];
33 | };
34 | in
35 | rec {
36 | packages.picom = pkgs.picom;
37 | packages.default = pkgs.picom;
38 | }
39 | )
40 | // { overlays.default = overlay; };
41 | }
42 |
--------------------------------------------------------------------------------
/make.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | git submodule update --init --recursive
4 | meson --buildtype=release . build
5 | ninja -C build
6 | meson configure -Dprefix=/usr build
7 | sudo ninja -C build install
8 |
--------------------------------------------------------------------------------
/man/meson.build:
--------------------------------------------------------------------------------
1 | mans = ['picom.1', 'picom-trans.1']
2 | if get_option('with_docs')
3 | a2x = find_program('a2x')
4 | foreach m : mans
5 | custom_target(m, output: [m], input: [m+'.asciidoc'],
6 | command: [a2x, '-a',
7 | 'picom-version='+version,
8 | '--format', 'manpage', '@INPUT@', '-D',
9 | meson.current_build_dir()],
10 | install: true,
11 | install_dir: join_paths(get_option('mandir'), 'man1'))
12 | endforeach
13 | endif
14 |
--------------------------------------------------------------------------------
/man/picom-trans.1.asciidoc:
--------------------------------------------------------------------------------
1 | picom-trans(1)
2 | ================
3 | :doctype: manpage
4 | :man source: picom
5 | :man version: {picom-version}
6 | :man manual: User Commands
7 |
8 | NAME
9 | ----
10 | picom-trans - an opacity setter tool
11 |
12 | SYNOPSIS
13 | --------
14 |
15 | *picom-trans* [-w 'WINDOW_ID'] [-n 'WINDOW_NAME'] [-c] [-s] 'OPACITY'
16 |
17 | DESCRIPTION
18 | -----------
19 |
20 | *picom-trans* is a bash script that sets '_NET_WM_WINDOW_OPACITY' attribute of a window using standard X11 command-line utilities, including *xprop*(1) and *xwininfo*(1). It is similar to *transset*(1) or *transset-df*(1).
21 |
22 | OPTIONS
23 | -------
24 | *-w*, *--window*='WINDOW_ID'::
25 | Specify the window id of the target window.
26 |
27 | *-n*, *--name*='WINDOW_NAME'::
28 | Specify and try to match a window name.
29 |
30 | *-c*, *--current*::
31 | Specify the currently active window as target. Only works if EWMH '_NET_ACTIVE_WINDOW' property exists on root window.
32 |
33 | *-s*, *--select*::
34 | Select target window with mouse cursor. This is the default if no window has been specified.
35 |
36 | *-o*, *--opacity*='OPACITY'::
37 | Specify the new opacity value for the window. This value can be anywhere from 1-100. If it is prefixed with a plus or minus (+/-), this will increment or decrement from the target window's current opacity instead.
38 |
39 | *-g*, *--get*::
40 | Print the target window's opacity instead of setting it.
41 |
42 | *-d*, *--delete*::
43 | Delete opacity of the target window instead of setting it.
44 |
45 | *-t*, *--toggle*::
46 | Toggle the target window's opacity: Set opacity if not already set, and delete if already set.
47 |
48 | *-r*, *--reset*::
49 | Reset opacity for all windows instead of setting it.
50 |
51 | EXAMPLES
52 | --------
53 |
54 | * Set the opacity of the window with specific window ID to 75%:
55 | +
56 | ------------
57 | picom-trans -w "$WINDOWID" 75
58 | ------------
59 |
60 | * Set the opacity of the window with the name "urxvt" to 75%:
61 | +
62 | ------------
63 | picom-trans -n "urxvt" 75
64 | ------------
65 |
66 | * Set current window to opacity of 75%:
67 | +
68 | ------------
69 | picom-trans -c 75
70 | ------------
71 |
72 | * Select target window and set opacity to 75%:
73 | +
74 | ------------
75 | picom-trans -s 75
76 | ------------
77 |
78 | * Increment opacity of current active window by 5%:
79 | +
80 | ------------
81 | picom-trans -c +5
82 | ------------
83 |
84 | * Decrement opacity of current active window by 5%:
85 | +
86 | ------------
87 | picom-trans -c -- -5
88 | ------------
89 |
90 | * Delete current window's opacity:
91 | +
92 | ------------
93 | picom-trans -c --delete
94 | ------------
95 |
96 | * Toggle current window's opacity between 90 and unset
97 | +
98 | ------------
99 | picom-trans -c --toggle 90
100 | ------------
101 |
102 | * Reset all windows:
103 | +
104 | ------------
105 | picom-trans --reset
106 | ------------
107 |
108 | BUGS
109 | ----
110 | Please submit bug reports to .
111 |
112 | SEE ALSO
113 | --------
114 | link:picom.1.html[*picom*(1)], *xprop*(1), *xwininfo*(1)
115 |
--------------------------------------------------------------------------------
/media/compton.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
77 |
--------------------------------------------------------------------------------
/media/icons/48x48/compton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yaocccc/picom/eddcf51dc10182b50cdbb22f11f155a836a8aa53/media/icons/48x48/compton.png
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | project('picom', 'c', version: '9',
2 | default_options: ['c_std=c11'])
3 |
4 | cc = meson.get_compiler('c')
5 |
6 | # use project version by default
7 | version = 'v'+meson.project_version()
8 |
9 | # use git describe if that's available
10 | git = find_program('git', required: false)
11 | if git.found()
12 | gitv = run_command('git', 'rev-parse', '--short=5', 'HEAD')
13 | if gitv.returncode() == 0
14 | version = 'vgit-'+gitv.stdout().strip()
15 | endif
16 | endif
17 |
18 | add_global_arguments('-DCOMPTON_VERSION="'+version+'"', language: 'c')
19 |
20 | if get_option('buildtype') == 'release'
21 | add_global_arguments('-DNDEBUG', language: 'c')
22 | endif
23 |
24 | if get_option('sanitize')
25 | sanitizers = ['address', 'undefined']
26 | if cc.has_argument('-fsanitize=integer')
27 | sanitizers += ['integer']
28 | endif
29 | if cc.has_argument('-fsanitize=nullability')
30 | sanitizers += ['nullability']
31 | endif
32 | add_global_arguments('-fsanitize='+','.join(sanitizers), language: 'c')
33 | add_global_link_arguments('-fsanitize='+','.join(sanitizers), language: 'c')
34 | if cc.has_argument('-fno-sanitize=unsigned-integer-overflow')
35 | add_global_arguments('-fno-sanitize=unsigned-integer-overflow', language: 'c')
36 | endif
37 | endif
38 |
39 | if get_option('modularize')
40 | if not cc.has_argument('-fmodules')
41 | error('option \'modularize\' requires clang')
42 | endif
43 | add_global_arguments(['-fmodules',
44 | '-fmodule-map-file='+
45 | meson.current_source_dir()+
46 | '/src/picom.modulemap'],
47 | language: 'c')
48 | endif
49 |
50 | add_global_arguments('-D_GNU_SOURCE', language: 'c')
51 |
52 | if cc.has_header('stdc-predef.h')
53 | add_global_arguments('-DHAS_STDC_PREDEF_H', language: 'c')
54 | endif
55 |
56 | warns = [ 'all', 'cast-function-type', 'ignored-qualifiers', 'missing-parameter-type',
57 | 'nonnull', 'shadow', 'no-type-limits', 'old-style-declaration', 'override-init',
58 | 'sign-compare', 'type-limits', 'uninitialized', 'shift-negative-value',
59 | 'unused-but-set-parameter', 'unused-parameter', 'implicit-fallthrough',
60 | 'no-unknown-warning-option', 'no-missing-braces', 'conversion', 'empty-body' ]
61 | foreach w : warns
62 | if cc.has_argument('-W'+w)
63 | add_global_arguments('-W'+w, language: 'c')
64 | endif
65 | endforeach
66 |
67 | test_h_dep = subproject('test.h').get_variable('test_h_dep')
68 |
69 | subdir('src')
70 | subdir('man')
71 |
72 | install_data('bin/picom-trans', install_dir: get_option('bindir'))
73 | install_data('picom.desktop', install_dir: 'share/applications')
74 |
75 | if get_option('compton')
76 | install_data('compton.desktop', install_dir: 'share/applications')
77 | install_data('media/icons/48x48/compton.png',
78 | install_dir: 'share/icons/hicolor/48x48/apps')
79 | install_data('media/compton.svg',
80 | install_dir: 'share/icons/hicolor/scalable/apps')
81 |
82 | meson.add_install_script('meson/install.sh')
83 | endif
84 |
--------------------------------------------------------------------------------
/meson/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if [ ! -e "${MESON_INSTALL_DESTDIR_PREFIX}/bin/compton" ]; then
4 | echo "Linking picom to ${MESON_INSTALL_DESTDIR_PREFIX}/bin/compton"
5 | ln -s picom "${MESON_INSTALL_DESTDIR_PREFIX}/bin/compton"
6 | fi
7 |
8 | if [ ! -e "${MESON_INSTALL_DESTDIR_PREFIX}/bin/compton-trans" ]; then
9 | echo "Linking picom-trans to ${MESON_INSTALL_DESTDIR_PREFIX}/bin/compton-trans"
10 | ln -s picom-trans "${MESON_INSTALL_DESTDIR_PREFIX}/bin/compton-trans"
11 | fi
12 |
--------------------------------------------------------------------------------
/meson_options.txt:
--------------------------------------------------------------------------------
1 | option('sanitize', type: 'boolean', value: false, description: 'Build with sanitizers enabled (deprecated)')
2 | option('config_file', type: 'boolean', value: true, description: 'Enable config file support')
3 | option('regex', type: 'boolean', value: true, description: 'Enable regex support in window conditions')
4 |
5 | option('vsync_drm', type: 'boolean', value: false, description: 'Enable support for using drm for vsync')
6 |
7 | option('opengl', type: 'boolean', value: true, description: 'Enable features that require opengl (opengl backend, and opengl vsync methods)')
8 | option('dbus', type: 'boolean', value: true, description: 'Enable support for D-Bus remote control')
9 |
10 | option('xrescheck', type: 'boolean', value: false, description: 'Enable X resource leak checker (for debug only)')
11 |
12 | option('compton', type: 'boolean', value: true, description: 'Install backwards compat with compton')
13 |
14 | option('with_docs', type: 'boolean', value: false, description: 'Build documentation and man pages')
15 |
16 | option('modularize', type: 'boolean', value: false, description: 'Build with clang\'s module system')
17 |
18 | option('unittest', type: 'boolean', value: false, description: 'Enable unittests in the code')
19 |
--------------------------------------------------------------------------------
/picom-dbus.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Version=1.0
3 | Type=Application
4 | NoDisplay=true
5 | Name=picom (dbus)
6 | GenericName=X compositor (dbus)
7 | Comment=An X compositor with dbus backend enabled
8 | Categories=Utility;
9 | Keywords=compositor;composite manager;window effects;transparency;opacity;
10 | TryExec=picom
11 | Exec=picom --dbus
12 |
--------------------------------------------------------------------------------
/picom.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Version=1.0
3 | Type=Application
4 | NoDisplay=false
5 | Name=picom
6 | GenericName=X compositor
7 | Comment=An X compositor
8 | Categories=Utility;
9 | Keywords=compositor;composite manager;window effects;transparency;opacity;
10 | TryExec=picom
11 | Exec=picom
12 | # Thanks to quequotion for providing this file!
13 | Icon=picom
14 |
--------------------------------------------------------------------------------
/src/atom.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include "atom.h"
5 | #include "common.h"
6 | #include "utils.h"
7 | #include "log.h"
8 |
9 | static inline void *atom_getter(void *ud, const char *atom_name, int *err) {
10 | xcb_connection_t *c = ud;
11 | xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(
12 | c, xcb_intern_atom(c, 0, to_u16_checked(strlen(atom_name)), atom_name), NULL);
13 |
14 | xcb_atom_t atom = XCB_NONE;
15 | if (reply) {
16 | log_debug("Atom %s is %d", atom_name, reply->atom);
17 | atom = reply->atom;
18 | free(reply);
19 | } else {
20 | log_error("Failed to intern atoms");
21 | *err = 1;
22 | }
23 | return (void *)(intptr_t)atom;
24 | }
25 |
26 | /**
27 | * Create a new atom structure and fetch all predefined atoms
28 | */
29 | struct atom *init_atoms(xcb_connection_t *c) {
30 | auto atoms = ccalloc(1, struct atom);
31 | atoms->c = new_cache((void *)c, atom_getter, NULL);
32 | #define ATOM_GET(x) atoms->a##x = (xcb_atom_t)(intptr_t)cache_get(atoms->c, #x, NULL)
33 | LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST1);
34 | LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST2);
35 | #undef ATOM_GET
36 | return atoms;
37 | }
38 |
--------------------------------------------------------------------------------
/src/atom.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 |
4 | #include
5 |
6 | #include "meta.h"
7 | #include "cache.h"
8 |
9 | // clang-format off
10 | // Splitted into 2 lists because of the limitation of our macros
11 | #define ATOM_LIST1 \
12 | _NET_WM_WINDOW_OPACITY, \
13 | _NET_FRAME_EXTENTS, \
14 | WM_STATE, \
15 | _NET_WM_NAME, \
16 | _NET_WM_PID, \
17 | WM_NAME, \
18 | WM_CLASS, \
19 | WM_ICON_NAME, \
20 | WM_TRANSIENT_FOR, \
21 | WM_WINDOW_ROLE, \
22 | WM_CLIENT_LEADER, \
23 | WM_CLIENT_MACHINE, \
24 | _NET_ACTIVE_WINDOW, \
25 | _COMPTON_SHADOW, \
26 | _NET_WM_WINDOW_TYPE, \
27 | _NET_CURRENT_MON_CENTER
28 |
29 | #define ATOM_LIST2 \
30 | _NET_WM_WINDOW_TYPE_DESKTOP, \
31 | _NET_WM_WINDOW_TYPE_DOCK, \
32 | _NET_WM_WINDOW_TYPE_TOOLBAR, \
33 | _NET_WM_WINDOW_TYPE_MENU, \
34 | _NET_WM_WINDOW_TYPE_UTILITY, \
35 | _NET_WM_WINDOW_TYPE_SPLASH, \
36 | _NET_WM_WINDOW_TYPE_DIALOG, \
37 | _NET_WM_WINDOW_TYPE_NORMAL, \
38 | _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, \
39 | _NET_WM_WINDOW_TYPE_POPUP_MENU, \
40 | _NET_WM_WINDOW_TYPE_TOOLTIP, \
41 | _NET_WM_WINDOW_TYPE_NOTIFICATION, \
42 | _NET_WM_WINDOW_TYPE_COMBO, \
43 | _NET_WM_WINDOW_TYPE_DND, \
44 | _NET_WM_STATE, \
45 | _NET_WM_STATE_FULLSCREEN, \
46 | _NET_WM_BYPASS_COMPOSITOR, \
47 | UTF8_STRING, \
48 | C_STRING
49 | // clang-format on
50 |
51 | #define ATOM_DEF(x) xcb_atom_t a##x
52 |
53 | struct atom {
54 | struct cache *c;
55 | LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST1);
56 | LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST2);
57 | };
58 |
59 | struct atom *init_atoms(xcb_connection_t *);
60 |
61 | static inline xcb_atom_t get_atom(struct atom *a, const char *key) {
62 | return (xcb_atom_t)(intptr_t)cache_get(a->c, key, NULL);
63 | }
64 |
65 | static inline void destroy_atoms(struct atom *a) {
66 | cache_free(a->c);
67 | free(a);
68 | }
69 |
--------------------------------------------------------------------------------
/src/backend/backend_common.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) Yuxuan Shui
3 | #pragma once
4 |
5 | #include
6 | #include
7 |
8 | #include
9 |
10 | #include "backend.h"
11 | #include "config.h"
12 | #include "region.h"
13 |
14 | typedef struct session session_t;
15 | typedef struct win win;
16 | typedef struct conv conv;
17 | typedef struct backend_base backend_t;
18 | struct backend_operations;
19 |
20 | struct dual_kawase_params {
21 | /// Number of downsample passes
22 | int iterations;
23 | /// Pixel offset for down- and upsample
24 | float offset;
25 | /// Save area around blur target (@ref resize_width, @ref resize_height)
26 | int expand;
27 | };
28 |
29 | struct backend_image_inner_base {
30 | int refcount;
31 | bool has_alpha;
32 | };
33 |
34 | struct backend_image {
35 | // Backend dependent inner image data
36 | struct backend_image_inner_base *inner;
37 | double opacity;
38 | double dim;
39 | double max_brightness;
40 | double corner_radius;
41 | // Effective size of the image
42 | int ewidth, eheight;
43 | bool color_inverted;
44 | int border_width;
45 | };
46 |
47 | bool build_shadow(xcb_connection_t *, xcb_drawable_t, double opacity, int width,
48 | int height, const conv *kernel, xcb_render_picture_t shadow_pixel,
49 | xcb_pixmap_t *pixmap, xcb_render_picture_t *pict);
50 |
51 | xcb_render_picture_t solid_picture(xcb_connection_t *, xcb_drawable_t, bool argb,
52 | double a, double r, double g, double b);
53 |
54 | xcb_image_t *
55 | make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, int height);
56 |
57 | /// The default implementation of `is_win_transparent`, it simply looks at win::mode. So
58 | /// this is not suitable for backends that alter the content of windows
59 | bool default_is_win_transparent(void *, win *, void *);
60 |
61 | /// The default implementation of `is_frame_transparent`, it uses win::frame_opacity. Same
62 | /// caveat as `default_is_win_transparent` applies.
63 | bool default_is_frame_transparent(void *, win *, void *);
64 |
65 | void *
66 | default_backend_render_shadow(backend_t *backend_data, int width, int height,
67 | const conv *kernel, double r, double g, double b, double a);
68 |
69 | void init_backend_base(struct backend_base *base, session_t *ps);
70 |
71 | struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count);
72 | struct dual_kawase_params *generate_dual_kawase_params(void *args);
73 |
74 | void *default_clone_image(backend_t *base, const void *image_data, const region_t *reg);
75 | void *default_resize_image(backend_t *base, const void *image_data, uint16_t desired_width,
76 | uint16_t desired_height, const region_t *reg);
77 | bool default_is_image_transparent(backend_t *base attr_unused, void *image_data);
78 | bool default_set_image_property(backend_t *base attr_unused, enum image_properties op,
79 | void *image_data, void *arg);
80 | struct backend_image *default_new_backend_image(int w, int h);
81 |
--------------------------------------------------------------------------------
/src/backend/driver.c:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) Yuxuan Shui
3 | #include
4 | #include
5 |
6 | #include
7 | #include
8 |
9 | #include "backend/backend.h"
10 | #include "backend/driver.h"
11 | #include "common.h"
12 | #include "compiler.h"
13 | #include "log.h"
14 |
15 | /// Apply driver specified global workarounds. It's safe to call this multiple times.
16 | void apply_driver_workarounds(struct session *ps, enum driver driver) {
17 | if (driver & DRIVER_NVIDIA) {
18 | // setenv("__GL_YIELD", "usleep", true);
19 | setenv("__GL_MaxFramesAllowed", "1", true);
20 | ps->o.xrender_sync_fence = true;
21 | }
22 | }
23 |
24 | enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) {
25 | enum driver ret = 0;
26 | // First we try doing backend agnostic detection using RANDR
27 | // There's no way to query the X server about what driver is loaded, so RANDR is
28 | // our best shot.
29 | auto randr_version = xcb_randr_query_version_reply(
30 | c, xcb_randr_query_version(c, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION),
31 | NULL);
32 | if (randr_version &&
33 | (randr_version->major_version > 1 || randr_version->minor_version >= 4)) {
34 | auto r = xcb_randr_get_providers_reply(
35 | c, xcb_randr_get_providers(c, window), NULL);
36 | if (r == NULL) {
37 | log_warn("Failed to get RANDR providers");
38 | free(randr_version);
39 | return 0;
40 | }
41 |
42 | auto providers = xcb_randr_get_providers_providers(r);
43 | for (auto i = 0; i < xcb_randr_get_providers_providers_length(r); i++) {
44 | auto r2 = xcb_randr_get_provider_info_reply(
45 | c, xcb_randr_get_provider_info(c, providers[i], r->timestamp), NULL);
46 | if (r2 == NULL) {
47 | continue;
48 | }
49 | if (r2->num_outputs == 0) {
50 | free(r2);
51 | continue;
52 | }
53 |
54 | auto name_len = xcb_randr_get_provider_info_name_length(r2);
55 | assert(name_len >= 0);
56 | auto name =
57 | strndup(xcb_randr_get_provider_info_name(r2), (size_t)name_len);
58 | if (strcasestr(name, "modesetting") != NULL) {
59 | ret |= DRIVER_MODESETTING;
60 | } else if (strcasestr(name, "Radeon") != NULL) {
61 | // Be conservative, add both radeon drivers
62 | ret |= DRIVER_AMDGPU | DRIVER_RADEON;
63 | } else if (strcasestr(name, "NVIDIA") != NULL) {
64 | ret |= DRIVER_NVIDIA;
65 | } else if (strcasestr(name, "nouveau") != NULL) {
66 | ret |= DRIVER_NOUVEAU;
67 | } else if (strcasestr(name, "Intel") != NULL) {
68 | ret |= DRIVER_INTEL;
69 | }
70 | free(name);
71 | free(r2);
72 | }
73 | free(r);
74 | }
75 | free(randr_version);
76 |
77 | // If the backend supports driver detection, use that as well
78 | if (backend_data && backend_data->ops->detect_driver) {
79 | ret |= backend_data->ops->detect_driver(backend_data);
80 | }
81 | return ret;
82 | }
83 |
--------------------------------------------------------------------------------
/src/backend/driver.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) Yuxuan Shui
3 |
4 | #pragma once
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | #include "utils.h"
11 |
12 | struct session;
13 | struct backend_base;
14 |
15 | // A list of known driver quirks:
16 | // * NVIDIA driver doesn't like seeing the same pixmap under different
17 | // ids, so avoid naming the pixmap again when it didn't actually change.
18 |
19 | /// A list of possible drivers.
20 | /// The driver situation is a bit complicated. There are two drivers we care about: the
21 | /// DDX, and the OpenGL driver. They are usually paired, but not always, since there is
22 | /// also the generic modesetting driver.
23 | /// This enum represents _both_ drivers.
24 | enum driver {
25 | DRIVER_AMDGPU = 1, // AMDGPU for DDX, radeonsi for OpenGL
26 | DRIVER_RADEON = 2, // ATI for DDX, mesa r600 for OpenGL
27 | DRIVER_FGLRX = 4,
28 | DRIVER_NVIDIA = 8,
29 | DRIVER_NOUVEAU = 16,
30 | DRIVER_INTEL = 32,
31 | DRIVER_MODESETTING = 64,
32 | };
33 |
34 | static const char *driver_names[] = {
35 | "AMDGPU", "Radeon", "fglrx", "NVIDIA", "nouveau", "Intel", "modesetting",
36 | };
37 |
38 | /// Return a list of all drivers currently in use by the X server.
39 | /// Note, this is a best-effort test, so no guarantee all drivers will be detected.
40 | enum driver detect_driver(xcb_connection_t *, struct backend_base *, xcb_window_t);
41 |
42 | /// Apply driver specified global workarounds. It's safe to call this multiple times.
43 | void apply_driver_workarounds(struct session *ps, enum driver);
44 |
45 | // Print driver names to stdout, for diagnostics
46 | static inline void print_drivers(enum driver drivers) {
47 | const char *seen_drivers[ARR_SIZE(driver_names)];
48 | int driver_count = 0;
49 | for (size_t i = 0; i < ARR_SIZE(driver_names); i++) {
50 | if (drivers & (1ul << i)) {
51 | seen_drivers[driver_count++] = driver_names[i];
52 | }
53 | }
54 |
55 | if (driver_count > 0) {
56 | printf("%s", seen_drivers[0]);
57 | for (int i = 1; i < driver_count; i++) {
58 | printf(", %s", seen_drivers[i]);
59 | }
60 | }
61 | printf("\n");
62 | }
63 |
--------------------------------------------------------------------------------
/src/backend/dummy/dummy.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include "backend/backend.h"
5 | #include "backend/backend_common.h"
6 | #include "common.h"
7 | #include "compiler.h"
8 | #include "config.h"
9 | #include "log.h"
10 | #include "region.h"
11 | #include "types.h"
12 | #include "uthash_extra.h"
13 | #include "utils.h"
14 | #include "x.h"
15 |
16 | struct dummy_image {
17 | xcb_pixmap_t pixmap;
18 | bool transparent;
19 | int *refcount;
20 | UT_hash_handle hh;
21 | };
22 |
23 | struct dummy_data {
24 | struct backend_base base;
25 | struct dummy_image *images;
26 | };
27 |
28 | struct backend_base *dummy_init(struct session *ps attr_unused) {
29 | auto ret = (struct backend_base *)ccalloc(1, struct dummy_data);
30 | ret->c = ps->c;
31 | ret->loop = ps->loop;
32 | ret->root = ps->root;
33 | ret->busy = false;
34 | return ret;
35 | }
36 |
37 | void dummy_deinit(struct backend_base *data) {
38 | auto dummy = (struct dummy_data *)data;
39 | HASH_ITER2(dummy->images, img) {
40 | log_warn("Backend image for pixmap %#010x is not freed", img->pixmap);
41 | HASH_DEL(dummy->images, img);
42 | free(img->refcount);
43 | free(img);
44 | }
45 | free(dummy);
46 | }
47 |
48 | static void dummy_check_image(struct backend_base *base, const struct dummy_image *img) {
49 | auto dummy = (struct dummy_data *)base;
50 | struct dummy_image *tmp = NULL;
51 | HASH_FIND_INT(dummy->images, &img->pixmap, tmp);
52 | if (!tmp) {
53 | log_warn("Using an invalid (possibly freed) image");
54 | assert(false);
55 | }
56 | assert(*tmp->refcount > 0);
57 | }
58 |
59 | void dummy_compose(struct backend_base *base, void *image, int dst_x1 attr_unused,
60 | int dst_y1 attr_unused, int dst_x2 attr_unused, int dst_y2 attr_unused,
61 | const region_t *reg_paint attr_unused, const region_t *reg_visible attr_unused) {
62 | dummy_check_image(base, image);
63 | }
64 |
65 | void dummy_fill(struct backend_base *backend_data attr_unused, struct color c attr_unused,
66 | const region_t *clip attr_unused) {
67 | }
68 |
69 | bool dummy_blur(struct backend_base *backend_data attr_unused, double opacity attr_unused,
70 | void *blur_ctx attr_unused, const region_t *reg_blur attr_unused,
71 | const region_t *reg_visible attr_unused) {
72 | return true;
73 | }
74 |
75 | void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap,
76 | struct xvisual_info fmt, bool owned attr_unused) {
77 | auto dummy = (struct dummy_data *)base;
78 | struct dummy_image *img = NULL;
79 | HASH_FIND_INT(dummy->images, &pixmap, img);
80 | if (img) {
81 | (*img->refcount)++;
82 | return img;
83 | }
84 |
85 | img = ccalloc(1, struct dummy_image);
86 | img->pixmap = pixmap;
87 | img->transparent = fmt.alpha_size != 0;
88 | img->refcount = ccalloc(1, int);
89 | *img->refcount = 1;
90 |
91 | HASH_ADD_INT(dummy->images, pixmap, img);
92 | return (void *)img;
93 | }
94 |
95 | void dummy_release_image(backend_t *base, void *image) {
96 | auto dummy = (struct dummy_data *)base;
97 | auto img = (struct dummy_image *)image;
98 | assert(*img->refcount > 0);
99 | (*img->refcount)--;
100 | if (*img->refcount == 0) {
101 | HASH_DEL(dummy->images, img);
102 | free(img->refcount);
103 | free(img);
104 | }
105 | }
106 |
107 | bool dummy_is_image_transparent(struct backend_base *base, void *image) {
108 | auto img = (struct dummy_image *)image;
109 | dummy_check_image(base, img);
110 | return img->transparent;
111 | }
112 |
113 | int dummy_buffer_age(struct backend_base *base attr_unused) {
114 | return 2;
115 | }
116 |
117 | bool dummy_image_op(struct backend_base *base, enum image_operations op attr_unused,
118 | void *image, const region_t *reg_op attr_unused,
119 | const region_t *reg_visible attr_unused, void *args attr_unused) {
120 | dummy_check_image(base, image);
121 | return true;
122 | }
123 |
124 | bool dummy_set_image_property(struct backend_base *base, enum image_properties prop attr_unused,
125 | void *image, void *arg attr_unused) {
126 | dummy_check_image(base, image);
127 | return true;
128 | }
129 |
130 | void *dummy_clone_image(struct backend_base *base, const void *image,
131 | const region_t *reg_visible attr_unused) {
132 | auto img = (const struct dummy_image *)image;
133 | dummy_check_image(base, img);
134 | (*img->refcount)++;
135 | return (void *)img;
136 | }
137 |
138 | void *dummy_create_blur_context(struct backend_base *base attr_unused,
139 | enum blur_method method attr_unused, void *args attr_unused) {
140 | static int dummy_context;
141 | return &dummy_context;
142 | }
143 |
144 | void dummy_destroy_blur_context(struct backend_base *base attr_unused, void *ctx attr_unused) {
145 | }
146 |
147 | void dummy_get_blur_size(void *ctx attr_unused, int *width, int *height) {
148 | // These numbers are arbitrary, to make sure the reisze_region code path is
149 | // covered.
150 | *width = 5;
151 | *height = 5;
152 | }
153 |
154 | struct backend_operations dummy_ops = {
155 | .init = dummy_init,
156 | .deinit = dummy_deinit,
157 | .compose = dummy_compose,
158 | .fill = dummy_fill,
159 | .blur = dummy_blur,
160 | .bind_pixmap = dummy_bind_pixmap,
161 | .render_shadow = default_backend_render_shadow,
162 | .release_image = dummy_release_image,
163 | .is_image_transparent = dummy_is_image_transparent,
164 | .buffer_age = dummy_buffer_age,
165 | .max_buffer_age = 5,
166 |
167 | .image_op = dummy_image_op,
168 | .clone_image = dummy_clone_image,
169 | .set_image_property = dummy_set_image_property,
170 | .create_blur_context = dummy_create_blur_context,
171 | .destroy_blur_context = dummy_destroy_blur_context,
172 | .get_blur_size = dummy_get_blur_size,
173 |
174 | };
175 |
--------------------------------------------------------------------------------
/src/backend/gl/gl_common.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) Yuxuan Shui
3 | #pragma once
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "backend/backend.h"
10 | #include "log.h"
11 | #include "region.h"
12 |
13 | #define CASESTRRET(s) \
14 | case s: return #s
15 |
16 | static inline GLint glGetUniformLocationChecked(GLuint p, const char *name) {
17 | auto ret = glGetUniformLocation(p, name);
18 | if (ret < 0) {
19 | log_info("Failed to get location of uniform '%s'. This is normal when "
20 | "using custom shaders.",
21 | name);
22 | }
23 | return ret;
24 | }
25 |
26 | #define bind_uniform(shader, uniform) \
27 | (shader)->uniform_##uniform = glGetUniformLocationChecked((shader)->prog, #uniform)
28 |
29 | // Program and uniforms for window shader
30 | typedef struct {
31 | GLuint prog;
32 | GLint uniform_opacity;
33 | GLint uniform_invert_color;
34 | GLint uniform_tex;
35 | GLint uniform_dim;
36 | GLint uniform_brightness;
37 | GLint uniform_max_brightness;
38 | GLint uniform_corner_radius;
39 | GLint uniform_border_width;
40 | } gl_win_shader_t;
41 |
42 | // Program and uniforms for brightness shader
43 | typedef struct {
44 | GLuint prog;
45 | } gl_brightness_shader_t;
46 |
47 | // Program and uniforms for blur shader
48 | typedef struct {
49 | GLuint prog;
50 | GLint uniform_pixel_norm;
51 | GLint uniform_opacity;
52 | GLint texorig_loc;
53 | GLint scale_loc;
54 | } gl_blur_shader_t;
55 |
56 | typedef struct {
57 | GLuint prog;
58 | GLint color_loc;
59 | } gl_fill_shader_t;
60 |
61 | /// @brief Wrapper of a binded GLX texture.
62 | struct gl_texture {
63 | int refcount;
64 | bool has_alpha;
65 | GLuint texture;
66 | int width, height;
67 | bool y_inverted;
68 |
69 | // Textures for auxiliary uses.
70 | GLuint auxiliary_texture[2];
71 | void *user_data;
72 | };
73 |
74 | struct gl_data {
75 | backend_t base;
76 | // If we are using proprietary NVIDIA driver
77 | bool is_nvidia;
78 | // If ARB_robustness extension is present
79 | bool has_robustness;
80 | // Height and width of the root window
81 | int height, width;
82 | gl_win_shader_t win_shader;
83 | gl_brightness_shader_t brightness_shader;
84 | gl_fill_shader_t fill_shader;
85 | GLuint back_texture, back_fbo;
86 | GLuint present_prog;
87 |
88 | /// Called when an gl_texture is decoupled from the texture it refers. Returns
89 | /// the decoupled user_data
90 | void *(*decouple_texture_user_data)(backend_t *base, void *user_data);
91 |
92 | /// Release the user data attached to a gl_texture
93 | void (*release_user_data)(backend_t *base, struct gl_texture *);
94 |
95 | struct log_target *logger;
96 | };
97 |
98 | typedef struct session session_t;
99 |
100 | #define GL_PROG_MAIN_INIT \
101 | { .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, .unifm_tex = -1, }
102 |
103 | GLuint gl_create_shader(GLenum shader_type, const char *shader_str);
104 | GLuint gl_create_program(const GLuint *const shaders, int nshaders);
105 | GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str);
106 |
107 | /**
108 | * @brief Render a region with texture data.
109 | */
110 | void gl_compose(backend_t *, void *ptex,
111 | int dst_x1, int dst_y1, int dst_x2, int dst_y2,
112 | const region_t *reg_tgt, const region_t *reg_visible);
113 |
114 | void gl_resize(struct gl_data *, int width, int height);
115 |
116 | bool gl_init(struct gl_data *gd, session_t *);
117 | void gl_deinit(struct gl_data *gd);
118 |
119 | GLuint gl_new_texture(GLenum target);
120 |
121 | bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
122 | const region_t *reg_op, const region_t *reg_visible, void *arg);
123 |
124 | void gl_release_image(backend_t *base, void *image_data);
125 |
126 | void *gl_clone(backend_t *base, const void *image_data, const region_t *reg_visible);
127 |
128 | bool gl_blur(backend_t *base, double opacity, void *, const region_t *reg_blur,
129 | const region_t *reg_visible);
130 | void *gl_create_blur_context(backend_t *base, enum blur_method, void *args);
131 | void gl_destroy_blur_context(backend_t *base, void *ctx);
132 | void gl_get_blur_size(void *blur_context, int *width, int *height);
133 |
134 | void gl_fill(backend_t *base, struct color, const region_t *clip);
135 |
136 | void gl_present(backend_t *base, const region_t *);
137 | bool gl_read_pixel(backend_t *base, void *image_data, int x, int y, struct color *output);
138 | enum device_status gl_device_status(backend_t *base);
139 |
140 | static inline void gl_delete_texture(GLuint texture) {
141 | glDeleteTextures(1, &texture);
142 | }
143 |
144 | /**
145 | * Get a textual representation of an OpenGL error.
146 | */
147 | static inline const char *gl_get_err_str(GLenum err) {
148 | switch (err) {
149 | CASESTRRET(GL_NO_ERROR);
150 | CASESTRRET(GL_INVALID_ENUM);
151 | CASESTRRET(GL_INVALID_VALUE);
152 | CASESTRRET(GL_INVALID_OPERATION);
153 | CASESTRRET(GL_INVALID_FRAMEBUFFER_OPERATION);
154 | CASESTRRET(GL_OUT_OF_MEMORY);
155 | CASESTRRET(GL_STACK_UNDERFLOW);
156 | CASESTRRET(GL_STACK_OVERFLOW);
157 | CASESTRRET(GL_FRAMEBUFFER_UNDEFINED);
158 | CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
159 | CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT);
160 | CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER);
161 | CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER);
162 | CASESTRRET(GL_FRAMEBUFFER_UNSUPPORTED);
163 | CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE);
164 | CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS);
165 | }
166 | return NULL;
167 | }
168 |
169 | /**
170 | * Check for GLX error.
171 | *
172 | * http://blog.nobel-joergensen.com/2013/01/29/debugging-opengl-using-glgeterror/
173 | */
174 | static inline void gl_check_err_(const char *func, int line) {
175 | GLenum err = GL_NO_ERROR;
176 |
177 | while (GL_NO_ERROR != (err = glGetError())) {
178 | const char *errtext = gl_get_err_str(err);
179 | if (errtext) {
180 | log_printf(tls_logger, LOG_LEVEL_ERROR, func,
181 | "GLX error at line %d: %s", line, errtext);
182 | } else {
183 | log_printf(tls_logger, LOG_LEVEL_ERROR, func,
184 | "GLX error at line %d: %d", line, err);
185 | }
186 | }
187 | }
188 |
189 | static inline void gl_clear_err(void) {
190 | while (glGetError() != GL_NO_ERROR)
191 | ;
192 | }
193 |
194 | #define gl_check_err() gl_check_err_(__func__, __LINE__)
195 |
196 | /**
197 | * Check for GL framebuffer completeness.
198 | */
199 | static inline bool gl_check_fb_complete_(const char *func, int line, GLenum fb) {
200 | GLenum status = glCheckFramebufferStatus(fb);
201 |
202 | if (status == GL_FRAMEBUFFER_COMPLETE) {
203 | return true;
204 | }
205 |
206 | const char *stattext = gl_get_err_str(status);
207 | if (stattext) {
208 | log_printf(tls_logger, LOG_LEVEL_ERROR, func,
209 | "Framebuffer attachment failed at line %d: %s", line, stattext);
210 | } else {
211 | log_printf(tls_logger, LOG_LEVEL_ERROR, func,
212 | "Framebuffer attachment failed at line %d: %d", line, status);
213 | }
214 |
215 | return false;
216 | }
217 |
218 | #define gl_check_fb_complete(fb) gl_check_fb_complete_(__func__, __LINE__, (fb))
219 |
220 | /**
221 | * Check if a GLX extension exists.
222 | */
223 | static inline bool gl_has_extension(const char *ext) {
224 | int nexts = 0;
225 | glGetIntegerv(GL_NUM_EXTENSIONS, &nexts);
226 | for (int i = 0; i < nexts || !nexts; i++) {
227 | const char *exti = (const char *)glGetStringi(GL_EXTENSIONS, (GLuint)i);
228 | if (exti == NULL) {
229 | break;
230 | }
231 | if (strcmp(ext, exti) == 0) {
232 | return true;
233 | }
234 | }
235 | gl_clear_err();
236 | log_info("Missing GL extension %s.", ext);
237 | return false;
238 | }
239 |
--------------------------------------------------------------------------------
/src/backend/gl/glx.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) Yuxuan Shui
3 | #pragma once
4 | #include
5 | // Older version of glx.h defines function prototypes for these extensions...
6 | // Rename them to avoid conflicts
7 | #define glXSwapIntervalMESA glXSwapIntervalMESA_
8 | #define glXBindTexImageEXT glXBindTexImageEXT_
9 | #define glXReleaseTexImageEXT glXReleaseTexImageEXT
10 | #include
11 | #undef glXSwapIntervalMESA
12 | #undef glXBindTexImageEXT
13 | #undef glXReleaseTexImageEXT
14 | #include
15 | #include
16 | #include
17 |
18 | #include "log.h"
19 | #include "compiler.h"
20 | #include "utils.h"
21 | #include "x.h"
22 |
23 | struct glx_fbconfig_info {
24 | GLXFBConfig cfg;
25 | int texture_tgts;
26 | int texture_fmt;
27 | int y_inverted;
28 | };
29 |
30 | /// The search criteria for glx_find_fbconfig
31 | struct glx_fbconfig_criteria {
32 | /// Bit width of the red component
33 | int red_size;
34 | /// Bit width of the green component
35 | int green_size;
36 | /// Bit width of the blue component
37 | int blue_size;
38 | /// Bit width of the alpha component
39 | int alpha_size;
40 | /// The depth of X visual
41 | int visual_depth;
42 | };
43 |
44 | struct glx_fbconfig_info *glx_find_fbconfig(Display *, int screen, struct xvisual_info);
45 |
46 |
47 | struct glxext_info {
48 | bool initialized;
49 | bool has_GLX_SGI_video_sync;
50 | bool has_GLX_SGI_swap_control;
51 | bool has_GLX_OML_sync_control;
52 | bool has_GLX_MESA_swap_control;
53 | bool has_GLX_EXT_swap_control;
54 | bool has_GLX_EXT_texture_from_pixmap;
55 | bool has_GLX_ARB_create_context;
56 | bool has_GLX_EXT_buffer_age;
57 | bool has_GLX_MESA_query_renderer;
58 | bool has_GLX_ARB_create_context_robustness;
59 | };
60 |
61 | extern struct glxext_info glxext;
62 |
63 | extern PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI;
64 | extern PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
65 | extern PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML;
66 | extern PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML;
67 | extern PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
68 | extern PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
69 | extern PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA;
70 | extern PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT;
71 | extern PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT;
72 | extern PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
73 |
74 | #ifdef GLX_MESA_query_renderer
75 | extern PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA;
76 | #endif
77 |
78 | void glxext_init(Display *, int screen);
79 |
--------------------------------------------------------------------------------
/src/backend/meson.build:
--------------------------------------------------------------------------------
1 | # enable xrender
2 | srcs += [ files('backend_common.c', 'xrender/xrender.c', 'dummy/dummy.c', 'backend.c', 'driver.c') ]
3 |
4 | # enable opengl
5 | if get_option('opengl')
6 | srcs += [ files('gl/gl_common.c', 'gl/glx.c') ]
7 | endif
8 |
--------------------------------------------------------------------------------
/src/c2.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | /*
3 | * Compton - a compositor for X11
4 | *
5 | * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
6 | *
7 | * Copyright (c) 2011-2013, Christopher Jeffrey
8 | * See LICENSE-mit for more information.
9 | *
10 | */
11 |
12 | #pragma once
13 |
14 | #include
15 |
16 | typedef struct _c2_lptr c2_lptr_t;
17 | typedef struct session session_t;
18 | struct managed_win;
19 |
20 | c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data);
21 |
22 | c2_lptr_t *c2_free_lptr(c2_lptr_t *lp);
23 |
24 | bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst, void **pdata);
25 |
26 | bool c2_list_postprocess(session_t *ps, c2_lptr_t *list);
27 |
--------------------------------------------------------------------------------
/src/cache.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "compiler.h"
4 | #include "utils.h"
5 | #include "cache.h"
6 |
7 | struct cache_entry {
8 | char *key;
9 | void *value;
10 | UT_hash_handle hh;
11 | };
12 |
13 | struct cache {
14 | cache_getter_t getter;
15 | cache_free_t free;
16 | void *user_data;
17 | struct cache_entry *entries;
18 | };
19 |
20 | void cache_set(struct cache *c, const char *key, void *data) {
21 | struct cache_entry *e = NULL;
22 | HASH_FIND_STR(c->entries, key, e);
23 | CHECK(!e);
24 |
25 | e = ccalloc(1, struct cache_entry);
26 | e->key = strdup(key);
27 | e->value = data;
28 | HASH_ADD_STR(c->entries, key, e);
29 | }
30 |
31 | void *cache_get(struct cache *c, const char *key, int *err) {
32 | struct cache_entry *e;
33 | HASH_FIND_STR(c->entries, key, e);
34 | if (e) {
35 | return e->value;
36 | }
37 |
38 | int tmperr;
39 | if (!err) {
40 | err = &tmperr;
41 | }
42 |
43 | *err = 0;
44 | e = ccalloc(1, struct cache_entry);
45 | e->key = strdup(key);
46 | e->value = c->getter(c->user_data, key, err);
47 | if (*err) {
48 | free(e->key);
49 | free(e);
50 | return NULL;
51 | }
52 |
53 | HASH_ADD_STR(c->entries, key, e);
54 | return e->value;
55 | }
56 |
57 | static inline void _cache_invalidate(struct cache *c, struct cache_entry *e) {
58 | if (c->free) {
59 | c->free(c->user_data, e->value);
60 | }
61 | free(e->key);
62 | HASH_DEL(c->entries, e);
63 | free(e);
64 | }
65 |
66 | void cache_invalidate(struct cache *c, const char *key) {
67 | struct cache_entry *e;
68 | HASH_FIND_STR(c->entries, key, e);
69 |
70 | if (e) {
71 | _cache_invalidate(c, e);
72 | }
73 | }
74 |
75 | void cache_invalidate_all(struct cache *c) {
76 | struct cache_entry *e, *tmpe;
77 | HASH_ITER(hh, c->entries, e, tmpe) {
78 | _cache_invalidate(c, e);
79 | }
80 | }
81 |
82 | void *cache_free(struct cache *c) {
83 | void *ret = c->user_data;
84 | cache_invalidate_all(c);
85 | free(c);
86 | return ret;
87 | }
88 |
89 | struct cache *new_cache(void *ud, cache_getter_t getter, cache_free_t f) {
90 | auto c = ccalloc(1, struct cache);
91 | c->user_data = ud;
92 | c->getter = getter;
93 | c->free = f;
94 | return c;
95 | }
96 |
--------------------------------------------------------------------------------
/src/cache.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | struct cache;
4 |
5 | typedef void *(*cache_getter_t)(void *user_data, const char *key, int *err);
6 | typedef void (*cache_free_t)(void *user_data, void *data);
7 |
8 | /// Create a cache with `getter`, and a free function `f` which is used to free the cache
9 | /// value when they are invalidated.
10 | ///
11 | /// `user_data` will be passed to `getter` and `f` when they are called.
12 | struct cache *new_cache(void *user_data, cache_getter_t getter, cache_free_t f);
13 |
14 | /// Fetch a value from the cache. If the value doesn't present in the cache yet, the
15 | /// getter will be called, and the returned value will be stored into the cache.
16 | void *cache_get(struct cache *, const char *key, int *err);
17 |
18 | /// Invalidate a value in the cache.
19 | void cache_invalidate(struct cache *, const char *key);
20 |
21 | /// Invalidate all values in the cache.
22 | void cache_invalidate_all(struct cache *);
23 |
24 | /// Invalidate all values in the cache and free it. Returns the user data passed to
25 | /// `new_cache`
26 | void *cache_free(struct cache *);
27 |
28 | /// Insert a key-value pair into the cache. Only used for internal testing. Takes
29 | /// ownership of `data`
30 | ///
31 | /// If `key` already exists in the cache, this function will abort the program.
32 | void cache_set(struct cache *c, const char *key, void *data);
33 |
--------------------------------------------------------------------------------
/src/compiler.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) 2018 Yuxuan Shui
3 | #pragma once
4 |
5 | #ifdef HAS_STDC_PREDEF_H
6 | #include
7 | #endif
8 |
9 | // clang-format off
10 | #define auto __auto_type
11 | #define likely(x) __builtin_expect(!!(x), 1)
12 | #define unlikely(x) __builtin_expect(!!(x), 0)
13 | #define likely_if(x) if (likely(x))
14 | #define unlikely_if(x) if (unlikely(x))
15 |
16 | #ifndef __has_attribute
17 | # if __GNUC__ >= 4
18 | # define __has_attribute(x) 1
19 | # else
20 | # define __has_attribute(x) 0
21 | # endif
22 | #endif
23 |
24 | #if __has_attribute(const)
25 | # define attr_const __attribute__((const))
26 | #else
27 | # define attr_const
28 | #endif
29 |
30 | #if __has_attribute(format)
31 | # define attr_printf(a, b) __attribute__((format(printf, a, b)))
32 | #else
33 | # define attr_printf(a, b)
34 | #endif
35 |
36 | #if __has_attribute(pure)
37 | # define attr_pure __attribute__((pure))
38 | #else
39 | # define attr_pure
40 | #endif
41 |
42 | #if __has_attribute(unused)
43 | # define attr_unused __attribute__((unused))
44 | #else
45 | # define attr_unused
46 | #endif
47 |
48 | #if __has_attribute(warn_unused_result)
49 | # define attr_warn_unused_result __attribute__((warn_unused_result))
50 | #else
51 | # define attr_warn_unused_result
52 | #endif
53 | // An alias for conveninence
54 | #define must_use attr_warn_unused_result
55 |
56 | #if __has_attribute(nonnull)
57 | # define attr_nonnull(...) __attribute__((nonnull(__VA_ARGS__)))
58 | # define attr_nonnull_all __attribute__((nonnull))
59 | #else
60 | # define attr_nonnull(...)
61 | # define attr_nonnull_all
62 | #endif
63 |
64 | #if __has_attribute(returns_nonnull)
65 | # define attr_ret_nonnull __attribute__((returns_nonnull))
66 | #else
67 | # define attr_ret_nonnull
68 | #endif
69 |
70 | #if __has_attribute(deprecated)
71 | # define attr_deprecated __attribute__((deprecated))
72 | #else
73 | # define attr_deprecated
74 | #endif
75 |
76 | #if __has_attribute(malloc)
77 | # define attr_malloc __attribute__((malloc))
78 | #else
79 | # define attr_malloc
80 | #endif
81 |
82 | #if __has_attribute(fallthrough)
83 | # define fallthrough() __attribute__((fallthrough))
84 | #else
85 | # define fallthrough()
86 | #endif
87 |
88 | #if __STDC_VERSION__ >= 201112L
89 | # define attr_noret _Noreturn
90 | #else
91 | # if __has_attribute(noreturn)
92 | # define attr_noret __attribute__((noreturn))
93 | # else
94 | # define attr_noret
95 | # endif
96 | #endif
97 |
98 | #if defined(__GNUC__) || defined(__clang__)
99 | # define unreachable __builtin_unreachable()
100 | #else
101 | # define unreachable do {} while(0)
102 | #endif
103 |
104 | #ifndef __has_include
105 | # define __has_include(x) 0
106 | #endif
107 |
108 | #if !defined(__STDC_NO_THREADS__) && __has_include()
109 | # include
110 | #elif __STDC_VERSION__ >= 201112L
111 | # define thread_local _Thread_local
112 | #elif defined(__GNUC__) || defined(__clang__)
113 | # define thread_local __thread
114 | #else
115 | # define thread_local _Pragma("GCC error \"No thread local storage support\"") __error__
116 | #endif
117 | // clang-format on
118 |
119 | typedef unsigned long ulong;
120 | typedef unsigned int uint;
121 |
122 | static inline int attr_const popcntul(unsigned long a) {
123 | return __builtin_popcountl(a);
124 | }
125 |
--------------------------------------------------------------------------------
/src/dbus.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | /*
3 | * Compton - a compositor for X11
4 | *
5 | * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
6 | *
7 | * Copyright (c) 2011-2013, Christopher Jeffrey
8 | * See LICENSE-mit for more information.
9 | *
10 | */
11 |
12 | #include
13 |
14 | #include
15 |
16 | typedef struct session session_t;
17 | struct win;
18 |
19 | /**
20 | * Return a string representation of a D-Bus message type.
21 | */
22 | static inline const char *cdbus_repr_msgtype(DBusMessage *msg) {
23 | return dbus_message_type_to_string(dbus_message_get_type(msg));
24 | }
25 |
26 | /**
27 | * Initialize D-Bus connection.
28 | */
29 | bool cdbus_init(session_t *ps, const char *uniq_name);
30 |
31 | /**
32 | * Destroy D-Bus connection.
33 | */
34 | void cdbus_destroy(session_t *ps);
35 |
36 | /// Generate dbus win_added signal
37 | void cdbus_ev_win_added(session_t *ps, struct win *w);
38 |
39 | /// Generate dbus win_destroyed signal
40 | void cdbus_ev_win_destroyed(session_t *ps, struct win *w);
41 |
42 | /// Generate dbus win_mapped signal
43 | void cdbus_ev_win_mapped(session_t *ps, struct win *w);
44 |
45 | /// Generate dbus win_unmapped signal
46 | void cdbus_ev_win_unmapped(session_t *ps, struct win *w);
47 |
48 | /// Generate dbus win_focusout signal
49 | void cdbus_ev_win_focusout(session_t *ps, struct win *w);
50 |
51 | /// Generate dbus win_focusin signal
52 | void cdbus_ev_win_focusin(session_t *ps, struct win *w);
53 |
54 | // vim: set noet sw=8 ts=8 :
55 |
--------------------------------------------------------------------------------
/src/diagnostic.c:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) 2018 Yuxuan Shui
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include "backend/driver.h"
9 | #include "diagnostic.h"
10 | #include "config.h"
11 | #include "picom.h"
12 | #include "common.h"
13 |
14 | void print_diagnostics(session_t *ps, const char *config_file, bool compositor_running) {
15 | printf("**Version:** " COMPTON_VERSION "\n");
16 | //printf("**CFLAGS:** %s\n", "??");
17 | printf("\n### Extensions:\n\n");
18 | printf("* Shape: %s\n", ps->shape_exists ? "Yes" : "No");
19 | printf("* XRandR: %s\n", ps->randr_exists ? "Yes" : "No");
20 | printf("* Present: %s\n", ps->present_exists ? "Present" : "Not Present");
21 | printf("\n### Misc:\n\n");
22 | printf("* Use Overlay: %s\n", ps->overlay != XCB_NONE ? "Yes" : "No");
23 | if (ps->overlay == XCB_NONE) {
24 | if (compositor_running) {
25 | printf(" (Another compositor is already running)\n");
26 | } else if (session_redirection_mode(ps) != XCB_COMPOSITE_REDIRECT_MANUAL) {
27 | printf(" (Not in manual redirection mode)\n");
28 | } else {
29 | printf("\n");
30 | }
31 | }
32 | #ifdef __FAST_MATH__
33 | printf("* Fast Math: Yes\n");
34 | #endif
35 | printf("* Config file used: %s\n", config_file ?: "None");
36 | printf("\n### Drivers (inaccurate):\n\n");
37 | print_drivers(ps->drivers);
38 |
39 | for (int i = 0; i < NUM_BKEND; i++) {
40 | if (backend_list[i] && backend_list[i]->diagnostics) {
41 | printf("\n### Backend: %s\n\n", BACKEND_STRS[i]);
42 | auto data = backend_list[i]->init(ps);
43 | if (!data) {
44 | printf(" Cannot initialize this backend\n");
45 | } else {
46 | backend_list[i]->diagnostics(data);
47 | backend_list[i]->deinit(data);
48 | }
49 | }
50 | }
51 | }
52 |
53 | // vim: set noet sw=8 ts=8 :
54 |
--------------------------------------------------------------------------------
/src/diagnostic.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) 2018 Yuxuan Shui
3 |
4 | #pragma once
5 | #include
6 |
7 | typedef struct session session_t;
8 |
9 | void print_diagnostics(session_t *, const char *config_file, bool compositor_running);
10 |
--------------------------------------------------------------------------------
/src/err.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) 2019 Yuxuan Shui
3 |
4 | #pragma once
5 | #include
6 | #include
7 | #include "compiler.h"
8 |
9 | // Functions for error reporting, adopted from Linux
10 |
11 | // INFO in user space we can probably be more liberal about what pointer we consider
12 | // error. e.g. In x86_64 Linux, all addresses with the highest bit set is invalid in user
13 | // space.
14 | #define MAX_ERRNO 4095
15 |
16 | static inline void *must_use ERR_PTR(intptr_t err) {
17 | return (void *)err;
18 | }
19 |
20 | static inline intptr_t must_use PTR_ERR(void *ptr) {
21 | return (intptr_t)ptr;
22 | }
23 |
24 | static inline bool must_use IS_ERR(void *ptr) {
25 | return unlikely((uintptr_t)ptr > (uintptr_t)-MAX_ERRNO);
26 | }
27 |
28 | static inline bool must_use IS_ERR_OR_NULL(void *ptr) {
29 | return unlikely(!ptr) || IS_ERR(ptr);
30 | }
31 |
32 | static inline intptr_t must_use PTR_ERR_OR_ZERO(void *ptr) {
33 | if (IS_ERR(ptr)) {
34 | return PTR_ERR(ptr);
35 | }
36 | return 0;
37 | }
38 |
--------------------------------------------------------------------------------
/src/event.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) 2019, Yuxuan Shui
3 |
4 | #include
5 |
6 | #include "common.h"
7 |
8 | void ev_handle(session_t *ps, xcb_generic_event_t *ev);
9 |
--------------------------------------------------------------------------------
/src/file_watch.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #ifdef HAS_INOTIFY
4 | #include
5 | #elif HAS_KQUEUE
6 | // clang-format off
7 | #include
8 | // clang-format on
9 | #include
10 | #undef EV_ERROR // Avoid clashing with libev's EV_ERROR
11 | #include // For O_RDONLY
12 | #include // For struct timespec
13 | #include // For open
14 | #endif
15 |
16 | #include
17 | #include
18 |
19 | #include "file_watch.h"
20 | #include "list.h"
21 | #include "log.h"
22 | #include "utils.h"
23 |
24 | struct watched_file {
25 | int wd;
26 | void *ud;
27 | file_watch_cb_t cb;
28 |
29 | UT_hash_handle hh;
30 | };
31 |
32 | struct file_watch_registry {
33 | struct ev_io w;
34 |
35 | struct watched_file *reg;
36 | };
37 |
38 | static void file_watch_ev_cb(EV_P attr_unused, struct ev_io *w, int revent attr_unused) {
39 | auto fwr = (struct file_watch_registry *)w;
40 |
41 | while (true) {
42 | int wd = -1;
43 | #ifdef HAS_INOTIFY
44 | struct inotify_event inotify_event;
45 | auto ret = read(w->fd, &inotify_event, sizeof(struct inotify_event));
46 | if (ret < 0) {
47 | if (errno != EAGAIN) {
48 | log_error_errno("Failed to read from inotify fd");
49 | }
50 | break;
51 | }
52 | wd = inotify_event.wd;
53 | #elif HAS_KQUEUE
54 | struct kevent ev;
55 | struct timespec timeout = {0};
56 | int ret = kevent(fwr->w.fd, NULL, 0, &ev, 1, &timeout);
57 | if (ret <= 0) {
58 | if (ret < 0) {
59 | log_error_errno("Failed to get kevent");
60 | }
61 | break;
62 | }
63 | wd = (int)ev.ident;
64 | #else
65 | assert(false);
66 | #endif
67 |
68 | struct watched_file *wf = NULL;
69 | HASH_FIND_INT(fwr->reg, &wd, wf);
70 | if (!wf) {
71 | log_warn("Got notification for a file I didn't watch.");
72 | continue;
73 | }
74 | wf->cb(wf->ud);
75 | }
76 | }
77 |
78 | void *file_watch_init(EV_P) {
79 | log_debug("Starting watching for file changes");
80 | int fd = -1;
81 | #ifdef HAS_INOTIFY
82 | fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
83 | if (fd < 0) {
84 | log_error_errno("inotify_init1 failed");
85 | return NULL;
86 | }
87 | #elif HAS_KQUEUE
88 | fd = kqueue();
89 | if (fd < 0) {
90 | log_error_errno("Failed to create kqueue");
91 | return NULL;
92 | }
93 | #else
94 | log_info("No file watching support found on the host system.");
95 | return NULL;
96 | #endif
97 | auto fwr = ccalloc(1, struct file_watch_registry);
98 | ev_io_init(&fwr->w, file_watch_ev_cb, fd, EV_READ);
99 | ev_io_start(EV_A_ & fwr->w);
100 |
101 | return fwr;
102 | }
103 |
104 | void file_watch_destroy(EV_P_ void *_fwr) {
105 | log_debug("Stopping watching for file changes");
106 | auto fwr = (struct file_watch_registry *)_fwr;
107 | struct watched_file *i, *tmp;
108 |
109 | HASH_ITER(hh, fwr->reg, i, tmp) {
110 | HASH_DEL(fwr->reg, i);
111 | #ifdef HAS_KQUEUE
112 | // kqueue watch descriptors are file descriptors of
113 | // the files we are watching, so we need to close
114 | // them
115 | close(i->wd);
116 | #endif
117 | free(i);
118 | }
119 |
120 | ev_io_stop(EV_A_ & fwr->w);
121 | close(fwr->w.fd);
122 | free(fwr);
123 | }
124 |
125 | bool file_watch_add(void *_fwr, const char *filename, file_watch_cb_t cb, void *ud) {
126 | log_debug("Adding \"%s\" to watched files", filename);
127 | auto fwr = (struct file_watch_registry *)_fwr;
128 | int wd = -1;
129 |
130 | struct stat statbuf;
131 | int ret = stat(filename, &statbuf);
132 | if (ret < 0) {
133 | log_error_errno("Failed to retrieve information about file \"%s\"", filename);
134 | return false;
135 | }
136 | if (!S_ISREG(statbuf.st_mode)) {
137 | log_info("\"%s\" is not a regular file, not watching it.", filename);
138 | return false;
139 | }
140 |
141 | #ifdef HAS_INOTIFY
142 | wd = inotify_add_watch(fwr->w.fd, filename,
143 | IN_CLOSE_WRITE | IN_MOVE_SELF | IN_DELETE_SELF);
144 | if (wd < 0) {
145 | log_error_errno("Failed to watch file \"%s\"", filename);
146 | return false;
147 | }
148 | #elif HAS_KQUEUE
149 | wd = open(filename, O_RDONLY);
150 | if (wd < 0) {
151 | log_error_errno("Cannot open file \"%s\" for watching", filename);
152 | return false;
153 | }
154 |
155 | uint32_t fflags = NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE | NOTE_ATTRIB;
156 | // NOTE_CLOSE_WRITE is relatively new, so we cannot just use it
157 | #ifdef NOTE_CLOSE_WRITE
158 | fflags |= NOTE_CLOSE_WRITE;
159 | #else
160 | // NOTE_WRITE will receive notification more frequent than necessary, so is less
161 | // preferrable
162 | fflags |= NOTE_WRITE;
163 | #endif
164 | struct kevent ev = {
165 | .ident = (unsigned int)wd, // the wd < 0 case is checked above
166 | .filter = EVFILT_VNODE,
167 | .flags = EV_ADD | EV_CLEAR,
168 | .fflags = fflags,
169 | .data = 0,
170 | .udata = NULL,
171 | };
172 | if (kevent(fwr->w.fd, &ev, 1, NULL, 0, NULL) < 0) {
173 | log_error_errno("Failed to register kevent");
174 | close(wd);
175 | return false;
176 | }
177 | #else
178 | assert(false);
179 | #endif // HAS_KQUEUE
180 |
181 | auto w = ccalloc(1, struct watched_file);
182 | w->wd = wd;
183 | w->cb = cb;
184 | w->ud = ud;
185 |
186 | HASH_ADD_INT(fwr->reg, wd, w);
187 | return true;
188 | }
189 |
--------------------------------------------------------------------------------
/src/file_watch.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 |
4 | #include
5 |
6 | typedef void (*file_watch_cb_t)(void *);
7 |
8 | void *file_watch_init(EV_P);
9 | bool file_watch_add(void *, const char *, file_watch_cb_t, void *);
10 | void file_watch_destroy(EV_P_ void *);
11 |
--------------------------------------------------------------------------------
/src/kernel.c:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) Yuxuan Shui
3 |
4 | #include
5 | #include
6 |
7 | #include "compiler.h"
8 | #include "kernel.h"
9 | #include "log.h"
10 | #include "utils.h"
11 |
12 | /// Sum a region convolution kernel. Region is defined by a width x height rectangle whose
13 | /// top left corner is at (x, y)
14 | double sum_kernel(const conv *map, int x, int y, int width, int height) {
15 | double ret = 0;
16 |
17 | // Compute sum of values which are "in range"
18 | int xstart = normalize_i_range(x, 0, map->w),
19 | xend = normalize_i_range(width + x, 0, map->w);
20 | int ystart = normalize_i_range(y, 0, map->h),
21 | yend = normalize_i_range(height + y, 0, map->h);
22 | assert(yend >= ystart && xend >= xstart);
23 |
24 | int d = map->w;
25 | if (map->rsum) {
26 | // See sum_kernel_preprocess
27 | double v1 = xstart ? map->rsum[(yend - 1) * d + xstart - 1] : 0;
28 | double v2 = ystart ? map->rsum[(ystart - 1) * d + xend - 1] : 0;
29 | double v3 = (xstart && ystart) ? map->rsum[(ystart - 1) * d + xstart - 1] : 0;
30 | return map->rsum[(yend - 1) * d + xend - 1] - v1 - v2 + v3;
31 | }
32 |
33 | for (int yi = ystart; yi < yend; yi++) {
34 | for (int xi = xstart; xi < xend; xi++) {
35 | ret += map->data[yi * d + xi];
36 | }
37 | }
38 |
39 | return ret;
40 | }
41 |
42 | double sum_kernel_normalized(const conv *map, int x, int y, int width, int height) {
43 | double ret = sum_kernel(map, x, y, width, height);
44 | if (ret < 0) {
45 | ret = 0;
46 | }
47 | if (ret > 1) {
48 | ret = 1;
49 | }
50 | return ret;
51 | }
52 |
53 | static inline double attr_const gaussian(double r, double x, double y) {
54 | // Formula can be found here:
55 | // https://en.wikipedia.org/wiki/Gaussian_blur#Mathematics
56 | // Except a special case for r == 0 to produce sharp shadows
57 | if (r == 0)
58 | return 1;
59 | return exp(-0.5 * (x * x + y * y) / (r * r)) / (2 * M_PI * r * r);
60 | }
61 |
62 | conv *gaussian_kernel(double r, int size) {
63 | conv *c;
64 | int center = size / 2;
65 | double t;
66 | assert(size % 2 == 1);
67 |
68 | c = cvalloc(sizeof(conv) + (size_t)(size * size) * sizeof(double));
69 | c->w = c->h = size;
70 | c->rsum = NULL;
71 | t = 0.0;
72 |
73 | for (int y = 0; y < size; y++) {
74 | for (int x = 0; x < size; x++) {
75 | double g = gaussian(r, x - center, y - center);
76 | t += g;
77 | c->data[y * size + x] = g;
78 | }
79 | }
80 |
81 | for (int y = 0; y < size; y++) {
82 | for (int x = 0; x < size; x++) {
83 | c->data[y * size + x] /= t;
84 | }
85 | }
86 |
87 | return c;
88 | }
89 |
90 | /// Estimate the element of the sum of the first row in a gaussian kernel with standard
91 | /// deviation `r` and size `size`,
92 | static inline double estimate_first_row_sum(double size, double r) {
93 | double factor = erf(size / r / sqrt(2));
94 | double a = exp(-0.5 * size * size / (r * r)) / sqrt(2 * M_PI) / r;
95 | return a / factor;
96 | }
97 |
98 | /// Pick a suitable gaussian kernel radius for a given kernel size. The returned radius
99 | /// is the maximum possible radius (<= size*2) that satisfies no sum of the rows in
100 | /// the kernel are less than `row_limit` (up to certain precision).
101 | static inline double gaussian_kernel_std_for_size(int size, double row_limit) {
102 | assert(size > 0);
103 | if (row_limit >= 1.0 / 2.0 / size) {
104 | return size * 2;
105 | }
106 | double l = 0, r = size * 2;
107 | while (r - l > 1e-2) {
108 | double mid = (l + r) / 2.0;
109 | double vmid = estimate_first_row_sum(size, mid);
110 | if (vmid > row_limit) {
111 | r = mid;
112 | } else {
113 | l = mid;
114 | }
115 | }
116 | return (l + r) / 2.0;
117 | }
118 |
119 | /// Create a gaussian kernel with auto detected standard deviation. The choosen standard
120 | /// deviation tries to make sure the outer most pixels of the shadow are completely
121 | /// transparent, so the transition from shadow to the background is smooth.
122 | ///
123 | /// @param[in] shadow_radius the radius of the shadow
124 | conv *gaussian_kernel_autodetect_deviation(int shadow_radius) {
125 | assert(shadow_radius >= 0);
126 | int size = shadow_radius * 2 + 1;
127 |
128 | if (shadow_radius == 0) {
129 | return gaussian_kernel(0, size);
130 | }
131 | double std = gaussian_kernel_std_for_size(shadow_radius, 1.0 / 256.0);
132 | return gaussian_kernel(std, size);
133 | }
134 |
135 | /// preprocess kernels to make shadow generation faster
136 | /// shadow_sum[x*d+y] is the sum of the kernel from (0, 0) to (x, y), inclusive
137 | void sum_kernel_preprocess(conv *map) {
138 | if (map->rsum) {
139 | free(map->rsum);
140 | }
141 |
142 | auto sum = map->rsum = ccalloc(map->w * map->h, double);
143 | sum[0] = map->data[0];
144 |
145 | for (int x = 1; x < map->w; x++) {
146 | sum[x] = sum[x - 1] + map->data[x];
147 | }
148 |
149 | const int d = map->w;
150 | for (int y = 1; y < map->h; y++) {
151 | sum[y * d] = sum[(y - 1) * d] + map->data[y * d];
152 | for (int x = 1; x < map->w; x++) {
153 | double tmp = sum[(y - 1) * d + x] + sum[y * d + x - 1] -
154 | sum[(y - 1) * d + x - 1];
155 | sum[y * d + x] = tmp + map->data[y * d + x];
156 | }
157 | }
158 | }
159 |
160 | // vim: set noet sw=8 ts=8 :
161 |
--------------------------------------------------------------------------------
/src/kernel.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) Yuxuan Shui
3 |
4 | #pragma once
5 | #include
6 | #include "compiler.h"
7 |
8 | /// Code for generating convolution kernels
9 |
10 | typedef struct conv {
11 | int w, h;
12 | double *rsum;
13 | double data[];
14 | } conv;
15 |
16 | /// Calculate the sum of a rectangle part of the convolution kernel
17 | /// the rectangle is defined by top left (x, y), and a size (width x height)
18 | double attr_pure sum_kernel(const conv *map, int x, int y, int width, int height);
19 | double attr_pure sum_kernel_normalized(const conv *map, int x, int y, int width, int height);
20 |
21 | /// Create a kernel with gaussian distribution with standard deviation `r`, and size
22 | /// `size`.
23 | conv *gaussian_kernel(double r, int size);
24 |
25 | /// Create a gaussian kernel with auto detected standard deviation. The choosen standard
26 | /// deviation tries to make sure the outer most pixels of the shadow are completely
27 | /// transparent.
28 | ///
29 | /// @param[in] shadow_radius the radius of the shadow
30 | conv *gaussian_kernel_autodetect_deviation(int shadow_radius);
31 |
32 | /// preprocess kernels to make shadow generation faster
33 | /// shadow_sum[x*d+y] is the sum of the kernel from (0, 0) to (x, y), inclusive
34 | void sum_kernel_preprocess(conv *map);
35 |
36 | static inline void free_conv(conv *k) {
37 | free(k->rsum);
38 | free(k);
39 | }
40 |
--------------------------------------------------------------------------------
/src/list.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | #include
4 |
5 | /**
6 | * container_of - cast a member of a structure out to the containing structure
7 | * @ptr: the pointer to the member.
8 | * @type: the type of the container struct this is embedded in.
9 | * @member: the name of the member within the struct.
10 | *
11 | */
12 | #define container_of(ptr, type, member) \
13 | ({ \
14 | const __typeof__(((type *)0)->member) *__mptr = (ptr); \
15 | (type *)((char *)__mptr - offsetof(type, member)); \
16 | })
17 |
18 | struct list_node {
19 | struct list_node *next, *prev;
20 | };
21 |
22 | #define list_entry(ptr, type, node) container_of(ptr, type, node)
23 | #define list_next_entry(ptr, node) list_entry((ptr)->node.next, __typeof__(*(ptr)), node)
24 | #define list_prev_entry(ptr, node) list_entry((ptr)->node.prev, __typeof__(*(ptr)), node)
25 |
26 | /// Insert a new node between two adjacent nodes in the list
27 | static inline void __list_insert_between(struct list_node *prev, struct list_node *next,
28 | struct list_node *new_) {
29 | new_->prev = prev;
30 | new_->next = next;
31 | next->prev = new_;
32 | prev->next = new_;
33 | }
34 |
35 | /// Insert a new node after `curr`
36 | static inline void list_insert_after(struct list_node *curr, struct list_node *new_) {
37 | __list_insert_between(curr, curr->next, new_);
38 | }
39 |
40 | /// Insert a new node before `curr`
41 | static inline void list_insert_before(struct list_node *curr, struct list_node *new_) {
42 | __list_insert_between(curr->prev, curr, new_);
43 | }
44 |
45 | /// Link two nodes in the list, so `next` becomes the successor node of `prev`
46 | static inline void __list_link(struct list_node *prev, struct list_node *next) {
47 | next->prev = prev;
48 | prev->next = next;
49 | }
50 |
51 | /// Remove a node from the list
52 | static inline void list_remove(struct list_node *to_remove) {
53 | __list_link(to_remove->prev, to_remove->next);
54 | to_remove->prev = (void *)-1;
55 | to_remove->next = (void *)-2;
56 | }
57 |
58 | /// Move `to_move` so that it's before `new_next`
59 | static inline void list_move_before(struct list_node *to_move, struct list_node *new_next) {
60 | list_remove(to_move);
61 | list_insert_before(new_next, to_move);
62 | }
63 |
64 | /// Move `to_move` so that it's after `new_prev`
65 | static inline void list_move_after(struct list_node *to_move, struct list_node *new_prev) {
66 | list_remove(to_move);
67 | list_insert_after(new_prev, to_move);
68 | }
69 |
70 | /// Initialize a list node that's intended to be the head node
71 | static inline void list_init_head(struct list_node *head) {
72 | head->next = head->prev = head;
73 | }
74 |
75 | /// Replace list node `old` with `n`
76 | static inline void list_replace(struct list_node *old, struct list_node *n) {
77 | __list_insert_between(old->prev, old->next, n);
78 | old->prev = (void *)-1;
79 | old->next = (void *)-2;
80 | }
81 |
82 | /// Return true if head is the only node in the list. Under usual circumstances this means
83 | /// the list is empty
84 | static inline bool list_is_empty(const struct list_node *head) {
85 | return head->prev == head;
86 | }
87 |
88 | /// Return true if `to_check` is the first node in list headed by `head`
89 | static inline bool
90 | list_node_is_first(const struct list_node *head, const struct list_node *to_check) {
91 | return head->next == to_check;
92 | }
93 |
94 | /// Return true if `to_check` is the last node in list headed by `head`
95 | static inline bool
96 | list_node_is_last(const struct list_node *head, const struct list_node *to_check) {
97 | return head->prev == to_check;
98 | }
99 |
100 | #define list_foreach(type, i, head, member) \
101 | for (type *i = list_entry((head)->next, type, member); &i->member != (head); \
102 | i = list_next_entry(i, member))
103 |
104 | /// Like list_for_each, but it's safe to remove the current list node from the list
105 | #define list_foreach_safe(type, i, head, member) \
106 | for (type *i = list_entry((head)->next, type, member), \
107 | *__tmp = list_next_entry(i, member); \
108 | &i->member != (head); i = __tmp, __tmp = list_next_entry(i, member))
109 |
--------------------------------------------------------------------------------
/src/log.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #ifdef CONFIG_OPENGL
12 | #include
13 | #include "backend/gl/gl_common.h"
14 | #include "backend/gl/glx.h"
15 | #endif
16 |
17 | #include "compiler.h"
18 | #include "log.h"
19 | #include "utils.h"
20 |
21 | thread_local struct log *tls_logger;
22 |
23 | struct log_target;
24 |
25 | struct log {
26 | struct log_target *head;
27 |
28 | int log_level;
29 | };
30 |
31 | struct log_target {
32 | const struct log_ops *ops;
33 | struct log_target *next;
34 | };
35 |
36 | struct log_ops {
37 | void (*write)(struct log_target *, const char *, size_t);
38 | void (*writev)(struct log_target *, const struct iovec *, int vcnt);
39 | void (*destroy)(struct log_target *);
40 |
41 | /// Additional strings to print around the log_level string
42 | const char *(*colorize_begin)(enum log_level);
43 | const char *(*colorize_end)(enum log_level);
44 | };
45 |
46 | /// Fallback writev for targets don't implement it
47 | static attr_unused void
48 | log_default_writev(struct log_target *tgt, const struct iovec *vec, int vcnt) {
49 | size_t total = 0;
50 | for (int i = 0; i < vcnt; i++) {
51 | total += vec[i].iov_len;
52 | }
53 |
54 | if (!total) {
55 | // Nothing to write
56 | return;
57 | }
58 | char *buf = ccalloc(total, char);
59 | total = 0;
60 | for (int i = 0; i < vcnt; i++) {
61 | memcpy(buf + total, vec[i].iov_base, vec[i].iov_len);
62 | total += vec[i].iov_len;
63 | }
64 | tgt->ops->write(tgt, buf, total);
65 | free(buf);
66 | }
67 |
68 | static attr_const const char *log_level_to_string(enum log_level level) {
69 | switch (level) {
70 | case LOG_LEVEL_TRACE: return "TRACE";
71 | case LOG_LEVEL_DEBUG: return "DEBUG";
72 | case LOG_LEVEL_INFO: return "INFO";
73 | case LOG_LEVEL_WARN: return "WARN";
74 | case LOG_LEVEL_ERROR: return "ERROR";
75 | case LOG_LEVEL_FATAL: return "FATAL ERROR";
76 | default: return "????";
77 | }
78 | }
79 |
80 | enum log_level string_to_log_level(const char *str) {
81 | if (strcasecmp(str, "TRACE") == 0)
82 | return LOG_LEVEL_TRACE;
83 | else if (strcasecmp(str, "DEBUG") == 0)
84 | return LOG_LEVEL_DEBUG;
85 | else if (strcasecmp(str, "INFO") == 0)
86 | return LOG_LEVEL_INFO;
87 | else if (strcasecmp(str, "WARN") == 0)
88 | return LOG_LEVEL_WARN;
89 | else if (strcasecmp(str, "ERROR") == 0)
90 | return LOG_LEVEL_ERROR;
91 | return LOG_LEVEL_INVALID;
92 | }
93 |
94 | struct log *log_new(void) {
95 | auto ret = cmalloc(struct log);
96 | ret->log_level = LOG_LEVEL_WARN;
97 | ret->head = NULL;
98 | return ret;
99 | }
100 |
101 | void log_add_target(struct log *l, struct log_target *tgt) {
102 | assert(tgt->ops->writev);
103 | tgt->next = l->head;
104 | l->head = tgt;
105 | }
106 |
107 | /// Remove a previously added log target for a log struct, and destroy it. If the log
108 | /// target was never added, nothing happens.
109 | void log_remove_target(struct log *l, struct log_target *tgt) {
110 | struct log_target *now = l->head, **prev = &l->head;
111 | while (now) {
112 | if (now == tgt) {
113 | *prev = now->next;
114 | tgt->ops->destroy(tgt);
115 | break;
116 | }
117 | prev = &now->next;
118 | now = now->next;
119 | }
120 | }
121 |
122 | /// Destroy a log struct and every log target added to it
123 | void log_destroy(struct log *l) {
124 | // free all tgt
125 | struct log_target *head = l->head;
126 | while (head) {
127 | auto next = head->next;
128 | head->ops->destroy(head);
129 | head = next;
130 | }
131 | free(l);
132 | }
133 |
134 | void log_set_level(struct log *l, int level) {
135 | assert(level <= LOG_LEVEL_FATAL && level >= 0);
136 | l->log_level = level;
137 | }
138 |
139 | enum log_level log_get_level(const struct log *l) {
140 | return l->log_level;
141 | }
142 |
143 | attr_printf(4, 5) void log_printf(struct log *l, int level, const char *func,
144 | const char *fmt, ...) {
145 | assert(level <= LOG_LEVEL_FATAL && level >= 0);
146 | if (level < l->log_level)
147 | return;
148 |
149 | char *buf = NULL;
150 | va_list args;
151 |
152 | va_start(args, fmt);
153 | int blen = vasprintf(&buf, fmt, args);
154 | va_end(args);
155 |
156 | if (blen < 0 || !buf) {
157 | free(buf);
158 | return;
159 | }
160 |
161 | struct timespec ts;
162 | timespec_get(&ts, TIME_UTC);
163 | struct tm now;
164 | localtime_r(&ts.tv_sec, &now);
165 | char time_buf[100];
166 | strftime(time_buf, sizeof time_buf, "%x %T", &now);
167 |
168 | char *time = NULL;
169 | int tlen = asprintf(&time, "%s.%03ld", time_buf, ts.tv_nsec / 1000000);
170 | if (tlen < 0 || !time) {
171 | free(buf);
172 | free(time);
173 | return;
174 | }
175 |
176 | const char *log_level_str = log_level_to_string(level);
177 | size_t llen = strlen(log_level_str);
178 | size_t flen = strlen(func);
179 |
180 | struct log_target *head = l->head;
181 | while (head) {
182 | const char *p = "", *s = "";
183 | size_t plen = 0, slen = 0;
184 |
185 | if (head->ops->colorize_begin) {
186 | // construct target specific prefix
187 | p = head->ops->colorize_begin(level);
188 | plen = strlen(p);
189 | if (head->ops->colorize_end) {
190 | s = head->ops->colorize_end(level);
191 | slen = strlen(s);
192 | }
193 | }
194 | head->ops->writev(
195 | head,
196 | (struct iovec[]){{.iov_base = "[ ", .iov_len = 2},
197 | {.iov_base = time, .iov_len = (size_t)tlen},
198 | {.iov_base = " ", .iov_len = 1},
199 | {.iov_base = (void *)func, .iov_len = flen},
200 | {.iov_base = " ", .iov_len = 1},
201 | {.iov_base = (void *)p, .iov_len = plen},
202 | {.iov_base = (void *)log_level_str, .iov_len = llen},
203 | {.iov_base = (void *)s, .iov_len = slen},
204 | {.iov_base = " ] ", .iov_len = 3},
205 | {.iov_base = buf, .iov_len = (size_t)blen},
206 | {.iov_base = "\n", .iov_len = 1}},
207 | 11);
208 | head = head->next;
209 | }
210 | free(time);
211 | free(buf);
212 | }
213 |
214 | /// A trivial deinitializer that simply frees the memory
215 | static attr_unused void logger_trivial_destroy(struct log_target *tgt) {
216 | free(tgt);
217 | }
218 |
219 | /// A null log target that does nothing
220 | static const struct log_ops null_logger_ops;
221 | static struct log_target null_logger_target = {
222 | .ops = &null_logger_ops,
223 | };
224 |
225 | struct log_target *null_logger_new(void) {
226 | return &null_logger_target;
227 | }
228 |
229 | static void null_logger_write(struct log_target *tgt attr_unused,
230 | const char *str attr_unused, size_t len attr_unused) {
231 | return;
232 | }
233 |
234 | static void null_logger_writev(struct log_target *tgt attr_unused,
235 | const struct iovec *vec attr_unused, int vcnt attr_unused) {
236 | return;
237 | }
238 |
239 | static const struct log_ops null_logger_ops = {
240 | .write = null_logger_write,
241 | .writev = null_logger_writev,
242 | };
243 |
244 | /// A file based logger that writes to file (or stdout/stderr)
245 | struct file_logger {
246 | struct log_target tgt;
247 | FILE *f;
248 | struct log_ops ops;
249 | };
250 |
251 | static void file_logger_write(struct log_target *tgt, const char *str, size_t len) {
252 | auto f = (struct file_logger *)tgt;
253 | fwrite(str, 1, len, f->f);
254 | }
255 |
256 | static void file_logger_writev(struct log_target *tgt, const struct iovec *vec, int vcnt) {
257 | auto f = (struct file_logger *)tgt;
258 | fflush(f->f);
259 | writev(fileno(f->f), vec, vcnt);
260 | }
261 |
262 | static void file_logger_destroy(struct log_target *tgt) {
263 | auto f = (struct file_logger *)tgt;
264 | fclose(f->f);
265 | free(tgt);
266 | }
267 |
268 | #define ANSI(x) "\033[" x "m"
269 | static const char *terminal_colorize_begin(enum log_level level) {
270 | switch (level) {
271 | case LOG_LEVEL_TRACE: return ANSI("30;2");
272 | case LOG_LEVEL_DEBUG: return ANSI("37;2");
273 | case LOG_LEVEL_INFO: return ANSI("92");
274 | case LOG_LEVEL_WARN: return ANSI("33");
275 | case LOG_LEVEL_ERROR: return ANSI("31;1");
276 | case LOG_LEVEL_FATAL: return ANSI("30;103;1");
277 | default: return "";
278 | }
279 | }
280 |
281 | static const char *terminal_colorize_end(enum log_level level attr_unused) {
282 | return ANSI("0");
283 | }
284 | #undef PREFIX
285 |
286 | static const struct log_ops file_logger_ops = {
287 | .write = file_logger_write,
288 | .writev = file_logger_writev,
289 | .destroy = file_logger_destroy,
290 | };
291 |
292 | struct log_target *file_logger_new(const char *filename) {
293 | FILE *f = fopen(filename, "a");
294 | if (!f) {
295 | return NULL;
296 | }
297 |
298 | auto ret = cmalloc(struct file_logger);
299 | ret->tgt.ops = &ret->ops;
300 | ret->f = f;
301 |
302 | // Always assume a file is not a terminal
303 | ret->ops = file_logger_ops;
304 |
305 | return &ret->tgt;
306 | }
307 |
308 | struct log_target *stderr_logger_new(void) {
309 | int fd = dup(STDERR_FILENO);
310 | if (fd < 0) {
311 | return NULL;
312 | }
313 |
314 | FILE *f = fdopen(fd, "w");
315 | if (!f) {
316 | return NULL;
317 | }
318 |
319 | auto ret = cmalloc(struct file_logger);
320 | ret->tgt.ops = &ret->ops;
321 | ret->f = f;
322 | ret->ops = file_logger_ops;
323 |
324 | if (isatty(fd)) {
325 | ret->ops.colorize_begin = terminal_colorize_begin;
326 | ret->ops.colorize_end = terminal_colorize_end;
327 | }
328 | return &ret->tgt;
329 | }
330 |
331 | #ifdef CONFIG_OPENGL
332 | /// An opengl logger that can be used for logging into opengl debugging tools,
333 | /// such as apitrace
334 | struct gl_string_marker_logger {
335 | struct log_target tgt;
336 | PFNGLSTRINGMARKERGREMEDYPROC gl_string_marker;
337 | };
338 |
339 | static void
340 | gl_string_marker_logger_write(struct log_target *tgt, const char *str, size_t len) {
341 | auto g = (struct gl_string_marker_logger *)tgt;
342 | // strip newlines at the end of the string
343 | while (len > 0 && str[len-1] == '\n') {
344 | len--;
345 | }
346 | g->gl_string_marker((GLsizei)len, str);
347 | }
348 |
349 | static const struct log_ops gl_string_marker_logger_ops = {
350 | .write = gl_string_marker_logger_write,
351 | .writev = log_default_writev,
352 | .destroy = logger_trivial_destroy,
353 | };
354 |
355 | struct log_target *gl_string_marker_logger_new(void) {
356 | if (!gl_has_extension("GL_GREMEDY_string_marker")) {
357 | return NULL;
358 | }
359 |
360 | void *fnptr = glXGetProcAddress((GLubyte *)"glStringMarkerGREMEDY");
361 | if (!fnptr)
362 | return NULL;
363 |
364 | auto ret = cmalloc(struct gl_string_marker_logger);
365 | ret->tgt.ops = &gl_string_marker_logger_ops;
366 | ret->gl_string_marker = fnptr;
367 | return &ret->tgt;
368 | }
369 |
370 | #else
371 | struct log_target *gl_string_marker_logger_new(void) {
372 | return NULL;
373 | }
374 | #endif
375 |
376 | // vim: set noet sw=8 ts=8:
377 |
--------------------------------------------------------------------------------
/src/log.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) 2018 Yuxuan Shui
3 |
4 | #pragma once
5 | #include
6 | #include
7 |
8 | #include "compiler.h"
9 |
10 | enum log_level {
11 | LOG_LEVEL_INVALID = -1,
12 | LOG_LEVEL_TRACE = 0,
13 | LOG_LEVEL_DEBUG,
14 | LOG_LEVEL_INFO,
15 | LOG_LEVEL_WARN,
16 | LOG_LEVEL_ERROR,
17 | LOG_LEVEL_FATAL,
18 | };
19 |
20 | #define LOG_UNLIKELY(level, x, ...) \
21 | do { \
22 | if (unlikely(LOG_LEVEL_##level >= log_get_level_tls())) { \
23 | log_printf(tls_logger, LOG_LEVEL_##level, __func__, x, ##__VA_ARGS__); \
24 | } \
25 | } while (0)
26 |
27 | #define LOG(level, x, ...) \
28 | do { \
29 | if (LOG_LEVEL_##level >= log_get_level_tls()) { \
30 | log_printf(tls_logger, LOG_LEVEL_##level, __func__, x, ##__VA_ARGS__); \
31 | } \
32 | } while (0)
33 | #define log_trace(x, ...) LOG_UNLIKELY(TRACE, x, ##__VA_ARGS__)
34 | #define log_debug(x, ...) LOG_UNLIKELY(DEBUG, x, ##__VA_ARGS__)
35 | #define log_info(x, ...) LOG(INFO, x, ##__VA_ARGS__)
36 | #define log_warn(x, ...) LOG(WARN, x, ##__VA_ARGS__)
37 | #define log_error(x, ...) LOG(ERROR, x, ##__VA_ARGS__)
38 | #define log_fatal(x, ...) LOG(FATAL, x, ##__VA_ARGS__)
39 |
40 | #define log_error_errno(x, ...) LOG(ERROR, x ": %s", ##__VA_ARGS__, strerror(errno))
41 |
42 | struct log;
43 | struct log_target;
44 |
45 | attr_printf(4, 5) void log_printf(struct log *, int level, const char *func,
46 | const char *fmt, ...);
47 |
48 | attr_malloc struct log *log_new(void);
49 | /// Destroy a log struct and every log target added to it
50 | attr_nonnull_all void log_destroy(struct log *);
51 | attr_nonnull(1) void log_set_level(struct log *l, int level);
52 | attr_pure enum log_level log_get_level(const struct log *l);
53 | attr_nonnull_all void log_add_target(struct log *, struct log_target *);
54 | attr_pure enum log_level string_to_log_level(const char *);
55 | /// Remove a previously added log target for a log struct, and destroy it. If the log
56 | /// target was never added, nothing happens.
57 | void log_remove_target(struct log *l, struct log_target *tgt);
58 |
59 | extern thread_local struct log *tls_logger;
60 |
61 | /// Create a thread local logger
62 | static inline void log_init_tls(void) {
63 | tls_logger = log_new();
64 | }
65 | /// Set thread local logger log level
66 | static inline void log_set_level_tls(int level) {
67 | assert(tls_logger);
68 | log_set_level(tls_logger, level);
69 | }
70 | static inline attr_nonnull_all void log_add_target_tls(struct log_target *tgt) {
71 | assert(tls_logger);
72 | log_add_target(tls_logger, tgt);
73 | }
74 |
75 | static inline attr_nonnull_all void log_remove_target_tls(struct log_target *tgt) {
76 | assert(tls_logger);
77 | log_remove_target(tls_logger, tgt);
78 | }
79 |
80 | static inline attr_pure enum log_level log_get_level_tls(void) {
81 | assert(tls_logger);
82 | return log_get_level(tls_logger);
83 | }
84 |
85 | static inline void log_deinit_tls(void) {
86 | assert(tls_logger);
87 | log_destroy(tls_logger);
88 | tls_logger = NULL;
89 | }
90 |
91 | attr_malloc struct log_target *stderr_logger_new(void);
92 | attr_malloc struct log_target *file_logger_new(const char *file);
93 | attr_malloc struct log_target *null_logger_new(void);
94 | attr_malloc struct log_target *gl_string_marker_logger_new(void);
95 |
96 | // vim: set noet sw=8 ts=8:
97 |
--------------------------------------------------------------------------------
/src/meson.build:
--------------------------------------------------------------------------------
1 | libev = dependency('libev', required: false)
2 | if not libev.found()
3 | libev = cc.find_library('ev')
4 | endif
5 | base_deps = [
6 | cc.find_library('m'),
7 | libev
8 | ]
9 |
10 | srcs = [ files('picom.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', 'utils.c',
11 | 'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c', 'log.c',
12 | 'options.c', 'event.c', 'cache.c', 'atom.c', 'file_watch.c') ]
13 | picom_inc = include_directories('.')
14 |
15 | cflags = []
16 |
17 | required_xcb_packages = [
18 | 'xcb-render', 'xcb-damage', 'xcb-randr', 'xcb-sync', 'xcb-composite',
19 | 'xcb-shape', 'xcb-xinerama', 'xcb-xfixes', 'xcb-present', 'xcb-glx', 'xcb'
20 | ]
21 |
22 | required_packages = [
23 | 'x11', 'x11-xcb', 'xcb-renderutil', 'xcb-image', 'xext', 'pixman-1'
24 | ]
25 |
26 | foreach i : required_packages
27 | base_deps += [dependency(i, required: true)]
28 | endforeach
29 |
30 | foreach i : required_xcb_packages
31 | base_deps += [dependency(i, version: '>=1.12.0', required: true)]
32 | endforeach
33 |
34 | if not cc.has_header('uthash.h')
35 | error('Dependency uthash not found')
36 | endif
37 |
38 | deps = []
39 |
40 | if get_option('config_file')
41 | deps += [dependency('libconfig', version: '>=1.4', required: true)]
42 |
43 | cflags += ['-DCONFIG_LIBCONFIG']
44 | srcs += [ 'config_libconfig.c' ]
45 | endif
46 | if get_option('regex')
47 | pcre = dependency('libpcre', required: true)
48 | cflags += ['-DCONFIG_REGEX_PCRE']
49 | if pcre.version().version_compare('>=8.20')
50 | cflags += ['-DCONFIG_REGEX_PCRE_JIT']
51 | endif
52 | deps += [pcre]
53 | endif
54 |
55 | if get_option('vsync_drm')
56 | cflags += ['-DCONFIG_VSYNC_DRM']
57 | deps += [dependency('libdrm', required: true)]
58 | endif
59 |
60 | if get_option('opengl')
61 | cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES']
62 | deps += [dependency('gl', required: true)]
63 | srcs += [ 'opengl.c' ]
64 | endif
65 |
66 | if get_option('dbus')
67 | cflags += ['-DCONFIG_DBUS']
68 | deps += [dependency('dbus-1', required: true)]
69 | srcs += [ 'dbus.c' ]
70 | endif
71 |
72 | if get_option('xrescheck')
73 | cflags += ['-DDEBUG_XRC']
74 | srcs += [ 'xrescheck.c' ]
75 | endif
76 |
77 | if get_option('unittest')
78 | cflags += ['-DUNIT_TEST']
79 | endif
80 |
81 | host_system = host_machine.system()
82 | if host_system == 'linux'
83 | cflags += ['-DHAS_INOTIFY']
84 | elif (host_system == 'freebsd' or host_system == 'netbsd' or
85 | host_system == 'dragonfly' or host_system == 'openbsd')
86 | cflags += ['-DHAS_KQUEUE']
87 | endif
88 |
89 | subdir('backend')
90 |
91 | picom = executable('picom', srcs, c_args: cflags,
92 | dependencies: [ base_deps, deps, test_h_dep ],
93 | install: true, include_directories: picom_inc)
94 |
95 | if get_option('unittest')
96 | test('picom unittest', picom, args: [ '--unittest' ])
97 | endif
98 |
--------------------------------------------------------------------------------
/src/meta.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) 2019, Yuxuan Shui
3 |
4 | #pragma once
5 |
6 | /// Macro metaprogramming
7 |
8 | #define _APPLY1(a, ...) a(__VA_ARGS__)
9 | #define _APPLY2(a, ...) a(__VA_ARGS__)
10 | #define _APPLY3(a, ...) a(__VA_ARGS__)
11 | #define _APPLY4(a, ...) a(__VA_ARGS__)
12 |
13 | #define RIOTA1(x) x
14 | #define RIOTA2(x) RIOTA1(x##1), RIOTA1(x##0)
15 | #define RIOTA4(x) RIOTA2(x##1), RIOTA2(x##0)
16 | #define RIOTA8(x) RIOTA4(x##1), RIOTA4(x##0)
17 | #define RIOTA16(x) RIOTA8(x##1), RIOTA8(x##0)
18 | /// Generate a list containing 31, 30, ..., 0, in binary
19 | #define RIOTA32(x) RIOTA16(x##1), RIOTA16(x##0)
20 |
21 | #define CONCAT2(a, b) a##b
22 | #define CONCAT1(a, b) CONCAT2(a, b)
23 | #define CONCAT(a, b) CONCAT1(a, b)
24 |
25 | #define _ARGS_HEAD(head, ...) head
26 | #define _ARGS_SKIP4(_1, _2, _3, _4, ...) __VA_ARGS__
27 | #define _ARGS_SKIP8(...) _APPLY1(_ARGS_SKIP4, _ARGS_SKIP4(__VA_ARGS__))
28 | #define _ARGS_SKIP16(...) _APPLY2(_ARGS_SKIP8, _ARGS_SKIP8(__VA_ARGS__))
29 | #define _ARGS_SKIP32(...) _APPLY3(_ARGS_SKIP16, _ARGS_SKIP16(__VA_ARGS__))
30 |
31 | /// Return the 33rd argument
32 | #define _ARG33(...) _APPLY4(_ARGS_HEAD, _ARGS_SKIP32(__VA_ARGS__))
33 |
34 | /// Return the number of arguments passed in binary, handles at most 31 elements
35 | #define VA_ARGS_LENGTH(...) _ARG33(0, ##__VA_ARGS__, RIOTA32(0))
36 |
37 | #define LIST_APPLY_000000(fn, sep, ...)
38 | #define LIST_APPLY_000001(fn, sep, x, ...) fn(x)
39 | #define LIST_APPLY_000010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000001(fn, sep, __VA_ARGS__)
40 | #define LIST_APPLY_000011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000010(fn, sep, __VA_ARGS__)
41 | #define LIST_APPLY_000100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000011(fn, sep, __VA_ARGS__)
42 | #define LIST_APPLY_000101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000100(fn, sep, __VA_ARGS__)
43 | #define LIST_APPLY_000110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000101(fn, sep, __VA_ARGS__)
44 | #define LIST_APPLY_000111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000110(fn, sep, __VA_ARGS__)
45 | #define LIST_APPLY_001000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000111(fn, sep, __VA_ARGS__)
46 | #define LIST_APPLY_001001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001000(fn, sep, __VA_ARGS__)
47 | #define LIST_APPLY_001010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001001(fn, sep, __VA_ARGS__)
48 | #define LIST_APPLY_001011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001010(fn, sep, __VA_ARGS__)
49 | #define LIST_APPLY_001100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001011(fn, sep, __VA_ARGS__)
50 | #define LIST_APPLY_001101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001100(fn, sep, __VA_ARGS__)
51 | #define LIST_APPLY_001110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001101(fn, sep, __VA_ARGS__)
52 | #define LIST_APPLY_001111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001110(fn, sep, __VA_ARGS__)
53 | #define LIST_APPLY_010000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001111(fn, sep, __VA_ARGS__)
54 | #define LIST_APPLY_010001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010000(fn, sep, __VA_ARGS__)
55 | #define LIST_APPLY_010010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010001(fn, sep, __VA_ARGS__)
56 | #define LIST_APPLY_010011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010010(fn, sep, __VA_ARGS__)
57 | #define LIST_APPLY_010100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010011(fn, sep, __VA_ARGS__)
58 | #define LIST_APPLY_010101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010100(fn, sep, __VA_ARGS__)
59 | #define LIST_APPLY_010110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010101(fn, sep, __VA_ARGS__)
60 | #define LIST_APPLY_010111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010110(fn, sep, __VA_ARGS__)
61 | #define LIST_APPLY_011000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010111(fn, sep, __VA_ARGS__)
62 | #define LIST_APPLY_011001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011000(fn, sep, __VA_ARGS__)
63 | #define LIST_APPLY_011010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011001(fn, sep, __VA_ARGS__)
64 | #define LIST_APPLY_011011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011010(fn, sep, __VA_ARGS__)
65 | #define LIST_APPLY_011100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011011(fn, sep, __VA_ARGS__)
66 | #define LIST_APPLY_011101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011100(fn, sep, __VA_ARGS__)
67 | #define LIST_APPLY_011110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011101(fn, sep, __VA_ARGS__)
68 | #define LIST_APPLY_011111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011110(fn, sep, __VA_ARGS__)
69 | #define LIST_APPLY_(N, fn, sep, ...) CONCAT(LIST_APPLY_, N)(fn, sep, __VA_ARGS__)
70 | #define LIST_APPLY(fn, sep, ...) \
71 | LIST_APPLY_(VA_ARGS_LENGTH(__VA_ARGS__), fn, sep, __VA_ARGS__)
72 |
73 | #define SEP_COMMA() ,
74 | #define SEP_COLON() ;
75 | #define SEP_NONE()
76 |
--------------------------------------------------------------------------------
/src/opengl.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | /*
3 | * Compton - a compositor for X11
4 | *
5 | * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
6 | *
7 | * Copyright (c) 2011-2013, Christopher Jeffrey
8 | * See LICENSE-mit for more information.
9 | *
10 | */
11 |
12 | #pragma once
13 |
14 | #include "common.h"
15 | #include "compiler.h"
16 | #include "log.h"
17 | #include "region.h"
18 | #include "render.h"
19 | #include "win.h"
20 |
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 |
30 | typedef struct {
31 | /// Fragment shader for blur.
32 | GLuint frag_shader;
33 | /// GLSL program for blur.
34 | GLuint prog;
35 | /// Location of uniform "offset_x" in blur GLSL program.
36 | GLint unifm_offset_x;
37 | /// Location of uniform "offset_y" in blur GLSL program.
38 | GLint unifm_offset_y;
39 | /// Location of uniform "factor_center" in blur GLSL program.
40 | GLint unifm_factor_center;
41 | } glx_blur_pass_t;
42 |
43 | typedef struct {
44 | /// Fragment shader for rounded corners.
45 | GLuint frag_shader;
46 | /// GLSL program for rounded corners.
47 | GLuint prog;
48 | /// Location of uniform "radius" in rounded-corners GLSL program.
49 | GLint unifm_radius;
50 | /// Location of uniform "texcoord" in rounded-corners GLSL program.
51 | GLint unifm_texcoord;
52 | /// Location of uniform "texsize" in rounded-corners GLSL program.
53 | GLint unifm_texsize;
54 | /// Location of uniform "borderw" in rounded-corners GLSL program.
55 | GLint unifm_borderw;
56 | /// Location of uniform "borderc" in rounded-corners GLSL program.
57 | GLint unifm_borderc;
58 | /// Location of uniform "resolution" in rounded-corners GLSL program.
59 | GLint unifm_resolution;
60 | /// Location of uniform "texture_scr" in rounded-corners GLSL program.
61 | GLint unifm_tex_scr;
62 |
63 | } glx_round_pass_t;
64 |
65 | /// Structure containing GLX-dependent data for a session.
66 | typedef struct glx_session {
67 | // === OpenGL related ===
68 | /// GLX context.
69 | GLXContext context;
70 | /// Whether we have GL_ARB_texture_non_power_of_two.
71 | bool has_texture_non_power_of_two;
72 | /// Current GLX Z value.
73 | int z;
74 | glx_blur_pass_t *blur_passes;
75 | glx_round_pass_t *round_passes;
76 | } glx_session_t;
77 |
78 | /// @brief Wrapper of a binded GLX texture.
79 | typedef struct _glx_texture {
80 | GLuint texture;
81 | GLXPixmap glpixmap;
82 | xcb_pixmap_t pixmap;
83 | GLenum target;
84 | int width;
85 | int height;
86 | bool y_inverted;
87 | } glx_texture_t;
88 |
89 | #define CGLX_SESSION_INIT \
90 | { .context = NULL }
91 |
92 | bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z,
93 | GLfloat factor, const region_t *reg_tgt);
94 |
95 | bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, int dy,
96 | int width, int height, int z, double opacity, bool argb, bool neg,
97 | const region_t *reg_tgt, const glx_prog_main_t *pprogram);
98 |
99 | bool glx_init(session_t *ps, bool need_render);
100 |
101 | void glx_destroy(session_t *ps);
102 |
103 | void glx_on_root_change(session_t *ps);
104 |
105 | bool glx_init_blur(session_t *ps);
106 |
107 | bool glx_init_rounded_corners(session_t *ps);
108 |
109 | #ifdef CONFIG_OPENGL
110 | bool glx_load_prog_main(const char *vshader_str, const char *fshader_str,
111 | glx_prog_main_t *pprogram);
112 | #endif
113 |
114 | bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, int width,
115 | int height, bool repeat, const struct glx_fbconfig_info *);
116 |
117 | void glx_release_pixmap(session_t *ps, glx_texture_t *ptex);
118 |
119 | bool glx_bind_texture(session_t *ps, glx_texture_t **pptex, int x, int y, int width, int height);
120 |
121 | void glx_paint_pre(session_t *ps, region_t *preg) attr_nonnull(1, 2);
122 |
123 | /**
124 | * Check if a texture is binded, or is binded to the given pixmap.
125 | */
126 | static inline bool glx_tex_binded(const glx_texture_t *ptex, xcb_pixmap_t pixmap) {
127 | return ptex && ptex->glpixmap && ptex->texture && (!pixmap || pixmap == ptex->pixmap);
128 | }
129 |
130 | void glx_set_clip(session_t *ps, const region_t *reg);
131 |
132 | bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z,
133 | GLfloat factor_center, const region_t *reg_tgt, glx_blur_cache_t *pbc);
134 |
135 | bool glx_round_corners_dst(session_t *ps, struct managed_win *w,
136 | const glx_texture_t *ptex, int dx, int dy, int width,
137 | int height, float z, float cr, const region_t *reg_tgt);
138 |
139 | GLuint glx_create_shader(GLenum shader_type, const char *shader_str);
140 |
141 | GLuint glx_create_program(const GLuint *const shaders, int nshaders);
142 |
143 | GLuint glx_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str);
144 |
145 | unsigned char *glx_take_screenshot(session_t *ps, int *out_length);
146 |
147 | /**
148 | * Check if there's a GLX context.
149 | */
150 | static inline bool glx_has_context(session_t *ps) {
151 | return ps->psglx && ps->psglx->context;
152 | }
153 |
154 | /**
155 | * Ensure we have a GLX context.
156 | */
157 | static inline bool ensure_glx_context(session_t *ps) {
158 | // Create GLX context
159 | if (!glx_has_context(ps))
160 | glx_init(ps, false);
161 |
162 | return ps->psglx->context;
163 | }
164 |
165 | /**
166 | * Free a GLX texture.
167 | */
168 | static inline void free_texture_r(session_t *ps attr_unused, GLuint *ptexture) {
169 | if (*ptexture) {
170 | assert(glx_has_context(ps));
171 | glDeleteTextures(1, ptexture);
172 | *ptexture = 0;
173 | }
174 | }
175 |
176 | /**
177 | * Free a GLX Framebuffer object.
178 | */
179 | static inline void free_glx_fbo(GLuint *pfbo) {
180 | if (*pfbo) {
181 | glDeleteFramebuffers(1, pfbo);
182 | *pfbo = 0;
183 | }
184 | assert(!*pfbo);
185 | }
186 |
187 | /**
188 | * Free data in glx_blur_cache_t on resize.
189 | */
190 | static inline void free_glx_bc_resize(session_t *ps, glx_blur_cache_t *pbc) {
191 | free_texture_r(ps, &pbc->textures[0]);
192 | free_texture_r(ps, &pbc->textures[1]);
193 | pbc->width = 0;
194 | pbc->height = 0;
195 | }
196 |
197 | /**
198 | * Free a glx_blur_cache_t
199 | */
200 | static inline void free_glx_bc(session_t *ps, glx_blur_cache_t *pbc) {
201 | free_glx_fbo(&pbc->fbo);
202 | free_glx_bc_resize(ps, pbc);
203 | }
204 |
205 | /**
206 | * Free a glx_texture_t.
207 | */
208 | static inline void free_texture(session_t *ps, glx_texture_t **pptex) {
209 | glx_texture_t *ptex = *pptex;
210 |
211 | // Quit if there's nothing
212 | if (!ptex) {
213 | return;
214 | }
215 |
216 | glx_release_pixmap(ps, ptex);
217 |
218 | free_texture_r(ps, &ptex->texture);
219 |
220 | // Free structure itself
221 | free(ptex);
222 | *pptex = NULL;
223 | }
224 |
225 | /**
226 | * Free GLX part of paint_t.
227 | */
228 | static inline void free_paint_glx(session_t *ps, paint_t *ppaint) {
229 | free_texture(ps, &ppaint->ptex);
230 | #ifdef CONFIG_OPENGL
231 | free(ppaint->fbcfg);
232 | #endif
233 | ppaint->fbcfg = NULL;
234 | }
235 |
236 | /**
237 | * Free GLX part of win.
238 | */
239 | static inline void free_win_res_glx(session_t *ps, struct managed_win *w) {
240 | free_paint_glx(ps, &w->paint);
241 | free_paint_glx(ps, &w->shadow_paint);
242 | #ifdef CONFIG_OPENGL
243 | free_glx_bc(ps, &w->glx_blur_cache);
244 | free_texture(ps, &w->glx_texture_bg);
245 | #endif
246 | }
247 |
--------------------------------------------------------------------------------
/src/options.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) Yuxuan Shui
3 | #pragma once
4 |
5 | /// Parse command line options
6 |
7 | #include
8 | #include // for xcb_render_fixed_t
9 |
10 | #include "compiler.h"
11 | #include "config.h"
12 | #include "types.h"
13 | #include "win.h" // for wintype_t
14 |
15 | typedef struct session session_t;
16 |
17 | /// Get config options that are needed to parse the rest of the options
18 | /// Return true if we should quit
19 | bool get_early_config(int argc, char *const *argv, char **config_file, bool *all_xerrors,
20 | bool *fork, int *exit_code);
21 |
22 | /**
23 | * Process arguments and configuration files.
24 | *
25 | * Parameters:
26 | * shadow_enable = Carry overs from parse_config
27 | * fading_enable
28 | * conv_kern_hasneg
29 | * winopt_mask
30 | * Returns:
31 | * Whether configuration are processed successfully.
32 | */
33 | bool must_use get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
34 | bool fading_enable, bool conv_kern_hasneg,
35 | win_option_mask_t *winopt_mask);
36 |
37 | // vim: set noet sw=8 ts=8:
38 |
--------------------------------------------------------------------------------
/src/picom.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // Copyright (c)
3 |
4 | // Throw everything in here.
5 | // !!! DON'T !!!
6 |
7 | // === Includes ===
8 |
9 | #include
10 | #include
11 | #include
12 | #include
13 |
14 | #include
15 | #include "backend/backend.h"
16 | #include "c2.h"
17 | #include "common.h"
18 | #include "compiler.h"
19 | #include "config.h"
20 | #include "log.h" // XXX clean up
21 | #include "region.h"
22 | #include "render.h"
23 | #include "types.h"
24 | #include "utils.h"
25 | #include "win.h"
26 | #include "x.h"
27 |
28 | enum root_flags {
29 | ROOT_FLAGS_SCREEN_CHANGE = 1, // Received RandR screen change notify, we
30 | // use this to track refresh rate changes
31 | ROOT_FLAGS_CONFIGURED = 2 // Received configure notify on the root window
32 | };
33 |
34 | // == Functions ==
35 | // TODO(yshui) move static inline functions that are only used in picom.c, into picom.c
36 |
37 | void add_damage(session_t *ps, const region_t *damage);
38 |
39 | uint32_t determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode);
40 |
41 | void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce);
42 |
43 | void root_damaged(session_t *ps);
44 |
45 | void cxinerama_upd_scrs(session_t *ps);
46 |
47 | void queue_redraw(session_t *ps);
48 |
49 | void discard_ignore(session_t *ps, unsigned long sequence);
50 |
51 | void set_root_flags(session_t *ps, uint64_t flags);
52 |
53 | void quit(session_t *ps);
54 |
55 | xcb_window_t session_get_target_window(session_t *);
56 |
57 | uint8_t session_redirection_mode(session_t *ps);
58 |
59 | /**
60 | * Set a switch_t
array of all unset wintypes to true.
61 | */
62 | static inline void wintype_arr_enable_unset(switch_t arr[]) {
63 | wintype_t i;
64 |
65 | for (i = 0; i < NUM_WINTYPES; ++i)
66 | if (UNSET == arr[i])
67 | arr[i] = ON;
68 | }
69 |
70 | /**
71 | * Check if a window ID exists in an array of window IDs.
72 | *
73 | * @param arr the array of window IDs
74 | * @param count amount of elements in the array
75 | * @param wid window ID to search for
76 | */
77 | static inline bool array_wid_exists(const xcb_window_t *arr, int count, xcb_window_t wid) {
78 | while (count--) {
79 | if (arr[count] == wid) {
80 | return true;
81 | }
82 | }
83 |
84 | return false;
85 | }
86 |
87 | /**
88 | * Destroy a condition list.
89 | */
90 | static inline void free_wincondlst(c2_lptr_t **pcondlst) {
91 | while ((*pcondlst = c2_free_lptr(*pcondlst)))
92 | continue;
93 | }
94 |
95 | #ifndef CONFIG_OPENGL
96 | static inline void free_paint_glx(session_t *ps attr_unused, paint_t *p attr_unused) {
97 | }
98 | static inline void
99 | free_win_res_glx(session_t *ps attr_unused, struct managed_win *w attr_unused) {
100 | }
101 | #endif
102 |
103 | /**
104 | * Dump an drawable's info.
105 | */
106 | static inline void dump_drawable(session_t *ps, xcb_drawable_t drawable) {
107 | auto r = xcb_get_geometry_reply(ps->c, xcb_get_geometry(ps->c, drawable), NULL);
108 | if (!r) {
109 | log_trace("Drawable %#010x: Failed", drawable);
110 | return;
111 | }
112 | log_trace("Drawable %#010x: x = %u, y = %u, wid = %u, hei = %d, b = %u, d = %u",
113 | drawable, r->x, r->y, r->width, r->height, r->border_width, r->depth);
114 | free(r);
115 | }
116 |
--------------------------------------------------------------------------------
/src/picom.modulemap:
--------------------------------------------------------------------------------
1 | // modulemap
2 |
3 | module compiler {
4 | header "compiler.h"
5 | }
6 | module string_utils {
7 | header "string_utils.h"
8 | }
9 | module dbus {
10 | header "dbus.h"
11 | }
12 | module kernel {
13 | header "kernel.h"
14 | }
15 | module utils {
16 | // Has macros expands to calloc/malloc
17 | header "utils.h"
18 | export libc.stdlib
19 | }
20 | module region {
21 | header "region.h"
22 | }
23 | module picom {
24 | header "picom.h"
25 | }
26 | module types {
27 | header "types.h"
28 | }
29 | module c2 {
30 | header "c2.h"
31 | }
32 | module render {
33 | header "render.h"
34 | }
35 | module options {
36 | header "options.h"
37 | }
38 | module opengl {
39 | header "opengl.h"
40 | }
41 | module diagnostic {
42 | header "diagnostic.h"
43 | }
44 | module win_defs {
45 | header "win_defs.h"
46 | }
47 | module win {
48 | header "win.h"
49 | export win_defs
50 | }
51 | module log {
52 | header "log.h"
53 | export compiler
54 | }
55 | module x {
56 | header "x.h"
57 | }
58 | module vsync {
59 | header "vsync.h"
60 | }
61 | module common {
62 | header "common.h"
63 | }
64 | module config {
65 | header "config.h"
66 | }
67 | module xrescheck {
68 | header "xrescheck.h"
69 | }
70 | module cache {
71 | header "cache.h"
72 | }
73 | module backend {
74 | module gl {
75 | module gl_common {
76 | header "backend/gl/gl_common.h"
77 | }
78 | module glx {
79 | header "backend/gl/glx.h"
80 | export GL.glx
81 | }
82 | }
83 | module backend {
84 | header "backend/backend.h"
85 | }
86 | module backend_common {
87 | header "backend/backend_common.h"
88 | }
89 | }
90 | module xcb [system] {
91 | module xcb {
92 | header "/usr/include/xcb/xcb.h"
93 | export *
94 | }
95 | module randr {
96 | header "/usr/include/xcb/randr.h"
97 | export *
98 | }
99 | module render {
100 | header "/usr/include/xcb/render.h"
101 | export *
102 | }
103 | module sync {
104 | header "/usr/include/xcb/sync.h"
105 | export *
106 | }
107 | module composite {
108 | header "/usr/include/xcb/composite.h"
109 | export *
110 | }
111 | module xfixes {
112 | header "/usr/include/xcb/xfixes.h"
113 | export *
114 | }
115 | module damage {
116 | header "/usr/include/xcb/damage.h"
117 | export *
118 | }
119 | module xproto {
120 | header "/usr/include/xcb/xproto.h"
121 | export *
122 | }
123 | module present {
124 | header "/usr/include/xcb/present.h"
125 | }
126 | module util {
127 | module render {
128 | header "/usr/include/xcb/xcb_renderutil.h"
129 | export *
130 | }
131 | }
132 | }
133 | module X11 [system] {
134 | module Xlib {
135 | header "/usr/include/X11/Xlib.h"
136 | export *
137 | }
138 | module Xutil {
139 | header "/usr/include/X11/Xutil.h"
140 | export *
141 | }
142 | }
143 | module GL [system] {
144 | module glx {
145 | header "/usr/include/GL/glx.h"
146 | export *
147 | }
148 | module gl {
149 | header "/usr/include/GL/gl.h"
150 | export *
151 | }
152 | }
153 | module libc [system] {
154 | export *
155 | module assert {
156 | export *
157 | textual header "/usr/include/assert.h"
158 | }
159 | module string {
160 | export *
161 | header "/usr/include/string.h"
162 | }
163 | module ctype {
164 | export *
165 | header "/usr/include/ctype.h"
166 | }
167 | module errno {
168 | export *
169 | header "/usr/include/errno.h"
170 | }
171 | module fenv {
172 | export *
173 | header "/usr/include/fenv.h"
174 | }
175 | module inttypes {
176 | export *
177 | header "/usr/include/inttypes.h"
178 | }
179 | module math {
180 | export *
181 | header "/usr/include/math.h"
182 | }
183 | module setjmp {
184 | export *
185 | header "/usr/include/setjmp.h"
186 | }
187 | module stdio {
188 | export *
189 | header "/usr/include/stdio.h"
190 | }
191 |
192 | module stdlib [system] {
193 | export *
194 | header "/usr/include/stdlib.h"
195 | }
196 | }
197 |
198 | // glib specific header. In it's own module because it
199 | // doesn't exist on some systems with unpatched glib 2.26+
200 | module "xlocale.h" [system] {
201 | export *
202 | header "/usr/include/xlocale.h"
203 | }
204 |
205 | // System header that we have difficult with merging.
206 | module "sys_types.h" [system] {
207 | export *
208 | header "/usr/include/sys/types.h"
209 | }
210 |
211 | module "signal.h" [system] {
212 | export *
213 | header "/usr/include/signal.h"
214 | }
215 |
--------------------------------------------------------------------------------
/src/region.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) 2018 Yuxuan Shui
3 | #pragma once
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "log.h"
10 | #include "utils.h"
11 |
12 | typedef struct pixman_region32 pixman_region32_t;
13 | typedef struct pixman_box32 pixman_box32_t;
14 | typedef pixman_region32_t region_t;
15 | typedef pixman_box32_t rect_t;
16 |
17 | RC_TYPE(region_t, rc_region, pixman_region32_init, pixman_region32_fini, static inline)
18 |
19 | static inline void dump_region(const region_t *x) {
20 | if (log_get_level_tls() < LOG_LEVEL_TRACE) {
21 | return;
22 | }
23 | int nrects;
24 | const rect_t *rects = pixman_region32_rectangles((region_t *)x, &nrects);
25 | log_trace("nrects: %d", nrects);
26 | for (int i = 0; i < nrects; i++)
27 | log_trace("(%d, %d) - (%d, %d)", rects[i].x1, rects[i].y1, rects[i].x2,
28 | rects[i].y2);
29 | }
30 |
31 | /// Convert one xcb rectangle to our rectangle type
32 | static inline rect_t from_x_rect(const xcb_rectangle_t *rect) {
33 | return (rect_t){
34 | .x1 = rect->x,
35 | .y1 = rect->y,
36 | .x2 = rect->x + rect->width,
37 | .y2 = rect->y + rect->height,
38 | };
39 | }
40 |
41 | /// Convert an array of xcb rectangles to our rectangle type
42 | /// Returning an array that needs to be freed
43 | static inline rect_t *from_x_rects(int nrects, const xcb_rectangle_t *rects) {
44 | rect_t *ret = ccalloc(nrects, rect_t);
45 | for (int i = 0; i < nrects; i++) {
46 | ret[i] = from_x_rect(rects + i);
47 | }
48 | return ret;
49 | }
50 |
51 | /**
52 | * Resize a region.
53 | */
54 | static inline void _resize_region(const region_t *region, region_t *output, int dx,
55 | int dy) {
56 | if (!region || !output) {
57 | return;
58 | }
59 | if (!dx && !dy) {
60 | if (region != output) {
61 | pixman_region32_copy(output, (region_t *)region);
62 | }
63 | return;
64 | }
65 | // Loop through all rectangles
66 | int nrects;
67 | int nnewrects = 0;
68 | const rect_t *rects = pixman_region32_rectangles((region_t *)region, &nrects);
69 | auto newrects = ccalloc(nrects, rect_t);
70 | for (int i = 0; i < nrects; i++) {
71 | int x1 = rects[i].x1 - dx;
72 | int y1 = rects[i].y1 - dy;
73 | int x2 = rects[i].x2 + dx;
74 | int y2 = rects[i].y2 + dy;
75 | int wid = x2 - x1;
76 | int hei = y2 - y1;
77 | if (wid <= 0 || hei <= 0) {
78 | continue;
79 | }
80 | newrects[nnewrects] =
81 | (rect_t){.x1 = x1, .x2 = x2, .y1 = y1, .y2 = y2};
82 | ++nnewrects;
83 | }
84 |
85 | pixman_region32_fini(output);
86 | pixman_region32_init_rects(output, newrects, nnewrects);
87 |
88 | free(newrects);
89 | }
90 |
91 | static inline region_t resize_region(const region_t *region, int dx, int dy) {
92 | region_t ret;
93 | pixman_region32_init(&ret);
94 | _resize_region(region, &ret, dx, dy);
95 | return ret;
96 | }
97 |
98 | static inline void resize_region_in_place(region_t *region, int dx, int dy) {
99 | return _resize_region(region, region, dx, dy);
100 | }
101 |
--------------------------------------------------------------------------------
/src/render.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) Yuxuan Shui
3 | #pragma once
4 |
5 | #include
6 | #include
7 | #include
8 | #ifdef CONFIG_OPENGL
9 | #include "backend/gl/glx.h"
10 | #endif
11 | #include "region.h"
12 |
13 | typedef struct _glx_texture glx_texture_t;
14 | typedef struct glx_prog_main glx_prog_main_t;
15 | typedef struct session session_t;
16 |
17 | struct managed_win;
18 |
19 | typedef struct paint {
20 | xcb_pixmap_t pixmap;
21 | xcb_render_picture_t pict;
22 | glx_texture_t *ptex;
23 | #ifdef CONFIG_OPENGL
24 | struct glx_fbconfig_info *fbcfg;
25 | #endif
26 | } paint_t;
27 |
28 | typedef struct clip {
29 | xcb_render_picture_t pict;
30 | int x;
31 | int y;
32 | } clip_t;
33 |
34 | void render(session_t *ps, int x, int y, int dx, int dy, int w, int h, int fullw,
35 | int fullh, double opacity, bool argb, bool neg, int cr,
36 | xcb_render_picture_t pict, glx_texture_t *ptex, const region_t *reg_paint,
37 | const glx_prog_main_t *pprogram, clip_t *clip);
38 | void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint);
39 |
40 | void paint_all(session_t *ps, struct managed_win *const t, bool ignore_damage);
41 |
42 | void free_picture(xcb_connection_t *c, xcb_render_picture_t *p);
43 |
44 | void free_paint(session_t *ps, paint_t *ppaint);
45 | void free_root_tile(session_t *ps);
46 |
47 | bool init_render(session_t *ps);
48 | void deinit_render(session_t *ps);
49 |
50 | int maximum_buffer_age(session_t *);
51 |
--------------------------------------------------------------------------------
/src/string_utils.c:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) Yuxuan Shui
3 |
4 | #include
5 |
6 | #include
7 |
8 | #include "compiler.h"
9 | #include "string_utils.h"
10 | #include "utils.h"
11 |
12 | #pragma GCC diagnostic push
13 |
14 | // gcc warns about legitimate strncpy in mstrjoin and mstrextend
15 | // strncpy(str, src1, len1) intentional truncates the null byte from src1.
16 | // strncpy(str+len1, src2, len2) uses bound depends on the source argument,
17 | // but str is allocated with len1+len2+1, so this strncpy can't overflow
18 | #pragma GCC diagnostic ignored "-Wpragmas"
19 | #pragma GCC diagnostic ignored "-Wstringop-truncation"
20 | #pragma GCC diagnostic ignored "-Wstringop-overflow"
21 |
22 | /**
23 | * Allocate the space and join two strings.
24 | */
25 | char *mstrjoin(const char *src1, const char *src2) {
26 | auto len1 = strlen(src1);
27 | auto len2 = strlen(src2);
28 | auto len = len1 + len2 + 1;
29 | auto str = ccalloc(len, char);
30 |
31 | strncpy(str, src1, len1);
32 | strncpy(str + len1, src2, len2);
33 | str[len - 1] = '\0';
34 |
35 | return str;
36 | }
37 |
38 | TEST_CASE(mstrjoin) {
39 | char *str = mstrjoin("asdf", "qwer");
40 | TEST_STREQUAL(str, "asdfqwer");
41 | free(str);
42 |
43 | str = mstrjoin("", "qwer");
44 | TEST_STREQUAL(str, "qwer");
45 | free(str);
46 |
47 | str = mstrjoin("asdf", "");
48 | TEST_STREQUAL(str, "asdf");
49 | free(str);
50 | }
51 |
52 | /**
53 | * Concatenate a string on heap with another string.
54 | */
55 | void mstrextend(char **psrc1, const char *src2) {
56 | if (!*psrc1) {
57 | *psrc1 = strdup(src2);
58 | return;
59 | }
60 |
61 | auto len1 = strlen(*psrc1);
62 | auto len2 = strlen(src2);
63 | auto len = len1 + len2 + 1;
64 | *psrc1 = crealloc(*psrc1, len);
65 |
66 | strncpy(*psrc1 + len1, src2, len2);
67 | (*psrc1)[len - 1] = '\0';
68 | }
69 |
70 | TEST_CASE(mstrextend) {
71 | char *str1 = NULL;
72 | mstrextend(&str1, "asdf");
73 | TEST_STREQUAL(str1, "asdf");
74 |
75 | mstrextend(&str1, "asd");
76 | TEST_STREQUAL(str1, "asdfasd");
77 |
78 | mstrextend(&str1, "");
79 | TEST_STREQUAL(str1, "asdfasd");
80 | free(str1);
81 | }
82 |
83 | #pragma GCC diagnostic pop
84 |
85 | /// Parse a floating point number of form (+|-)?[0-9]*(\.[0-9]*)
86 | double strtod_simple(const char *src, const char **end) {
87 | double neg = 1;
88 | if (*src == '-') {
89 | neg = -1;
90 | src++;
91 | } else if (*src == '+') {
92 | src++;
93 | }
94 |
95 | double ret = 0;
96 | while (*src >= '0' && *src <= '9') {
97 | ret = ret * 10 + (*src - '0');
98 | src++;
99 | }
100 |
101 | if (*src == '.') {
102 | double frac = 0, mult = 0.1;
103 | src++;
104 | while (*src >= '0' && *src <= '9') {
105 | frac += mult * (*src - '0');
106 | mult *= 0.1;
107 | src++;
108 | }
109 | ret += frac;
110 | }
111 |
112 | *end = src;
113 | return ret * neg;
114 | }
115 |
116 | TEST_CASE(strtod_simple) {
117 | const char *end;
118 | double result = strtod_simple("1.0", &end);
119 | TEST_EQUAL(result, 1);
120 | TEST_EQUAL(*end, '\0');
121 |
122 | result = strtod_simple("-1.0", &end);
123 | TEST_EQUAL(result, -1);
124 | TEST_EQUAL(*end, '\0');
125 |
126 | result = strtod_simple("+.5", &end);
127 | TEST_EQUAL(result, 0.5);
128 | TEST_EQUAL(*end, '\0');
129 | }
130 |
--------------------------------------------------------------------------------
/src/string_utils.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) Yuxuan Shui
3 | #pragma once
4 | #include
5 | #include
6 |
7 | #include "compiler.h"
8 |
9 | #define mstrncmp(s1, s2) strncmp((s1), (s2), strlen(s1))
10 |
11 | char *mstrjoin(const char *src1, const char *src2);
12 | char *mstrjoin3(const char *src1, const char *src2, const char *src3);
13 | void mstrextend(char **psrc1, const char *src2);
14 |
15 | /// Parse a floating point number of form (+|-)?[0-9]*(\.[0-9]*)
16 | double strtod_simple(const char *, const char **);
17 |
18 | static inline int uitostr(unsigned int n, char *buf) {
19 | int ret = 0;
20 | unsigned int tmp = n;
21 | while (tmp > 0) {
22 | tmp /= 10;
23 | ret++;
24 | }
25 |
26 | if (ret == 0)
27 | ret = 1;
28 |
29 | int pos = ret;
30 | while (pos--) {
31 | buf[pos] = (char)(n % 10 + '0');
32 | n /= 10;
33 | }
34 | return ret;
35 | }
36 |
37 | static inline const char *skip_space_const(const char *src) {
38 | if (!src)
39 | return NULL;
40 | while (*src && isspace((unsigned char)*src))
41 | src++;
42 | return src;
43 | }
44 |
45 | static inline char *skip_space_mut(char *src) {
46 | if (!src)
47 | return NULL;
48 | while (*src && isspace((unsigned char)*src))
49 | src++;
50 | return src;
51 | }
52 |
53 | #define skip_space(x) \
54 | _Generic((x), char * : skip_space_mut, const char * : skip_space_const)(x)
55 |
--------------------------------------------------------------------------------
/src/types.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) 2018 Yuxuan Shui
3 |
4 | #pragma once
5 |
6 | /// Some common types
7 |
8 | #include
9 |
10 | /// Enumeration type to represent switches.
11 | typedef enum {
12 | OFF = 0, // false
13 | ON, // true
14 | UNSET
15 | } switch_t;
16 |
17 | /// A structure representing margins around a rectangle.
18 | typedef struct {
19 | int top;
20 | int left;
21 | int bottom;
22 | int right;
23 | } margin_t;
24 |
25 | struct color {
26 | double red, green, blue, alpha;
27 | };
28 |
29 | typedef uint32_t opacity_t;
30 |
31 | #define MARGIN_INIT \
32 | { 0, 0, 0, 0 }
33 |
--------------------------------------------------------------------------------
/src/uthash_extra.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #define HASH_ITER2(head, el) \
6 | for (__typeof__(head) el = (head), __tmp = el != NULL ? el->hh.next : NULL; \
7 | el != NULL; el = __tmp, __tmp = el != NULL ? el->hh.next : NULL)
8 |
--------------------------------------------------------------------------------
/src/utils.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "compiler.h"
6 | #include "string_utils.h"
7 | #include "utils.h"
8 |
9 | /// Report allocation failure without allocating memory
10 | void report_allocation_failure(const char *func, const char *file, unsigned int line) {
11 | // Since memory allocation failed, we try to print this error message without any
12 | // memory allocation. Since logging framework allocates memory (and might even
13 | // have not been initialized yet), so we can't use it.
14 | char buf[11];
15 | int llen = uitostr(line, buf);
16 | const char msg1[] = " has failed to allocate memory, ";
17 | const char msg2[] = ". Aborting...\n";
18 | const struct iovec v[] = {
19 | {.iov_base = (void *)func, .iov_len = strlen(func)},
20 | {.iov_base = "()", .iov_len = 2},
21 | {.iov_base = (void *)msg1, .iov_len = sizeof(msg1) - 1},
22 | {.iov_base = "at ", .iov_len = 3},
23 | {.iov_base = (void *)file, .iov_len = strlen(file)},
24 | {.iov_base = ":", .iov_len = 1},
25 | {.iov_base = buf, .iov_len = (size_t)llen},
26 | {.iov_base = (void *)msg2, .iov_len = sizeof(msg2) - 1},
27 | };
28 |
29 | writev(STDERR_FILENO, v, ARR_SIZE(v));
30 | abort();
31 |
32 | unreachable;
33 | }
34 |
35 | ///
36 | /// Calculates next closest power of two of 32bit integer n
37 | /// ref: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
38 | ///
39 | int next_power_of_two(int n)
40 | {
41 | n--;
42 | n |= n >> 1;
43 | n |= n >> 2;
44 | n |= n >> 4;
45 | n |= n >> 8;
46 | n |= n >> 16;
47 | n++;
48 | return n;
49 | }
50 |
51 | // vim: set noet sw=8 ts=8 :
52 |
--------------------------------------------------------------------------------
/src/vsync.c:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) Yuxuan Shui
3 |
4 | /// Function pointers to init VSync modes.
5 |
6 | #include "common.h"
7 | #include "log.h"
8 |
9 | #ifdef CONFIG_OPENGL
10 | #include "backend/gl/glx.h"
11 | #include "opengl.h"
12 | #endif
13 |
14 | #ifdef CONFIG_VSYNC_DRM
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #endif
22 |
23 | #include "config.h"
24 | #include "vsync.h"
25 |
26 | #ifdef CONFIG_VSYNC_DRM
27 | /**
28 | * Wait for next VSync, DRM method.
29 | *
30 | * Stolen from:
31 | * https://github.com/MythTV/mythtv/blob/master/mythtv/libs/libmythtv/vsync.cpp
32 | */
33 | static int vsync_drm_wait(session_t *ps) {
34 | int ret = -1;
35 | drm_wait_vblank_t vbl;
36 |
37 | vbl.request.type = _DRM_VBLANK_RELATIVE, vbl.request.sequence = 1;
38 |
39 | do {
40 | ret = ioctl(ps->drm_fd, DRM_IOCTL_WAIT_VBLANK, &vbl);
41 | vbl.request.type &= ~(uint)_DRM_VBLANK_RELATIVE;
42 | } while (ret && errno == EINTR);
43 |
44 | if (ret)
45 | log_error("VBlank ioctl did not work, unimplemented in this drmver?");
46 |
47 | return ret;
48 | }
49 |
50 | /**
51 | * Initialize DRM VSync.
52 | *
53 | * @return true for success, false otherwise
54 | */
55 | static bool vsync_drm_init(session_t *ps) {
56 | // Should we always open card0?
57 | if (ps->drm_fd < 0 && (ps->drm_fd = open("/dev/dri/card0", O_RDWR)) < 0) {
58 | log_error("Failed to open device.");
59 | return false;
60 | }
61 |
62 | if (vsync_drm_wait(ps))
63 | return false;
64 |
65 | return true;
66 | }
67 | #endif
68 |
69 | #ifdef CONFIG_OPENGL
70 | /**
71 | * Initialize OpenGL VSync.
72 | *
73 | * Stolen from:
74 | * http://git.tuxfamily.org/?p=ccm/cairocompmgr.git;a=commitdiff;h=efa4ceb97da501e8630ca7f12c99b1dce853c73e
75 | * Possible original source: http://www.inb.uni-luebeck.de/~boehme/xvideo_sync.html
76 | *
77 | * @return true for success, false otherwise
78 | */
79 | static bool vsync_opengl_init(session_t *ps) {
80 | if (!ensure_glx_context(ps))
81 | return false;
82 |
83 | return glxext.has_GLX_SGI_video_sync;
84 | }
85 |
86 | static bool vsync_opengl_oml_init(session_t *ps) {
87 | if (!ensure_glx_context(ps))
88 | return false;
89 |
90 | return glxext.has_GLX_OML_sync_control;
91 | }
92 |
93 | static inline bool vsync_opengl_swc_swap_interval(session_t *ps, int interval) {
94 | if (glxext.has_GLX_MESA_swap_control)
95 | return glXSwapIntervalMESA((uint)interval) == 0;
96 | else if (glxext.has_GLX_SGI_swap_control)
97 | return glXSwapIntervalSGI(interval) == 0;
98 | else if (glxext.has_GLX_EXT_swap_control) {
99 | GLXDrawable d = glXGetCurrentDrawable();
100 | if (d == None) {
101 | // We don't have a context??
102 | return false;
103 | }
104 | glXSwapIntervalEXT(ps->dpy, glXGetCurrentDrawable(), interval);
105 | return true;
106 | }
107 | return false;
108 | }
109 |
110 | static bool vsync_opengl_swc_init(session_t *ps) {
111 | if (!bkend_use_glx(ps)) {
112 | log_error("OpenGL swap control requires the GLX backend.");
113 | return false;
114 | }
115 |
116 | if (!vsync_opengl_swc_swap_interval(ps, 1)) {
117 | log_error("Failed to load a swap control extension.");
118 | return false;
119 | }
120 |
121 | return true;
122 | }
123 |
124 | /**
125 | * Wait for next VSync, OpenGL method.
126 | */
127 | static int vsync_opengl_wait(session_t *ps attr_unused) {
128 | unsigned vblank_count = 0;
129 |
130 | glXGetVideoSyncSGI(&vblank_count);
131 | glXWaitVideoSyncSGI(2, (vblank_count + 1) % 2, &vblank_count);
132 | return 0;
133 | }
134 |
135 | /**
136 | * Wait for next VSync, OpenGL OML method.
137 | *
138 | * https://mail.gnome.org/archives/clutter-list/2012-November/msg00031.html
139 | */
140 | static int vsync_opengl_oml_wait(session_t *ps) {
141 | int64_t ust = 0, msc = 0, sbc = 0;
142 |
143 | glXGetSyncValuesOML(ps->dpy, ps->reg_win, &ust, &msc, &sbc);
144 | glXWaitForMscOML(ps->dpy, ps->reg_win, 0, 2, (msc + 1) % 2, &ust, &msc, &sbc);
145 | return 0;
146 | }
147 | #endif
148 |
149 | /**
150 | * Initialize current VSync method.
151 | */
152 | bool vsync_init(session_t *ps) {
153 | #ifdef CONFIG_OPENGL
154 | if (bkend_use_glx(ps)) {
155 | // Mesa turns on swap control by default, undo that
156 | vsync_opengl_swc_swap_interval(ps, 0);
157 | }
158 | #endif
159 | #ifdef CONFIG_VSYNC_DRM
160 | log_warn("The DRM vsync method is deprecated, please don't enable it.");
161 | #endif
162 |
163 | if (!ps->o.vsync) {
164 | return true;
165 | }
166 |
167 | #ifdef CONFIG_OPENGL
168 | if (bkend_use_glx(ps)) {
169 | if (!vsync_opengl_swc_init(ps)) {
170 | return false;
171 | }
172 | ps->vsync_wait = NULL; // glXSwapBuffers will automatically wait
173 | // for vsync, we don't need to do anything.
174 | return true;
175 | }
176 | #endif
177 |
178 | // Oh no, we are not using glx backend.
179 | // Throwing things at wall.
180 | #ifdef CONFIG_OPENGL
181 | if (vsync_opengl_oml_init(ps)) {
182 | log_info("Using the opengl-oml vsync method");
183 | ps->vsync_wait = vsync_opengl_oml_wait;
184 | return true;
185 | }
186 |
187 | if (vsync_opengl_init(ps)) {
188 | log_info("Using the opengl vsync method");
189 | ps->vsync_wait = vsync_opengl_wait;
190 | return true;
191 | }
192 | #endif
193 |
194 | #ifdef CONFIG_VSYNC_DRM
195 | if (vsync_drm_init(ps)) {
196 | log_info("Using the drm vsync method");
197 | ps->vsync_wait = vsync_drm_wait;
198 | return true;
199 | }
200 | #endif
201 |
202 | log_error("No supported vsync method found for this backend");
203 | return false;
204 | }
205 |
--------------------------------------------------------------------------------
/src/vsync.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MPL-2.0
2 | // Copyright (c) Yuxuan Shui
3 | #include
4 |
5 | typedef struct session session_t;
6 |
7 | bool vsync_init(session_t *ps);
8 |
--------------------------------------------------------------------------------
/src/win_defs.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 |
4 | typedef enum {
5 | WINTYPE_UNKNOWN,
6 | WINTYPE_DESKTOP,
7 | WINTYPE_DOCK,
8 | WINTYPE_TOOLBAR,
9 | WINTYPE_MENU,
10 | WINTYPE_UTILITY,
11 | WINTYPE_SPLASH,
12 | WINTYPE_DIALOG,
13 | WINTYPE_NORMAL,
14 | WINTYPE_DROPDOWN_MENU,
15 | WINTYPE_POPUP_MENU,
16 | WINTYPE_TOOLTIP,
17 | WINTYPE_NOTIFICATION,
18 | WINTYPE_COMBO,
19 | WINTYPE_DND,
20 | NUM_WINTYPES
21 | } wintype_t;
22 |
23 | /// Enumeration type of window painting mode.
24 | typedef enum {
25 | WMODE_TRANS, // The window body is (potentially) transparent
26 | WMODE_FRAME_TRANS, // The window body is opaque, but the frame is not
27 | WMODE_SOLID, // The window is opaque including the frame
28 | } winmode_t;
29 |
30 | /// Transition table:
31 | /// (DESTROYED is when the win struct is destroyed and freed)
32 | /// ('o' means in all other cases)
33 | /// (Window is created in the UNMAPPED state)
34 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+
35 | /// | |UNMAPPING|DESTROYING|MAPPING|FADING |UNMAPPED| MAPPED |DESTROYED|
36 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+
37 | /// | UNMAPPING | o | Window |Window | - | Fading | - | - |
38 | /// | | |destroyed |mapped | |finished| | |
39 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+
40 | /// | DESTROYING | - | o | - | - | - | - | Fading |
41 | /// | | | | | | | |finished |
42 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+
43 | /// | MAPPING | Window | Window | o |Opacity| - | Fading | - |
44 | /// | |unmapped |destroyed | |change | |finished| |
45 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+
46 | /// | FADING | Window | Window | - | o | - | Fading | - |
47 | /// | |unmapped |destroyed | | | |finished| |
48 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+
49 | /// | UNMAPPED | - | - |Window | - | o | - | Window |
50 | /// | | | |mapped | | | |destroyed|
51 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+
52 | /// | MAPPED | Window | Window | - |Opacity| - | o | - |
53 | /// | |unmapped |destroyed | |change | | | |
54 | /// +-------------+---------+----------+-------+-------+--------+--------+---------+
55 | typedef enum {
56 | // The window is being faded out because it's unmapped.
57 | WSTATE_UNMAPPING,
58 | // The window is being faded out because it's destroyed,
59 | WSTATE_DESTROYING,
60 | // The window is being faded in
61 | WSTATE_MAPPING,
62 | // Window opacity is not at the target level
63 | WSTATE_FADING,
64 | // The window is mapped, no fading is in progress.
65 | WSTATE_MAPPED,
66 | // The window is unmapped, no fading is in progress.
67 | WSTATE_UNMAPPED,
68 | } winstate_t;
69 |
70 | enum win_flags {
71 | // Note: *_NONE flags are mostly redudant and meant for detecting logical errors
72 | // in the code
73 |
74 | /// pixmap is out of date, will be update in win_process_flags
75 | WIN_FLAGS_PIXMAP_STALE = 1,
76 | /// window does not have pixmap bound
77 | WIN_FLAGS_PIXMAP_NONE = 2,
78 | /// there was an error trying to bind the images
79 | WIN_FLAGS_IMAGE_ERROR = 4,
80 | /// shadow is out of date, will be updated in win_process_flags
81 | WIN_FLAGS_SHADOW_STALE = 8,
82 | /// shadow has not been generated
83 | WIN_FLAGS_SHADOW_NONE = 16,
84 | /// the client window needs to be updated
85 | WIN_FLAGS_CLIENT_STALE = 32,
86 | /// the window is mapped by X, we need to call map_win_start for it
87 | WIN_FLAGS_MAPPED = 64,
88 | /// this window has properties which needs to be updated
89 | WIN_FLAGS_PROPERTY_STALE = 128,
90 | // TODO(yshui) _maybe_ split SIZE_STALE into SIZE_STALE and SHAPE_STALE
91 | /// this window has an unhandled size/shape change
92 | WIN_FLAGS_SIZE_STALE = 256,
93 | /// this window has an unhandled position (i.e. x and y) change
94 | WIN_FLAGS_POSITION_STALE = 512,
95 | /// need better name for this, is set when some aspects of the window changed
96 | WIN_FLAGS_FACTOR_CHANGED = 1024,
97 | };
98 |
99 | static const uint64_t WIN_FLAGS_IMAGES_STALE =
100 | WIN_FLAGS_PIXMAP_STALE | WIN_FLAGS_SHADOW_STALE;
101 |
102 | #define WIN_FLAGS_IMAGES_NONE (WIN_FLAGS_PIXMAP_NONE | WIN_FLAGS_SHADOW_NONE)
103 |
--------------------------------------------------------------------------------
/src/xrescheck.c:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // Copyright (c) 2014 Richard Grenville
3 |
4 | #include "compiler.h"
5 | #include "log.h"
6 |
7 | #include "xrescheck.h"
8 |
9 | static xrc_xid_record_t *gs_xid_records = NULL;
10 |
11 | #define HASH_ADD_XID(head, xidfield, add) HASH_ADD(hh, head, xidfield, sizeof(xid), add)
12 |
13 | #define HASH_FIND_XID(head, findxid, out) HASH_FIND(hh, head, findxid, sizeof(xid), out)
14 |
15 | #define M_CPY_POS_DATA(prec) \
16 | prec->file = file; \
17 | prec->func = func; \
18 | prec->line = line;
19 |
20 | /**
21 | * @brief Add a record of given XID to the allocation table.
22 | */
23 | void xrc_add_xid_(XID xid, const char *type, M_POS_DATA_PARAMS) {
24 | auto prec = ccalloc(1, xrc_xid_record_t);
25 | prec->xid = xid;
26 | prec->type = type;
27 | M_CPY_POS_DATA(prec);
28 |
29 | HASH_ADD_XID(gs_xid_records, xid, prec);
30 | }
31 |
32 | /**
33 | * @brief Delete a record of given XID in the allocation table.
34 | */
35 | void xrc_delete_xid_(XID xid, M_POS_DATA_PARAMS) {
36 | xrc_xid_record_t *prec = NULL;
37 | HASH_FIND_XID(gs_xid_records, &xid, prec);
38 | if (!prec) {
39 | log_error("XRC: %s:%d %s(): Can't find XID %#010lx we want to delete.",
40 | file, line, func, xid);
41 | return;
42 | }
43 | HASH_DEL(gs_xid_records, prec);
44 | free(prec);
45 | }
46 |
47 | /**
48 | * @brief Report about issues found in the XID allocation table.
49 | */
50 | void xrc_report_xid(void) {
51 | for (xrc_xid_record_t *prec = gs_xid_records; prec; prec = prec->hh.next)
52 | log_trace("XRC: %s:%d %s(): %#010lx (%s) not freed.\n", prec->file,
53 | prec->line, prec->func, prec->xid, prec->type);
54 | }
55 |
56 | /**
57 | * @brief Clear the XID allocation table.
58 | */
59 | void xrc_clear_xid(void) {
60 | xrc_xid_record_t *prec = NULL, *ptmp = NULL;
61 | HASH_ITER(hh, gs_xid_records, prec, ptmp) {
62 | HASH_DEL(gs_xid_records, prec);
63 | free(prec);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/xrescheck.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // Copyright (c) 2014 Richard Grenville
3 | #pragma once
4 |
5 | #include "common.h"
6 | #include "uthash.h"
7 |
8 | typedef struct {
9 | XID xid;
10 | const char *type;
11 | const char *file;
12 | const char *func;
13 | int line;
14 | UT_hash_handle hh;
15 | } xrc_xid_record_t;
16 |
17 | #define M_POS_DATA_PARAMS const char *file, int line, const char *func
18 | #define M_POS_DATA_PASSTHROUGH file, line, func
19 | #define M_POS_DATA __FILE__, __LINE__, __func__
20 |
21 | void xrc_add_xid_(XID xid, const char *type, M_POS_DATA_PARAMS);
22 |
23 | #define xrc_add_xid(xid, type) xrc_add_xid_(xid, type, M_POS_DATA)
24 |
25 | void xrc_delete_xid_(XID xid, M_POS_DATA_PARAMS);
26 |
27 | #define xrc_delete_xid(xid) xrc_delete_xid_(xid, M_POS_DATA)
28 |
29 | void xrc_report_xid(void);
30 |
31 | void xrc_clear_xid(void);
32 |
33 | // Pixmap
34 |
35 | static inline void xcb_create_pixmap_(xcb_connection_t *c, uint8_t depth,
36 | xcb_pixmap_t pixmap, xcb_drawable_t drawable,
37 | uint16_t width, uint16_t height, M_POS_DATA_PARAMS) {
38 | xcb_create_pixmap(c, depth, pixmap, drawable, width, height);
39 | xrc_add_xid_(pixmap, "Pixmap", M_POS_DATA_PASSTHROUGH);
40 | }
41 |
42 | #define xcb_create_pixmap(c, depth, pixmap, drawable, width, height) \
43 | xcb_create_pixmap_(c, depth, pixmap, drawable, width, height, M_POS_DATA)
44 |
45 | static inline xcb_void_cookie_t
46 | xcb_composite_name_window_pixmap_(xcb_connection_t *c, xcb_window_t window,
47 | xcb_pixmap_t pixmap, M_POS_DATA_PARAMS) {
48 | xcb_void_cookie_t ret = xcb_composite_name_window_pixmap(c, window, pixmap);
49 | xrc_add_xid_(pixmap, "PixmapC", M_POS_DATA_PASSTHROUGH);
50 | return ret;
51 | }
52 |
53 | #define xcb_composite_name_window_pixmap(dpy, window, pixmap) \
54 | xcb_composite_name_window_pixmap_(dpy, window, pixmap, M_POS_DATA)
55 |
56 | static inline void
57 | xcb_free_pixmap_(xcb_connection_t *c, xcb_pixmap_t pixmap, M_POS_DATA_PARAMS) {
58 | xcb_free_pixmap(c, pixmap);
59 | xrc_delete_xid_(pixmap, M_POS_DATA_PASSTHROUGH);
60 | }
61 |
62 | #define xcb_free_pixmap(c, pixmap) xcb_free_pixmap_(c, pixmap, M_POS_DATA);
63 |
--------------------------------------------------------------------------------
/subprojects/test.h/meson.build:
--------------------------------------------------------------------------------
1 | project('test.h', 'c')
2 | test_h_dep = declare_dependency(include_directories: include_directories('.'))
3 |
--------------------------------------------------------------------------------
/subprojects/test.h/test.h:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | #pragma once
3 |
4 | #ifdef UNIT_TEST
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
12 | defined(__NetBSD__) || defined(__OpenBSD__)
13 | #define USE_SYSCTL_FOR_ARGS 1
14 | // clang-format off
15 | #include
16 | #include
17 | // clang-format on
18 | #include // getpid
19 | #endif
20 |
21 | struct test_file_metadata;
22 |
23 | struct test_failure {
24 | bool present;
25 | const char *message;
26 | const char *file;
27 | int line;
28 | };
29 |
30 | struct test_case_metadata {
31 | void (*fn)(struct test_case_metadata *, struct test_file_metadata *);
32 | struct test_failure failure;
33 | const char *name;
34 | struct test_case_metadata *next;
35 | };
36 |
37 | struct test_file_metadata {
38 | bool registered;
39 | const char *name;
40 | struct test_file_metadata *next;
41 | struct test_case_metadata *tests;
42 | };
43 |
44 | struct test_file_metadata __attribute__((weak)) * test_file_head;
45 |
46 | #define SET_FAILURE(_message) \
47 | metadata->failure = (struct test_failure) { \
48 | .message = _message, .file = __FILE__, .line = __LINE__, .present = true, \
49 | }
50 |
51 | #define TEST_EQUAL(a, b) \
52 | do { \
53 | if ((a) != (b)) { \
54 | SET_FAILURE(#a " != " #b); \
55 | return; \
56 | } \
57 | } while (0)
58 |
59 | #define TEST_TRUE(a) \
60 | do { \
61 | if (!(a)) { \
62 | SET_FAILURE(#a " is not true"); \
63 | return; \
64 | } \
65 | } while (0)
66 |
67 | #define TEST_STREQUAL(a, b) \
68 | do { \
69 | if (strcmp(a, b) != 0) { \
70 | SET_FAILURE(#a " != " #b); \
71 | return; \
72 | } \
73 | } while (0)
74 |
75 | #define TEST_CASE(_name) \
76 | static void __test_h_##_name(struct test_case_metadata *, \
77 | struct test_file_metadata *); \
78 | static struct test_file_metadata __test_h_file; \
79 | static struct test_case_metadata __test_h_meta_##_name = { \
80 | .name = #_name, \
81 | .fn = __test_h_##_name, \
82 | }; \
83 | static void __attribute__((constructor(101))) __test_h_##_name##_register(void) { \
84 | __test_h_meta_##_name.next = __test_h_file.tests; \
85 | __test_h_file.tests = &__test_h_meta_##_name; \
86 | if (!__test_h_file.registered) { \
87 | __test_h_file.name = __FILE__; \
88 | __test_h_file.next = test_file_head; \
89 | test_file_head = &__test_h_file; \
90 | __test_h_file.registered = true; \
91 | } \
92 | } \
93 | static void __test_h_##_name( \
94 | struct test_case_metadata *metadata __attribute__((unused)), \
95 | struct test_file_metadata *file_metadata __attribute__((unused)))
96 |
97 | extern void __attribute__((weak)) (*test_h_unittest_setup)(void);
98 | /// Run defined tests, return true if all tests succeeds
99 | /// @param[out] tests_run if not NULL, set to whether tests were run
100 | static inline void __attribute__((constructor(102))) run_tests(void) {
101 | bool should_run = false;
102 | #ifdef USE_SYSCTL_FOR_ARGS
103 | int mib[] = {
104 | CTL_KERN,
105 | #if defined(__NetBSD__) || defined(__OpenBSD__)
106 | KERN_PROC_ARGS,
107 | getpid(),
108 | KERN_PROC_ARGV,
109 | #else
110 | KERN_PROC,
111 | KERN_PROC_ARGS,
112 | getpid(),
113 | #endif
114 | };
115 | char *arg = NULL;
116 | size_t arglen;
117 | sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &arglen, NULL, 0);
118 | arg = malloc(arglen);
119 | sysctl(mib, sizeof(mib) / sizeof(mib[0]), arg, &arglen, NULL, 0);
120 | #else
121 | FILE *cmdlinef = fopen("/proc/self/cmdline", "r");
122 | char *arg = NULL;
123 | int arglen;
124 | fscanf(cmdlinef, "%ms%n", &arg, &arglen);
125 | fclose(cmdlinef);
126 | #endif
127 | for (char *pos = arg; pos < arg + arglen; pos += strlen(pos) + 1) {
128 | if (strcmp(pos, "--unittest") == 0) {
129 | should_run = true;
130 | break;
131 | }
132 | }
133 | free(arg);
134 |
135 | if (!should_run) {
136 | return;
137 | }
138 |
139 | if (&test_h_unittest_setup) {
140 | test_h_unittest_setup();
141 | }
142 |
143 | struct test_file_metadata *i = test_file_head;
144 | int failed = 0, success = 0;
145 | while (i) {
146 | fprintf(stderr, "Running tests from %s:\n", i->name);
147 | struct test_case_metadata *j = i->tests;
148 | while (j) {
149 | fprintf(stderr, "\t%s ... ", j->name);
150 | j->failure.present = false;
151 | j->fn(j, i);
152 | if (j->failure.present) {
153 | fprintf(stderr, "failed (%s at %s:%d)\n", j->failure.message,
154 | j->failure.file, j->failure.line);
155 | failed++;
156 | } else {
157 | fprintf(stderr, "passed\n");
158 | success++;
159 | }
160 | j = j->next;
161 | }
162 | fprintf(stderr, "\n");
163 | i = i->next;
164 | }
165 | int total = failed + success;
166 | fprintf(stderr, "Test results: passed %d/%d, failed %d/%d\n", success, total,
167 | failed, total);
168 | exit(failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
169 | }
170 |
171 | #else
172 |
173 | #include
174 |
175 | #define TEST_CASE(name) static void __attribute__((unused)) __test_h_##name(void)
176 |
177 | #define TEST_EQUAL(a, b) \
178 | (void)(a); \
179 | (void)(b)
180 | #define TEST_TRUE(a) (void)(a)
181 | #define TEST_STREQUAL(a, b) \
182 | (void)(a); \
183 | (void)(b)
184 |
185 | #endif
186 |
--------------------------------------------------------------------------------
/tests/configs/clear_shadow_unredirected.conf:
--------------------------------------------------------------------------------
1 | shadow = true;
2 | shadow-exclude = [
3 | "name = 'NoShadow'"
4 | ]
5 | unredir-if-possible = true;
6 |
--------------------------------------------------------------------------------
/tests/configs/empty.conf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yaocccc/picom/eddcf51dc10182b50cdbb22f11f155a836a8aa53/tests/configs/empty.conf
--------------------------------------------------------------------------------
/tests/configs/issue239.conf:
--------------------------------------------------------------------------------
1 | fading = true;
2 | fade-in-step = 1;
3 | fade-out-step = 0.01;
4 | shadow = true;
5 | shadow-exclude = [
6 | "name = 'NoShadow'"
7 | ]
8 |
--------------------------------------------------------------------------------
/tests/configs/issue239_2.conf:
--------------------------------------------------------------------------------
1 | fading = true;
2 | fade-in-step = 1;
3 | fade-out-step = 0.01;
4 | shadow = true;
5 | shadow-exclude = [
6 | "name = 'NoShadow'"
7 | ]
8 | unredir-if-possible = true;
9 |
--------------------------------------------------------------------------------
/tests/configs/issue239_3.conf:
--------------------------------------------------------------------------------
1 | shadow = true;
2 | shadow-exclude = [
3 | "name = 'NoShadow'"
4 | ]
5 |
--------------------------------------------------------------------------------
/tests/configs/issue314.conf:
--------------------------------------------------------------------------------
1 | fading = true
2 | fade-in-step = 0.01
3 | fade-out-step = 0.01
4 | inactive-opacity = 0
5 | blur-background = true
6 | force-win-blend = true
7 |
--------------------------------------------------------------------------------
/tests/configs/issue357.conf:
--------------------------------------------------------------------------------
1 | fading = true;
2 | fade-in-step = 1;
3 | fade-out-step = 0.01;
4 |
--------------------------------------------------------------------------------
/tests/configs/issue394.conf:
--------------------------------------------------------------------------------
1 | fading = true;
2 | fade-in-step = 1;
3 | fade-out-step = 0.01;
4 | shadow = true;
5 |
--------------------------------------------------------------------------------
/tests/configs/issue465.conf:
--------------------------------------------------------------------------------
1 | shadow = true;
2 | shadow-exclude = [
3 | "focused != 1"
4 | ];
5 |
--------------------------------------------------------------------------------
/tests/run_one_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -xe
3 | if [ -z $DISPLAY ]; then
4 | exec xvfb-run -s "+extension composite" -a $0 $1 $2 $3
5 | fi
6 |
7 | echo "Running test $2"
8 |
9 | # TODO keep the log file, and parse it to see if test is successful
10 | ($1 --dbus --experimental-backends --backend dummy --log-level=debug --log-file=$PWD/log --config=$2) &
11 | main_pid=$!
12 | $3
13 |
14 | kill -INT $main_pid || true
15 | cat log
16 | rm log
17 | wait $main_pid
18 |
19 |
20 |
--------------------------------------------------------------------------------
/tests/run_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | exe=$(realpath $1)
4 | cd $(dirname $0)
5 |
6 | eval `dbus-launch --sh-syntax`
7 |
8 | ./run_one_test.sh $exe configs/empty.conf testcases/basic.py
9 | ./run_one_test.sh $exe configs/issue357.conf testcases/issue357.py
10 | ./run_one_test.sh $exe configs/issue239.conf testcases/issue239.py
11 | ./run_one_test.sh $exe configs/issue239_2.conf testcases/issue239_2.py
12 | ./run_one_test.sh $exe configs/issue239_3.conf testcases/issue239_3.py
13 | ./run_one_test.sh $exe configs/issue239_3.conf testcases/issue239_3_norefresh.py
14 | ./run_one_test.sh $exe configs/issue314.conf testcases/issue314.py
15 | ./run_one_test.sh $exe configs/issue314.conf testcases/issue314_2.py
16 | ./run_one_test.sh $exe configs/issue314.conf testcases/issue314_3.py
17 | ./run_one_test.sh $exe /dev/null testcases/issue299.py
18 | ./run_one_test.sh $exe configs/issue465.conf testcases/issue465.py
19 | ./run_one_test.sh $exe configs/clear_shadow_unredirected.conf testcases/clear_shadow_unredirected.py
20 | ./run_one_test.sh $exe configs/clear_shadow_unredirected.conf testcases/redirect_when_unmapped_window_has_shadow.py
21 | ./run_one_test.sh $exe configs/issue394.conf testcases/issue394.py
22 | ./run_one_test.sh $exe configs/issue239.conf testcases/issue525.py
23 |
--------------------------------------------------------------------------------
/tests/testcases/basic.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xcffib.xproto as xproto
4 | import xcffib
5 |
6 | conn = xcffib.connect()
7 | setup = conn.get_setup()
8 | root = setup.roots[0].root
9 | visual = setup.roots[0].root_visual
10 | depth = setup.roots[0].root_depth
11 |
12 | wid = conn.generate_id()
13 | conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check()
14 | conn.core.MapWindowChecked(wid).check()
15 | conn.core.UnmapWindowChecked(wid).check()
16 | conn.core.DestroyWindowChecked(wid).check()
17 |
18 |
19 |
--------------------------------------------------------------------------------
/tests/testcases/clear_shadow_unredirected.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xcffib.xproto as xproto
4 | import xcffib
5 | import time
6 | from common import set_window_name
7 |
8 | conn = xcffib.connect()
9 | setup = conn.get_setup()
10 | root = setup.roots[0].root
11 | visual = setup.roots[0].root_visual
12 | depth = setup.roots[0].root_depth
13 |
14 | name = "_NET_WM_STATE"
15 | name_atom = conn.core.InternAtom(False, len(name), name).reply().atom
16 | atom = "ATOM"
17 | atom_atom = conn.core.InternAtom(False, len(atom), atom).reply().atom
18 | fs = "_NET_WM_STATE_FULLSCREEN"
19 | fs_atom = conn.core.InternAtom(False, len(fs), fs).reply().atom
20 |
21 | # making sure disabling shadow while screen is unredirected doesn't cause assertion failure
22 | wid = conn.generate_id()
23 | print("Window id is ", hex(wid))
24 |
25 | # Create a window
26 | conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check()
27 |
28 | # Set Window name so it does get a shadow
29 | set_window_name(conn, wid, "YesShadow")
30 |
31 | # Map the window
32 | print("mapping")
33 | conn.core.MapWindowChecked(wid).check()
34 |
35 | time.sleep(0.5)
36 |
37 | # Set fullscreen property, causing screen to be unredirected
38 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, name_atom, atom_atom, 32, 1, [fs_atom]).check()
39 |
40 | time.sleep(0.5)
41 |
42 | # Set the Window name so it loses its shadow
43 | print("set new name")
44 | set_window_name(conn, wid, "NoShadow")
45 |
46 | # Unmap the window
47 | conn.core.UnmapWindowChecked(wid).check()
48 |
49 | time.sleep(0.5)
50 |
51 | # Destroy the window
52 | conn.core.DestroyWindowChecked(wid).check()
53 |
--------------------------------------------------------------------------------
/tests/testcases/common.py:
--------------------------------------------------------------------------------
1 | import xcffib.xproto as xproto
2 | import xcffib.randr as randr
3 | import xcffib
4 | import time
5 | import random
6 | import string
7 |
8 | def to_atom(conn, string):
9 | return conn.core.InternAtom(False, len(string), string).reply().atom
10 |
11 | def set_window_name(conn, wid, name):
12 | prop_name = to_atom(conn, "_NET_WM_NAME")
13 | str_type = to_atom(conn, "UTF8_STRING")
14 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, prop_name, str_type, 8, len(name), name).check()
15 | prop_name = to_atom(conn, "WM_NAME")
16 | str_type = to_atom(conn, "STRING")
17 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, prop_name, str_type, 8, len(name), name).check()
18 |
19 | def set_window_state(conn, wid, state):
20 | prop_name = to_atom(conn, "WM_STATE")
21 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, prop_name, prop_name, 32, 2, [state, 0]).check()
22 |
23 | def set_window_class(conn, wid, name):
24 | if not isinstance(name, bytearray):
25 | name = name.encode()
26 | name = name+b"\0"+name+b"\0"
27 | prop_name = to_atom(conn, "WM_CLASS")
28 | str_type = to_atom(conn, "STRING")
29 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, prop_name, str_type, 8, len(name), name).check()
30 |
31 | def set_window_size_async(conn, wid, width, height):
32 | value_mask = xproto.ConfigWindow.Width | xproto.ConfigWindow.Height
33 | value_list = [width, height]
34 | return conn.core.ConfigureWindowChecked(wid, value_mask, value_list)
35 |
36 | def find_picom_window(conn):
37 | prop_name = to_atom(conn, "WM_NAME")
38 | setup = conn.get_setup()
39 | root = setup.roots[0].root
40 | windows = conn.core.QueryTree(root).reply()
41 |
42 | ext = xproto.xprotoExtension(conn)
43 | for w in windows.children:
44 | name = ext.GetProperty(False, w, prop_name, xproto.GetPropertyType.Any, 0, (2 ** 32) - 1).reply()
45 | if name.value.buf() == b"picom":
46 | return w
47 |
48 | def prepare_root_configure(conn):
49 | setup = conn.get_setup()
50 | root = setup.roots[0].root
51 | # Xorg sends root ConfigureNotify when we add a new mode to an output
52 | rr = conn(randr.key)
53 | name = ''.join([random.choice(string.ascii_letters + string.digits) for n in range(0, 32)])
54 | mode_info = randr.ModeInfo.synthetic(id = 0, width = 1000, height = 1000, dot_clock = 0,
55 | hsync_start = 0, hsync_end = 0, htotal = 0, hskew = 0, vsync_start = 0, vsync_end = 0,
56 | vtotal = 0, name_len = len(name), mode_flags = 0)
57 |
58 | reply = rr.CreateMode(root, mode_info, len(name), name).reply()
59 | mode = reply.mode
60 | reply = rr.GetScreenResourcesCurrent(root).reply()
61 | # our xvfb is setup to only have 1 output
62 | output = reply.outputs[0]
63 | rr.AddOutputModeChecked(output, mode).check()
64 | return reply, mode, output
65 |
66 | def trigger_root_configure(conn, reply, mode, output):
67 | rr = conn(randr.key)
68 | return rr.SetCrtcConfig(reply.crtcs[0], reply.timestamp, reply.config_timestamp, 0, 0, mode, randr.Rotation.Rotate_0, 1, [output])
69 |
70 | def find_32bit_visual(conn):
71 | setup = conn.get_setup()
72 | render = conn(xcffib.render.key)
73 | r = render.QueryPictFormats().reply()
74 | pictfmt_ids = set()
75 | for pictform in r.formats:
76 | if (pictform.depth == 32 and
77 | pictform.type == xcffib.render.PictType.Direct and
78 | pictform.direct.alpha_mask != 0):
79 | pictfmt_ids.add(pictform.id)
80 | print(pictfmt_ids)
81 | for screen in r.screens:
82 | for depth in screen.depths:
83 | for pv in depth.visuals:
84 | if pv.format in pictfmt_ids:
85 | return pv.visual
86 |
--------------------------------------------------------------------------------
/tests/testcases/issue239.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xcffib.xproto as xproto
4 | import xcffib
5 | import time
6 | from common import set_window_name
7 |
8 | conn = xcffib.connect()
9 | setup = conn.get_setup()
10 | root = setup.roots[0].root
11 | visual = setup.roots[0].root_visual
12 | depth = setup.roots[0].root_depth
13 |
14 | # issue 239 is caused by a window gaining a shadow during its fade-out transition
15 | wid = conn.generate_id()
16 | print("Window id is ", hex(wid))
17 |
18 | # Create a window
19 | conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check()
20 |
21 | # Set Window name so it doesn't get a shadow
22 | set_window_name(conn, wid, "NoShadow")
23 |
24 | # Map the window
25 | print("mapping")
26 | conn.core.MapWindowChecked(wid).check()
27 |
28 | time.sleep(0.5)
29 |
30 | # Set the Window name so it gets a shadow
31 | print("set new name")
32 | set_window_name(conn, wid, "YesShadow")
33 |
34 | # Unmap the window
35 | conn.core.UnmapWindowChecked(wid).check()
36 |
37 | time.sleep(0.5)
38 |
39 | # Destroy the window
40 | conn.core.DestroyWindowChecked(wid).check()
41 |
--------------------------------------------------------------------------------
/tests/testcases/issue239_2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xcffib.xproto as xproto
4 | import xcffib
5 | import time
6 |
7 | conn = xcffib.connect()
8 | setup = conn.get_setup()
9 | root = setup.roots[0].root
10 | visual = setup.roots[0].root_visual
11 | depth = setup.roots[0].root_depth
12 |
13 | # issue 239 is caused by a window gaining a shadow during its fade-out transition
14 | wid = conn.generate_id()
15 | print("Window ids are ", hex(wid))
16 |
17 | # Create a window
18 | mask = xproto.CW.BackPixel
19 | value = [ setup.roots[0].white_pixel ]
20 | conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, mask, value).check()
21 |
22 | name = "_NET_WM_STATE"
23 | name_atom = conn.core.InternAtom(False, len(name), name).reply().atom
24 | atom = "ATOM"
25 | atom_atom = conn.core.InternAtom(False, len(atom), atom).reply().atom
26 | fs = "_NET_WM_STATE_FULLSCREEN"
27 | fs_atom = conn.core.InternAtom(False, len(fs), fs).reply().atom
28 |
29 |
30 | # Map the window, causing screen to be redirected
31 | conn.core.MapWindowChecked(wid).check()
32 |
33 | time.sleep(0.5)
34 |
35 | # Set fullscreen property, causing screen to be unredirected
36 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, name_atom, atom_atom, 32, 1, [fs_atom]).check()
37 |
38 | time.sleep(0.5)
39 |
40 | # Clear fullscreen property, causing screen to be redirected
41 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, name_atom, atom_atom, 32, 0, []).check()
42 |
43 | # Do a round trip to X server so the compositor has a chance to start the rerun of _draw_callback
44 | conn.core.GetInputFocus().reply()
45 |
46 | # Unmap the window, triggers the bug
47 | conn.core.UnmapWindowChecked(wid).check()
48 |
49 | time.sleep(0.5)
50 |
51 | # Destroy the window
52 | conn.core.DestroyWindowChecked(wid).check()
53 |
--------------------------------------------------------------------------------
/tests/testcases/issue239_3.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xcffib.xproto as xproto
4 | import xcffib
5 | import time
6 | from common import set_window_name
7 |
8 | conn = xcffib.connect()
9 | setup = conn.get_setup()
10 | root = setup.roots[0].root
11 | visual = setup.roots[0].root_visual
12 | depth = setup.roots[0].root_depth
13 |
14 | # issue 239 is caused by a window gaining a shadow during its fade-out transition
15 | wid = conn.generate_id()
16 | print("Window id is ", hex(wid))
17 |
18 | # Create a window
19 | conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check()
20 |
21 | # Set Window name so it gets a shadow
22 | set_window_name(conn, wid, "YesShadow")
23 |
24 | # Map the window
25 | print("mapping")
26 | conn.core.MapWindowChecked(wid).check()
27 |
28 | time.sleep(0.5)
29 |
30 | print("set new name")
31 | set_window_name(conn, wid, "NoShadow")
32 |
33 | time.sleep(0.5)
34 |
35 | # Set the Window name so it gets a shadow
36 | print("set new name")
37 | set_window_name(conn, wid, "YesShadow")
38 |
39 | time.sleep(0.5)
40 |
41 | # Unmap the window
42 | conn.core.UnmapWindowChecked(wid).check()
43 |
44 | time.sleep(0.5)
45 |
46 | # Destroy the window
47 | conn.core.DestroyWindowChecked(wid).check()
48 |
--------------------------------------------------------------------------------
/tests/testcases/issue239_3_norefresh.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xcffib.xproto as xproto
4 | import xcffib
5 | import time
6 | from common import set_window_name
7 |
8 | conn = xcffib.connect()
9 | setup = conn.get_setup()
10 | root = setup.roots[0].root
11 | visual = setup.roots[0].root_visual
12 | depth = setup.roots[0].root_depth
13 |
14 | # issue 239 is caused by a window gaining a shadow during its fade-out transition
15 | wid = conn.generate_id()
16 | print("Window id is ", hex(wid))
17 |
18 | # Create a window
19 | conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check()
20 |
21 | # Set Window name so it gets a shadow
22 | set_window_name(conn, wid, "YesShadow")
23 |
24 | # Map the window
25 | print("mapping")
26 | conn.core.MapWindowChecked(wid).check()
27 |
28 | time.sleep(0.5)
29 |
30 | print("set new name")
31 | set_window_name(conn, wid, "NoShadow")
32 |
33 | # Set the Window name so it gets a shadow
34 | print("set new name")
35 | set_window_name(conn, wid, "YesShadow")
36 |
37 | time.sleep(0.5)
38 |
39 | # Unmap the window
40 | conn.core.UnmapWindowChecked(wid).check()
41 |
42 | time.sleep(0.5)
43 |
44 | # Destroy the window
45 | conn.core.DestroyWindowChecked(wid).check()
46 |
--------------------------------------------------------------------------------
/tests/testcases/issue299.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xcffib.xproto as xproto
4 | import xcffib
5 | import time
6 | import os
7 | import subprocess
8 | import asyncio
9 | from dbus_next.aio import MessageBus
10 | from dbus_next.message import Message, MessageType
11 | from common import *
12 |
13 | display = os.environ["DISPLAY"].replace(":", "_")
14 | conn = xcffib.connect()
15 | setup = conn.get_setup()
16 | root = setup.roots[0].root
17 | visual = setup.roots[0].root_visual
18 | depth = setup.roots[0].root_depth
19 | x = xproto.xprotoExtension(conn)
20 | visual32 = find_32bit_visual(conn)
21 |
22 | async def get_client_win_async(wid):
23 | message = await bus.call(Message(destination='com.github.chjj.compton.'+display,
24 | path='/com/github/chjj/compton',
25 | interface='com.github.chjj.compton',
26 | member='win_get',
27 | signature='us',
28 | body=[wid, 'client_win']))
29 | return message.body[0]
30 |
31 | def get_client_win(wid):
32 | return loop.run_until_complete(get_client_win_async(wid))
33 |
34 | def wait():
35 | time.sleep(0.5)
36 |
37 | def create_client_window(name):
38 | client_win = conn.generate_id()
39 | print("Window : ", hex(client_win))
40 | conn.core.CreateWindowChecked(depth, client_win, root, 0, 0, 100, 100, 0,
41 | xproto.WindowClass.InputOutput, visual, 0, []).check()
42 | set_window_name(conn, client_win, "Test window "+name)
43 | set_window_class(conn, client_win, "Test windows")
44 | set_window_state(conn, client_win, 1)
45 | conn.core.MapWindowChecked(client_win).check()
46 | return client_win
47 |
48 | loop = asyncio.get_event_loop()
49 | bus = loop.run_until_complete(MessageBus().connect())
50 |
51 | cmid = conn.generate_id()
52 | colormap = conn.core.CreateColormapChecked(xproto.ColormapAlloc._None, cmid, root, visual32).check()
53 |
54 | # Create window
55 | client_wins = []
56 | for i in range(0,2):
57 | client_wins.append(create_client_window(str(i)))
58 |
59 | # Create frame window
60 | frame_win = conn.generate_id()
61 | print("Window : ", hex(frame_win))
62 | conn.core.CreateWindowChecked(depth, frame_win, root, 0, 0, 200, 200, 0,
63 | xproto.WindowClass.InputOutput, visual, 0, []).check()
64 | set_window_name(conn, frame_win, "Frame")
65 | conn.core.MapWindowChecked(frame_win).check()
66 |
67 | # Scenario 1.1
68 | # 1. reparent placeholder to frame
69 | conn.core.ReparentWindowChecked(client_wins[0], frame_win, 0, 0).check()
70 | wait()
71 | # 2. reparent real client to frame
72 | conn.core.ReparentWindowChecked(client_wins[1], frame_win, 0, 0).check()
73 | wait()
74 | # 3. detach the placeholder
75 | conn.core.ReparentWindowChecked(client_wins[0], root, 0, 0).check()
76 | wait()
77 | assert get_client_win(frame_win) == client_wins[1]
78 |
79 | # Scenario 1.2
80 | # 1. reparent placeholder to frame
81 | conn.core.ReparentWindowChecked(client_wins[0], frame_win, 0, 0).check()
82 | wait()
83 | # 2. reparent real client to frame
84 | conn.core.ReparentWindowChecked(client_wins[1], frame_win, 0, 0).check()
85 | wait()
86 | # 3. destroy the placeholder
87 | conn.core.DestroyWindowChecked(client_wins[0]).check()
88 | wait()
89 | assert get_client_win(frame_win) == client_wins[1]
90 |
91 | client_wins[0] = create_client_window("0")
92 |
93 | # Scenario 2
94 | # 1. frame is unmapped
95 | conn.core.UnmapWindowChecked(frame_win).check()
96 | wait()
97 | # 2. reparent placeholder to frame
98 | conn.core.ReparentWindowChecked(client_wins[0], frame_win, 0, 0).check()
99 | wait()
100 | # 3. destroy placeholder, map frame and reparent real client to frame
101 | conn.core.DestroyWindowChecked(client_wins[0]).check()
102 | conn.core.MapWindowChecked(frame_win).check()
103 | conn.core.ReparentWindowChecked(client_wins[1], frame_win, 0, 0).check()
104 | wait()
105 | assert get_client_win(frame_win) == client_wins[1]
106 |
107 | client_wins[0] = create_client_window("0")
108 |
109 | # Destroy the windows
110 | for wid in client_wins:
111 | conn.core.DestroyWindowChecked(wid).check()
112 | conn.core.DestroyWindowChecked(frame_win).check()
113 |
--------------------------------------------------------------------------------
/tests/testcases/issue314.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xcffib.xproto as xproto
4 | import xcffib
5 | import time
6 | from common import set_window_name, trigger_root_configure
7 |
8 | conn = xcffib.connect()
9 | setup = conn.get_setup()
10 | root = setup.roots[0].root
11 | visual = setup.roots[0].root_visual
12 | depth = setup.roots[0].root_depth
13 | x = xproto.xprotoExtension(conn)
14 |
15 | # issue 314 is caused by changing a windows target opacity during its fade-in/-out transition
16 | wid1 = conn.generate_id()
17 | print("Window 1: ", hex(wid1))
18 | wid2 = conn.generate_id()
19 | print("Window 2: ", hex(wid2))
20 |
21 | # Create windows
22 | conn.core.CreateWindowChecked(depth, wid1, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check()
23 | conn.core.CreateWindowChecked(depth, wid2, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check()
24 |
25 | # Set Window names
26 | set_window_name(conn, wid1, "Test window 1")
27 | set_window_name(conn, wid2, "Test window 2")
28 |
29 | # Check updating opacity while UNMAPPING/DESTROYING windows
30 | print("Mapping 1")
31 | conn.core.MapWindowChecked(wid1).check()
32 | print("Mapping 2")
33 | conn.core.MapWindowChecked(wid2).check()
34 | time.sleep(0.5)
35 |
36 | x.SetInputFocusChecked(0, wid1, xproto.Time.CurrentTime).check()
37 | time.sleep(0.5)
38 |
39 | # Destroy the windows
40 | print("Destroy 1 while fading out")
41 | conn.core.DestroyWindowChecked(wid1).check()
42 | x.SetInputFocusChecked(0, wid2, xproto.Time.CurrentTime).check()
43 | time.sleep(1)
44 | conn.core.DestroyWindowChecked(wid2).check()
45 |
--------------------------------------------------------------------------------
/tests/testcases/issue314_2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xcffib.xproto as xproto
4 | import xcffib
5 | import time
6 | from common import set_window_name
7 |
8 | conn = xcffib.connect()
9 | setup = conn.get_setup()
10 | root = setup.roots[0].root
11 | visual = setup.roots[0].root_visual
12 | depth = setup.roots[0].root_depth
13 | x = xproto.xprotoExtension(conn)
14 |
15 | opacity_80 = [int(0xffffffff * 0.8), ]
16 | opacity_single = [int(0xffffffff * 0.002), ]
17 |
18 | # issue 314 is caused by changing a windows target opacity during its fade-in/-out transition
19 | wid1 = conn.generate_id()
20 | print("Window 1: ", hex(wid1))
21 |
22 | atom = "_NET_WM_WINDOW_OPACITY"
23 | opacity_atom = conn.core.InternAtom(False, len(atom), atom).reply().atom
24 |
25 | # Create windows
26 | conn.core.CreateWindowChecked(depth, wid1, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check()
27 |
28 | # Set Window names
29 | set_window_name(conn, wid1, "Test window 1")
30 |
31 | # Check updating opacity while MAPPING windows
32 | print("Mapping window")
33 | conn.core.MapWindowChecked(wid1).check()
34 | time.sleep(0.5)
35 |
36 | print("Update opacity while fading in")
37 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_80).check()
38 | time.sleep(0.2)
39 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_single).check()
40 | time.sleep(1)
41 |
42 | conn.core.DeletePropertyChecked(wid1, opacity_atom).check()
43 | time.sleep(0.5)
44 |
45 | # Destroy the windows
46 | conn.core.DestroyWindowChecked(wid1).check()
47 |
--------------------------------------------------------------------------------
/tests/testcases/issue314_3.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xcffib.xproto as xproto
4 | import xcffib
5 | import time
6 | from common import set_window_name
7 |
8 | conn = xcffib.connect()
9 | setup = conn.get_setup()
10 | root = setup.roots[0].root
11 | visual = setup.roots[0].root_visual
12 | depth = setup.roots[0].root_depth
13 | x = xproto.xprotoExtension(conn)
14 |
15 | opacity_100 = [0xffffffff, ]
16 | opacity_80 = [int(0xffffffff * 0.8), ]
17 | opacity_single = [int(0xffffffff * 0.002), ]
18 | opacity_0 = [0, ]
19 |
20 | # issue 314 is caused by changing a windows target opacity during its fade-in/-out transition
21 | wid1 = conn.generate_id()
22 | print("Window 1: ", hex(wid1))
23 |
24 | atom = "_NET_WM_WINDOW_OPACITY"
25 | opacity_atom = conn.core.InternAtom(False, len(atom), atom).reply().atom
26 |
27 | # Create windows
28 | conn.core.CreateWindowChecked(depth, wid1, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check()
29 |
30 | # Set Window names
31 | set_window_name(conn, wid1, "Test window 1")
32 |
33 | # Check updating opacity while FADING windows
34 | print("Mapping window")
35 | conn.core.MapWindowChecked(wid1).check()
36 | time.sleep(1.2)
37 |
38 | print("Update opacity while fading out")
39 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_single).check()
40 | time.sleep(0.2)
41 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_0).check()
42 | time.sleep(1)
43 |
44 | print("Change from fading in to fading out")
45 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_80).check()
46 | time.sleep(0.5)
47 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_0).check()
48 | time.sleep(1)
49 |
50 | print("Update opacity while fading in")
51 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_80).check()
52 | time.sleep(0.2)
53 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_100).check()
54 | time.sleep(1)
55 |
56 | print("Change from fading out to fading in")
57 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_0).check()
58 | time.sleep(0.5)
59 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_80).check()
60 | time.sleep(1)
61 |
62 | # Destroy the windows
63 | conn.core.DestroyWindowChecked(wid1).check()
64 |
--------------------------------------------------------------------------------
/tests/testcases/issue357.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xcffib.xproto as xproto
4 | import xcffib
5 | import time
6 | from common import set_window_name, trigger_root_configure, prepare_root_configure
7 |
8 | conn = xcffib.connect()
9 | setup = conn.get_setup()
10 | root = setup.roots[0].root
11 | visual = setup.roots[0].root_visual
12 | depth = setup.roots[0].root_depth
13 |
14 | # issue 357 is triggered when a window is destroyed right after configure_root
15 | wid = conn.generate_id()
16 | print("Window 1: ", hex(wid))
17 |
18 | # Create a window
19 | conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check()
20 |
21 | # Set Window name
22 | set_window_name(conn, wid, "Test window 1")
23 |
24 | print("mapping 1")
25 | conn.core.MapWindowChecked(wid).check()
26 | time.sleep(0.5)
27 |
28 | reply, mode, output = prepare_root_configure(conn)
29 | trigger_root_configure(conn, reply, mode, output).reply()
30 |
31 | # Destroy the windows
32 | conn.core.DestroyWindowChecked(wid).check()
33 |
34 | time.sleep(1)
35 |
--------------------------------------------------------------------------------
/tests/testcases/issue394.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xcffib.xproto as xproto
4 | import xcffib
5 | import time
6 | from common import set_window_name, set_window_size_async
7 |
8 | conn = xcffib.connect()
9 | setup = conn.get_setup()
10 | root = setup.roots[0].root
11 | visual = setup.roots[0].root_visual
12 | depth = setup.roots[0].root_depth
13 |
14 | # issue 394 is caused by a window getting a size update just before destroying leading to a shadow update on destroyed window.
15 | wid = conn.generate_id()
16 | print("Window id is ", hex(wid))
17 |
18 | # Create a window
19 | conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check()
20 |
21 | # Set Window name so it doesn't get a shadow
22 | set_window_name(conn, wid, "Test Window")
23 |
24 | # Map the window
25 | print("mapping")
26 | conn.core.MapWindowChecked(wid).check()
27 |
28 | time.sleep(0.5)
29 |
30 | # Resize the window and destroy
31 | print("resize and destroy")
32 | set_window_size_async(conn, wid, 150, 150)
33 | conn.core.DestroyWindowChecked(wid).check()
34 |
35 | time.sleep(0.5)
36 |
--------------------------------------------------------------------------------
/tests/testcases/issue465.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xcffib.xproto as xproto
4 | import xcffib
5 | import time
6 | from common import set_window_name
7 |
8 | conn = xcffib.connect()
9 | setup = conn.get_setup()
10 | root = setup.roots[0].root
11 | visual = setup.roots[0].root_visual
12 | depth = setup.roots[0].root_depth
13 | x = xproto.xprotoExtension(conn)
14 |
15 | # issue 465 is triggered when focusing a new window with a shadow-exclude rule for unfocused windows.
16 | wid1 = conn.generate_id()
17 | print("Window 1: ", hex(wid1))
18 | wid2 = conn.generate_id()
19 | print("Window 2: ", hex(wid2))
20 |
21 | # Create a window
22 | conn.core.CreateWindowChecked(depth, wid1, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check()
23 | conn.core.CreateWindowChecked(depth, wid2, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check()
24 |
25 | # Set Window name
26 | set_window_name(conn, wid1, "Test window 1")
27 | set_window_name(conn, wid2, "Test window 2")
28 |
29 | print("mapping 1")
30 | conn.core.MapWindowChecked(wid1).check()
31 | print("mapping 2")
32 | conn.core.MapWindowChecked(wid2).check()
33 | time.sleep(0.5)
34 |
35 | x.SetInputFocusChecked(0, wid1, xproto.Time.CurrentTime).check()
36 | time.sleep(0.5)
37 |
38 | # Destroy the windows
39 | conn.core.DestroyWindowChecked(wid1).check()
40 | time.sleep(1)
41 |
--------------------------------------------------------------------------------
/tests/testcases/issue525.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xcffib.xproto as xproto
4 | import xcffib
5 | import time
6 | from common import set_window_name
7 |
8 | conn = xcffib.connect()
9 | setup = conn.get_setup()
10 | root = setup.roots[0].root
11 | visual = setup.roots[0].root_visual
12 | depth = setup.roots[0].root_depth
13 |
14 | # issue 525 happens when a window is unmapped with pixmap stale flag set
15 | wid = conn.generate_id()
16 | print("Window id is ", hex(wid))
17 |
18 | # Create a window
19 | conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check()
20 |
21 | # Map the window
22 | print("mapping")
23 | conn.core.MapWindowChecked(wid).check()
24 |
25 | time.sleep(0.5)
26 |
27 | # change window size, invalidate the pixmap
28 | conn.core.ConfigureWindow(wid, xproto.ConfigWindow.X | xproto.ConfigWindow.Width, [100, 200])
29 |
30 | # unmap the window immediately after
31 | conn.core.UnmapWindowChecked(wid).check()
32 |
33 | time.sleep(0.1)
34 |
35 | # Destroy the window
36 | conn.core.DestroyWindowChecked(wid).check()
37 |
--------------------------------------------------------------------------------
/tests/testcases/redirect_when_unmapped_window_has_shadow.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xcffib.xproto as xproto
4 | import xcffib
5 | import time
6 | from common import set_window_name
7 |
8 | conn = xcffib.connect()
9 | setup = conn.get_setup()
10 | root = setup.roots[0].root
11 | visual = setup.roots[0].root_visual
12 | depth = setup.roots[0].root_depth
13 |
14 | name = "_NET_WM_STATE"
15 | name_atom = conn.core.InternAtom(False, len(name), name).reply().atom
16 | atom = "ATOM"
17 | atom_atom = conn.core.InternAtom(False, len(atom), atom).reply().atom
18 | fs = "_NET_WM_STATE_FULLSCREEN"
19 | fs_atom = conn.core.InternAtom(False, len(fs), fs).reply().atom
20 |
21 | wid1 = conn.generate_id()
22 | print("Window 1 id is ", hex(wid1))
23 |
24 | # Create a window
25 | conn.core.CreateWindowChecked(depth, wid1, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check()
26 |
27 | # Map the window
28 | print("mapping 1")
29 | conn.core.MapWindowChecked(wid1).check()
30 |
31 | time.sleep(0.5)
32 |
33 | print("unmapping 1")
34 | # Unmap the window
35 | conn.core.UnmapWindowChecked(wid1).check()
36 |
37 | time.sleep(0.5)
38 |
39 | # create and map a second window
40 | wid2 = conn.generate_id()
41 | print("Window 2 id is ", hex(wid2))
42 | conn.core.CreateWindowChecked(depth, wid2, root, 200, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check()
43 | print("mapping 2")
44 | conn.core.MapWindowChecked(wid2).check()
45 | time.sleep(0.5)
46 |
47 | # Set fullscreen property on the second window, causing screen to be unredirected
48 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid2, name_atom, atom_atom, 32, 1, [fs_atom]).check()
49 |
50 | time.sleep(0.5)
51 |
52 | # Unset fullscreen property on the second window, causing screen to be redirected
53 | conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid2, name_atom, atom_atom, 32, 0, []).check()
54 |
55 | time.sleep(0.5)
56 |
57 | # map the first window again
58 | print("mapping 1")
59 | conn.core.MapWindowChecked(wid1).check()
60 |
61 | time.sleep(0.5)
62 |
63 | # Destroy the windows
64 | conn.core.DestroyWindowChecked(wid1).check()
65 | conn.core.DestroyWindowChecked(wid2).check()
66 |
--------------------------------------------------------------------------------