Compare commits
302 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df4c6a3d9b | ||
|
|
3b9e2c5812 | ||
|
|
b99c7db73e | ||
|
|
7c15a1438a | ||
|
|
2b52edd9e3 | ||
|
|
9175489f65 | ||
|
|
45ffdf9849 | ||
|
|
96de4f07ca | ||
|
|
a93bbc30e5 | ||
|
|
6315faed20 | ||
|
|
580aef939f | ||
|
|
071b77c49f | ||
|
|
0dcca2228e | ||
|
|
bb097730c7 | ||
|
|
8d0284da1b | ||
|
|
9b6b6855d7 | ||
|
|
2d98518b7d | ||
|
|
7f3f8b37b5 | ||
|
|
42782689e8 | ||
|
|
216bfefd9f | ||
|
|
bef1240b41 | ||
|
|
7e7c2b0cef | ||
|
|
f509008cb9 | ||
|
|
8c96fbebc4 | ||
|
|
05b1fbff9e | ||
|
|
613d179f2d | ||
|
|
84b9ff3148 | ||
|
|
7da8b7afeb | ||
|
|
5b81ea2c58 | ||
|
|
2238cf1e54 | ||
|
|
85bb56e8a6 | ||
|
|
241d7f1d03 | ||
|
|
e8d42885fa | ||
|
|
e948b74363 | ||
|
|
64f6c4885a | ||
|
|
23b0c5a1d5 | ||
|
|
c7591982b6 | ||
|
|
1aa90f6466 | ||
|
|
b0dfcf4a32 | ||
|
|
28cb220b5b | ||
|
|
c6db632d9d | ||
|
|
f28905b62d | ||
|
|
53dd8a4e66 | ||
|
|
f179119d84 | ||
|
|
466fb4c9e0 | ||
|
|
4f792243c1 | ||
|
|
9392829d84 | ||
|
|
a4ec70982c | ||
|
|
0ab3e0740e | ||
|
|
7ada6db4a3 | ||
|
|
75d0b7ba1e | ||
|
|
a5826b6fb0 | ||
|
|
bdc0943399 | ||
|
|
baeafb3a3b | ||
|
|
037be5cca2 | ||
|
|
0e1628e031 | ||
|
|
642a43acbb | ||
|
|
755996a42c | ||
|
|
eb723eee29 | ||
|
|
fcd51e7373 | ||
|
|
dff77aae27 | ||
|
|
709f0168d9 | ||
|
|
726b8d0e28 | ||
|
|
023103c620 | ||
|
|
39534b98f0 | ||
|
|
0f22b70705 | ||
|
|
f265e049a8 | ||
|
|
a39cd94e1f | ||
|
|
7fad0d51d7 | ||
|
|
9204426caf | ||
|
|
0d3d18cd88 | ||
|
|
c738400058 | ||
|
|
eb39426b08 | ||
|
|
77c4ebaafc | ||
|
|
e8ca1912cd | ||
|
|
0ed8d0cadf | ||
|
|
bbc657e4bc | ||
|
|
839a101527 | ||
|
|
5c640ac452 | ||
|
|
4a79e7b777 | ||
|
|
a655730e49 | ||
|
|
4401666cfb | ||
|
|
90f5f4ca29 | ||
|
|
c1b4ede06e | ||
|
|
238c3cc833 | ||
|
|
0638de5c56 | ||
|
|
fdbcd15975 | ||
|
|
bacdb919c7 | ||
|
|
733113c691 | ||
|
|
14a345a817 | ||
|
|
5fba210ad6 | ||
|
|
1bfb129985 | ||
|
|
8ca66f8a00 | ||
|
|
f3bdd01dc8 | ||
|
|
71e29c4128 | ||
|
|
606844c4bb | ||
|
|
39de1e49cf | ||
|
|
fffc7ce535 | ||
|
|
fe5b416ed6 | ||
|
|
2417084628 | ||
|
|
4299336146 | ||
|
|
702e30df4a | ||
|
|
d87dd41f4c | ||
|
|
ce205d1591 | ||
|
|
4c4df9b918 | ||
|
|
56da153f1c | ||
|
|
81d137a3cc | ||
|
|
6e3c86226b | ||
|
|
197b4bd396 | ||
|
|
a3f50e8a3b | ||
|
|
4c34944d76 | ||
|
|
a73ca2dc8d | ||
|
|
1b8d321c45 | ||
|
|
30e37dbf09 | ||
|
|
dc2d7b2876 | ||
|
|
5e119123a7 | ||
|
|
6d4eaec811 | ||
|
|
eb3a58a6b0 | ||
|
|
3390494bfe | ||
|
|
148e61a0b2 | ||
|
|
d8f303761b | ||
|
|
217284642a | ||
|
|
496452cfce | ||
|
|
cacb45fbcd | ||
|
|
7336e4142b | ||
|
|
b368072e12 | ||
|
|
359d004b99 | ||
|
|
6e0bad0034 | ||
|
|
a28e221b83 | ||
|
|
b582d2989e | ||
|
|
db9b808bfb | ||
|
|
db6ed75b60 | ||
|
|
dd83f550e1 | ||
|
|
c42e6cef0a | ||
|
|
25d16b0352 | ||
|
|
3838b45e2c | ||
|
|
4b57e3aa4c | ||
|
|
1430d28116 | ||
|
|
147561a313 | ||
|
|
23eb3d5f52 | ||
|
|
2e1c4e51b3 | ||
|
|
827380e1e3 | ||
|
|
8d98b7d639 | ||
|
|
e76cf43f02 | ||
|
|
c178be2874 | ||
|
|
7da885e6c0 | ||
|
|
70ea36bedd | ||
|
|
fd9c52d0ee | ||
|
|
e60cb65672 | ||
|
|
c7ec4c2c16 | ||
|
|
aed2a205ed | ||
|
|
7366553be2 | ||
|
|
268e1d890a | ||
|
|
e92745671b | ||
|
|
1b97f18e5f | ||
|
|
47bb825b2c | ||
|
|
e90757a6ec | ||
|
|
40d5f42f00 | ||
|
|
a26e4df85d | ||
|
|
8b7c3ffb8b | ||
|
|
5a8c61daed | ||
|
|
b1fce63a26 | ||
|
|
b73e6a0c45 | ||
|
|
a6b4e285f8 | ||
|
|
98e842e83b | ||
|
|
2241c7d87a | ||
|
|
e07b1d7a12 | ||
|
|
44800ed845 | ||
|
|
bd3134efd9 | ||
|
|
8bf82857c4 | ||
|
|
156423c010 | ||
|
|
825cc563e1 | ||
|
|
249f681857 | ||
|
|
751f30578e | ||
|
|
21c2da549a | ||
|
|
2b74392ec7 | ||
|
|
fbc803b983 | ||
|
|
a9914cda11 | ||
|
|
0cd72bf61a | ||
|
|
c6abb4e270 | ||
|
|
d9e5795818 | ||
|
|
9e6842c607 | ||
|
|
367643e98a | ||
|
|
8cc5090a6c | ||
|
|
f773e723be | ||
|
|
34024092d7 | ||
|
|
ae3c77b1a5 | ||
|
|
c8627989ad | ||
|
|
72ede90147 | ||
|
|
b528b87cc8 | ||
|
|
8c29fb046b | ||
|
|
71782da245 | ||
|
|
5d6957d3da | ||
|
|
013c03c9a5 | ||
|
|
fd15a05166 | ||
|
|
7713432927 | ||
|
|
6f868e54b9 | ||
|
|
d03cb4ea24 | ||
|
|
4a8b937c90 | ||
|
|
2c83fb821b | ||
|
|
bd47a47275 | ||
|
|
599594465c | ||
|
|
dc8def0492 | ||
|
|
8eff87f2a3 | ||
|
|
5c03f101f2 | ||
|
|
a5c4095082 | ||
|
|
961dc998dd | ||
|
|
e8477e0a73 | ||
|
|
5c00ccf4d1 | ||
|
|
0f4cd33b35 | ||
|
|
4a39423edb | ||
|
|
7a45e35ca0 | ||
|
|
3d82e76d4b | ||
|
|
a5f0ac3cbd | ||
|
|
6bd780f10f | ||
|
|
1307d9ec70 | ||
|
|
4e6dddc76e | ||
|
|
d4f72828f7 | ||
|
|
d044e3e386 | ||
|
|
6496f75359 | ||
|
|
d08b6092a3 | ||
|
|
0bb45d50d6 | ||
|
|
f8cdc81635 | ||
|
|
f6625198ba | ||
|
|
e0c14f63c6 | ||
|
|
9295f7e4c7 | ||
|
|
15aa5014a6 | ||
|
|
b85272312a | ||
|
|
4911cbc24a | ||
|
|
a5591b33a6 | ||
|
|
b1cae3d367 | ||
|
|
15667d6b6e | ||
|
|
689321419b | ||
|
|
dd85c3e175 | ||
|
|
a377d12a66 | ||
|
|
550518c5d1 | ||
|
|
cbd2d4125c | ||
|
|
fea1dc794c | ||
|
|
6b6a8da035 | ||
|
|
7fc9436a0c | ||
|
|
c02a1e2a30 | ||
|
|
8ee2162a1b | ||
|
|
73a366ffe0 | ||
|
|
043703e58f | ||
|
|
c065ad1b8d | ||
|
|
56745b64d7 | ||
|
|
07303ce2cb | ||
|
|
01bb26012c | ||
|
|
e2ecef92ba | ||
|
|
ecbc8b50ed | ||
|
|
9f9cff3b06 | ||
|
|
4d724047ef | ||
|
|
5e22434387 | ||
|
|
0deaa9a241 | ||
|
|
4d624bbee5 | ||
|
|
ac55ce7b56 | ||
|
|
9956844647 | ||
|
|
037fd4c07b | ||
|
|
6b80280af0 | ||
|
|
223872bc7f | ||
|
|
1e398b9c24 | ||
|
|
8245de27ed | ||
|
|
5826adf853 | ||
|
|
5b6f6ecbf5 | ||
|
|
6af0251f2a | ||
|
|
608423a5ce | ||
|
|
d0c121ec83 | ||
|
|
6a69cdb002 | ||
|
|
7d9692360b | ||
|
|
b6e7ea5639 | ||
|
|
8e1f3c92f5 | ||
|
|
336cb0917a | ||
|
|
fcb9dc8cfd | ||
|
|
edeb17ad46 | ||
|
|
1899ef0d49 | ||
|
|
480fd24da6 | ||
|
|
a4fae2b60a | ||
|
|
47ffaf0a38 | ||
|
|
86d3739374 | ||
|
|
2a4e32babf | ||
|
|
64a97b57b5 | ||
|
|
62fcfe5d1a | ||
|
|
3aed5599c3 | ||
|
|
dc9d1fe248 | ||
|
|
05ef18d78f | ||
|
|
379f67f5c2 | ||
|
|
6d459badbc | ||
|
|
838f1f097c | ||
|
|
1c7466c07c | ||
|
|
44cfa0b0ef | ||
|
|
ce7758a07b | ||
|
|
bb2b4801f7 | ||
|
|
36e6b73cde | ||
|
|
90f57e9b1a | ||
|
|
157ecd2077 | ||
|
|
9f14c43989 | ||
|
|
cee1287562 | ||
|
|
317275abf6 | ||
|
|
986b3c1cb3 | ||
|
|
102a0bc5e0 | ||
|
|
11840cb658 | ||
|
|
332501aef7 |
@@ -12,6 +12,7 @@ packages:
|
|||||||
- uthash
|
- uthash
|
||||||
- libconfig
|
- libconfig
|
||||||
- libglvnd
|
- libglvnd
|
||||||
|
- libepoxy
|
||||||
- dbus
|
- dbus
|
||||||
- pcre
|
- pcre
|
||||||
sources:
|
sources:
|
||||||
@@ -19,7 +20,7 @@ sources:
|
|||||||
tasks:
|
tasks:
|
||||||
- setup: |
|
- setup: |
|
||||||
cd picom
|
cd picom
|
||||||
CPPFLAGS="-I/usr/local/include" meson -Dunittest=true build
|
CPPFLAGS="-I/usr/local/include" meson setup -Dunittest=true --werror build
|
||||||
- build: |
|
- build: |
|
||||||
cd picom
|
cd picom
|
||||||
ninja -C build
|
ninja -C build
|
||||||
|
|||||||
23
.builds/openbsd.yml
Normal file
23
.builds/openbsd.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
image: openbsd/latest
|
||||||
|
packages:
|
||||||
|
- libev
|
||||||
|
- xcb
|
||||||
|
- meson
|
||||||
|
- pkgconf
|
||||||
|
- cmake
|
||||||
|
- uthash
|
||||||
|
- libconfig
|
||||||
|
- dbus
|
||||||
|
- pcre2
|
||||||
|
sources:
|
||||||
|
- https://github.com/yshui/picom
|
||||||
|
tasks:
|
||||||
|
- setup: |
|
||||||
|
cd picom
|
||||||
|
CPPFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib" meson setup -Dunittest=true --werror build
|
||||||
|
- build: |
|
||||||
|
cd picom
|
||||||
|
ninja -C build
|
||||||
|
- unittest: |
|
||||||
|
cd picom
|
||||||
|
ninja -C build test
|
||||||
@@ -29,7 +29,7 @@ commands:
|
|||||||
- ".git"
|
- ".git"
|
||||||
- run:
|
- run:
|
||||||
name: config
|
name: config
|
||||||
command: CC=<< parameters.cc >> meson << parameters.build-config >> -Dunittest=true --werror . build
|
command: CC=<< parameters.cc >> meson setup << parameters.build-config >> -Dunittest=true --werror . build
|
||||||
- run:
|
- run:
|
||||||
name: build
|
name: build
|
||||||
command: ninja -vC build
|
command: ninja -vC build
|
||||||
|
|||||||
@@ -23,5 +23,9 @@ CheckOptions:
|
|||||||
value: 255.0;1.0;
|
value: 255.0;1.0;
|
||||||
- key: readability-function-cognitive-complexity.IgnoreMacros
|
- key: readability-function-cognitive-complexity.IgnoreMacros
|
||||||
value: true
|
value: true
|
||||||
|
- key: readability-function-cognitive-complexity.Threshold
|
||||||
|
value: 50
|
||||||
- key: readability-function-cognitive-complexity.DescribeBasicIncrements
|
- key: readability-function-cognitive-complexity.DescribeBasicIncrements
|
||||||
value: true
|
value: true
|
||||||
|
- key: bugprone-signed-char-misuse.CharTypdefsToIgnore
|
||||||
|
value: int8_t
|
||||||
|
|||||||
@@ -3,3 +3,6 @@ root = true
|
|||||||
indent_style = tab
|
indent_style = tab
|
||||||
indent_size = 8
|
indent_size = 8
|
||||||
max_line_length = 90
|
max_line_length = 90
|
||||||
|
[*.nix]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|||||||
19
.github/workflows/codeql-analysis.yml
vendored
19
.github/workflows/codeql-analysis.yml
vendored
@@ -18,30 +18,21 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
# We must fetch at least the immediate parents so that if this is
|
|
||||||
# a pull request then we can checkout the head.
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
# If this run was triggered by a pull request event, then checkout
|
|
||||||
# the head of the pull request instead of the merge commit.
|
|
||||||
- run: git checkout HEAD^2
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
- run: sudo apt update && sudo apt install libxext-dev libxcb1-dev libxcb-dpms0-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
|
- run: sudo apt update && sudo apt install libconfig-dev libdbus-1-dev libegl-dev libev-dev libgl-dev libpcre2-dev libpixman-1-dev libx11-xcb-dev libxcb1-dev libxcb-composite0-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-glx0-dev libxcb-image0-dev libxcb-present-dev libxcb-randr0-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev libxcb-util-dev libxcb-xfixes0-dev libxext-dev meson ninja-build uthash-dev
|
||||||
if: ${{ matrix.language == 'cpp' }}
|
if: ${{ matrix.language == 'cpp' }}
|
||||||
|
|
||||||
# Autobuild
|
# Autobuild
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v1
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v3
|
||||||
|
|||||||
4
.github/workflows/coding-style-pr.yml
vendored
4
.github/workflows/coding-style-pr.yml
vendored
@@ -6,8 +6,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- run: git fetch --depth=1 origin ${{ github.event.pull_request.base.sha }}
|
- run: git fetch --depth=1 origin ${{ github.event.pull_request.base.sha }}
|
||||||
- uses: yshui/git-clang-format-lint@v1.13
|
- uses: yshui/git-clang-format-lint@v1.15
|
||||||
with:
|
with:
|
||||||
base: ${{ github.event.pull_request.base.sha }}
|
base: ${{ github.event.pull_request.base.sha }}
|
||||||
|
|||||||
4
.github/workflows/coding-style.yml
vendored
4
.github/workflows/coding-style.yml
vendored
@@ -6,9 +6,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- uses: yshui/git-clang-format-lint@v1.13
|
- uses: yshui/git-clang-format-lint@v1.15
|
||||||
with:
|
with:
|
||||||
base: ${{ github.event.ref }}~1
|
base: ${{ github.event.ref }}~1
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
# Build files
|
# Build files
|
||||||
.deps
|
.deps
|
||||||
|
.direnv
|
||||||
aclocal.m4
|
aclocal.m4
|
||||||
autom4te.cache
|
autom4te.cache
|
||||||
config.log
|
config.log
|
||||||
|
|||||||
0
.gitmodules
vendored
0
.gitmodules
vendored
74
CHANGELOG.md
Normal file
74
CHANGELOG.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# Unreleased
|
||||||
|
|
||||||
|
## New features
|
||||||
|
|
||||||
|
* Allow `corner-radius-rules` to override `corner-radius = 0`. Previously setting corner radius to 0 globally disables rounded corners. (#1170)
|
||||||
|
|
||||||
|
## Notable changes
|
||||||
|
|
||||||
|
* Marginally improve performance when resizing/opening/closing windows. (#1190)
|
||||||
|
|
||||||
|
# v11.2 (2024-Feb-13)
|
||||||
|
|
||||||
|
## Build changes
|
||||||
|
|
||||||
|
* `picom` now depends on `libepoxy` for OpenGL symbol management.
|
||||||
|
|
||||||
|
## Bug fixes
|
||||||
|
|
||||||
|
* Workaround a NVIDIA problem that causes high CPU usage after suspend/resume (#1172, #1168)
|
||||||
|
* Fix building on OpenBSD (#1189, #1188)
|
||||||
|
* Fix occasional freezes (#1040, #1145, #1166)
|
||||||
|
* Fix `corner-radius-rules` not applying sometimes (#1177)
|
||||||
|
* Fix window shader not having an effect when frame opacity is enabled (#1174)
|
||||||
|
* Fix binding root pixmap in case of depth mismatch (#984)
|
||||||
|
|
||||||
|
# v11.1 (2024-Jan-28)
|
||||||
|
|
||||||
|
## Bug fixes
|
||||||
|
|
||||||
|
* Fix missing fading on window close for some window managers. (#704)
|
||||||
|
|
||||||
|
# v11 (2024-Jan-20)
|
||||||
|
|
||||||
|
## Build changes
|
||||||
|
|
||||||
|
* Due to some caveats discovered related to setting the `CAP_SYS_NICE` capability, it is now recommended to **NOT** set this capability for picom.
|
||||||
|
|
||||||
|
## Deprecations
|
||||||
|
|
||||||
|
* Uses of `--sw-opti`, and `--respect-prop-shadow` are now hard errors.
|
||||||
|
* `-F` has been removed completely. It was deprecated before the picom fork.
|
||||||
|
|
||||||
|
# v11-rc1 (2024-Jan-14)
|
||||||
|
|
||||||
|
## Notable features
|
||||||
|
|
||||||
|
* picom now uses dithering to prevent banding. Banding is most notable when using a strong background blur. (#952)
|
||||||
|
* Frame pacing. picom uses present feedback information to schedule new frames when it makes sense to do so. This improves latency, and replaces the `glFlush` and `GL_MaxFramesAllowed=1` hacks we used to do for NVIDIA. (#968 #1156)
|
||||||
|
* Some missing features have been implemented for the EGL backend (#1004 #1007)
|
||||||
|
|
||||||
|
## Bug fixes
|
||||||
|
|
||||||
|
* Many memory/resource leak fixes thanks to @absolutelynothelix . (#977 #978 #979 #980 #982 #985 #992 #1009 #1022)
|
||||||
|
* Fix tiling of wallpaper. (#1002)
|
||||||
|
* Fix some blur artifacts (#1095)
|
||||||
|
* Fix shadow color for transparent shadows (#1124)
|
||||||
|
* Don't spam logs when another compositor is running (#1104)
|
||||||
|
* Fix rounded corners showing as black with the xrender backend (#1003)
|
||||||
|
* Fix blur with rounded windows (#950)
|
||||||
|
|
||||||
|
## Build changes
|
||||||
|
|
||||||
|
* Dependency `pcre` has been replaced by `pcre2`.
|
||||||
|
* New dependency `xcb-util`.
|
||||||
|
* `xinerama` is no longer used.
|
||||||
|
* `picom` now tries to give itself a real-time scheduling priority. ~~Please consider giving `picom` the `CAP_SYS_NICE` capability when packaging it.~~
|
||||||
|
|
||||||
|
## Deprecations
|
||||||
|
|
||||||
|
* The `kawase` blur method is removed. Note this is just an alias to the `dual_kawase` method, which is still available. (#1102)
|
||||||
|
|
||||||
|
# Earlier versions
|
||||||
|
|
||||||
|
Please see the GitHub releases page.
|
||||||
167
CONTRIBUTORS
167
CONTRIBUTORS
@@ -1,85 +1,98 @@
|
|||||||
Sorted in alphabetical order
|
Sorted in alphabetical order. Feel free to open an issue or create a
|
||||||
Open an issue or pull request if you don't want your name listed here.
|
pull request if you want to change or remove your mention.
|
||||||
|
|
||||||
Adam Jackson <ajax@nwnk.net>
|
Adam Jackson <ajax at nwnk.net>
|
||||||
adelin-b <berard.adelin@gmail.com>
|
adelin-b <berard.adelin at gmail.com>
|
||||||
Alexander Kapshuna <kapsh@kap.sh>
|
Alexander Kapshuna <kapsh at kap.sh>
|
||||||
Antonin Décimo <antonin.decimo@gmail.com>
|
Antonin Décimo <antonin.decimo at gmail.com>
|
||||||
Antonio Vivace <dev@avivace.com>
|
Antonio Vivace <dev at avivace.com>
|
||||||
Avi-D-coder <avi.the.coder@gmail.com>
|
Avi ד <avi.the.coder at gmail.com>
|
||||||
Ben Friesen <bfriesen95@gmail.com>
|
Ben Friesen <bfriesen95 at gmail.com>
|
||||||
Bernd Busse <bernd@busse-net.de>
|
Bernd Busse <bernd at busse-net.de>
|
||||||
Bert Gijsbers <gijsbers@science.uva.nl>
|
Bert Gijsbers <gijsbers at science.uva.nl>
|
||||||
bhagwan <bhagwan@disroot.org>
|
bhagwan <bhagwan at disroot.org>
|
||||||
Bodhi <craig.langman@gmail.com>
|
Bodhi <craig.langman at gmail.com>
|
||||||
Brottweiler <tibell.christoffer@gmail.com>
|
Brottweiler <tibell.christoffer at gmail.com>
|
||||||
Carl Worth <cworth@cworth.org>
|
Carl Worth <cworth at cworth.org>
|
||||||
Christopher Jeffrey <chjjeffrey@gmail.com>
|
Christopher Jeffrey <chjjeffrey at gmail.com>
|
||||||
Corax26 <cor.ax26@gmail.com>
|
Corax26 <cor.ax26 at gmail.com>
|
||||||
Dan Elkouby <streetwalkermc@gmail.com>
|
Dan Elkouby <streetwalkermc at gmail.com>
|
||||||
Dana Jansens <danakj@orodu.net>
|
Dana Jansens <danakj at orodu.net>
|
||||||
Daniel Kwan <daniel.w.t.kwan@outlook.com>
|
Daniel Kwan <daniel.w.t.kwan at outlook.com>
|
||||||
Dave Airlie <airlied@linux.ie>
|
Dave Airlie <airlied at linux.ie>
|
||||||
David Schlachter
|
David Schlachter
|
||||||
dolio
|
dolio
|
||||||
Duncan <duncan.britton@outlook.com>
|
Duncan <duncan.britton at outlook.com>
|
||||||
Dylan Araps <dylan.araps@gmail.com>
|
Dylan Araps <dylan.araps at gmail.com>
|
||||||
Einar Lielmanis <einars@gmail.com>
|
Einar Lielmanis <einars at gmail.com>
|
||||||
Eric Anholt <anholt@freebsd.org> <eric@anholt.net>
|
Eric Anholt <anholt at freebsd.org> <eric at anholt.net>
|
||||||
|
Evgeniy Baskov <j-basevgser at yandex.ru>
|
||||||
Greg Flynn
|
Greg Flynn
|
||||||
Harish Rajagopal <harish.rajagopals@gmail.com>
|
h7x4 <h7x4 at nani.wtf>
|
||||||
hasufell <julian.ospald@googlemail.com>
|
Harish Rajagopal <harish.rajagopals at gmail.com>
|
||||||
Ignacio Taranto <ignacio.taranto@eclypsium.com>
|
hasufell <julian.ospald at googlemail.com>
|
||||||
|
i-c-u-p
|
||||||
|
Ignacio Taranto <ignacio.taranto at eclypsium.com>
|
||||||
Istvan Petres
|
Istvan Petres
|
||||||
James Cloos <cloos@jhcloos.com>
|
Ivan Malison <ivanmalison at gmail.com>
|
||||||
Jamey Sharp <jamey@minilop.net>
|
Jake <jakeroggenbuck2 at gmail.com>
|
||||||
Jan Beich <jbeich@FreeBSD.org>
|
James Cloos <cloos at jhcloos.com>
|
||||||
Jarrad <jarrad.whitaker@gmail.com>
|
Jamey Sharp <jamey at minilop.net>
|
||||||
Javeed Shaikh <syscrash2k@gmail.com>
|
Jan Beich <jbeich at freebsd.org>
|
||||||
Jerónimo Navarro <jnavarro@ancasrl.com.ar>
|
Jarrad <jarrad.whitaker at gmail.com>
|
||||||
jialeens <jialeadmin@163.com>
|
Javeed Shaikh <syscrash2k at gmail.com>
|
||||||
Johnny Pribyl <pribylsnbits@gmail.com>
|
Jerónimo Navarro <jnavarro at ancasrl.com.ar>
|
||||||
Keith Packard <keithp@keithp.com>
|
jialeens <jialeadmin at 163.com>
|
||||||
Kevin Kelley <kelleyk@kelleyk.net>
|
Johnny Pribyl <pribylsnbits at gmail.com>
|
||||||
ktprograms <ktprograms@gmail.com>
|
Jose Maldonado aka Yukiteru <josemald89 at gmail.com>
|
||||||
Lukas Schmelzeisen <l.schmelzeisen@gmx.de>
|
Keith Packard <keithp at keithp.com>
|
||||||
mæp <m.aep@live.com>
|
Kevin Kelley <kelleyk at kelleyk.net>
|
||||||
Mark Tiefenbruck <mark@fluxbox.org>
|
ktprograms <ktprograms at gmail.com>
|
||||||
Matthew Allum <breakfast@10.am>
|
Kurenshe Nurdaulet
|
||||||
Maxim Solovyov <visleaf@protonmail.com>
|
Lukas Schmelzeisen <l.schmelzeisen at gmx.de>
|
||||||
Michael Reed <supertron421@gmail.com>
|
Mark Tiefenbruck <mark at fluxbox.org>
|
||||||
Michele Lambertucci <michele.lambertucci@mail.polimi.it>
|
Matthew Allum <breakfast at 10.am>
|
||||||
Namkhai Bourquin <namkhai.n3@protonmail.com>
|
Maxim Solovyov <msolovyov at protonmail.com>
|
||||||
Nate Hart <nejthan@gmail.com>
|
Michael Reed <supertron421 at gmail.com>
|
||||||
nia <nia@netbsd.org>
|
Michele Lambertucci <michele.lambertucci at mail.polimi.it>
|
||||||
notfoss <static.vortex@gmx.com>
|
mæp <m.aep at live.com>
|
||||||
Omar Polo <op@omarpolo.com>
|
Namkhai Bourquin <namkhai.n3 at protonmail.com>
|
||||||
orbea <orbea@riseup.net>
|
Nate Hart <nejthan at gmail.com>
|
||||||
@Paradigm0001
|
nia <nia at netbsd.org>
|
||||||
|
Nikolay Borodin <monsterovich at gmail.com>
|
||||||
|
notfoss <static.vortex at gmx.com>
|
||||||
|
Omar Polo <op at omarpolo.com>
|
||||||
|
oofsauce <alanpanayotov at gmail.com>
|
||||||
|
orbea <orbea at riseup.net>
|
||||||
|
Paradigm0001
|
||||||
Patrick Collins
|
Patrick Collins
|
||||||
Peter Mattern <matternp@arcor.de>
|
Peter Mattern <matternp at arcor.de>
|
||||||
Phil Blundell <pb@reciva.com>
|
Phil Blundell <pb at reciva.com>
|
||||||
Que Quotion <quequotion@bugmenot.com> <the_q123@hotmail.com>
|
Que Quotion <quequotion at bugmenot.com>
|
||||||
Rafael Kitover <rkitover@gmail.com>
|
Rafael Kitover <rkitover at gmail.com>
|
||||||
Richard Grenville <pyxlcy@gmail.com>
|
Reith
|
||||||
Rytis Karpuska <rytis.karpuska@gmail.com>
|
Richard Grenville <pyxlcy at gmail.com>
|
||||||
Samuel Hand <samuel.d.hand@gmail.com>
|
Rytis Karpuska <rytis.karpuska at gmail.com>
|
||||||
Scott Leggett <scott@sl.id.au>
|
Samuel Hand <samuel.d.hand at gmail.com>
|
||||||
scrouthtv <lennivh24@gmail.com>
|
Scott Leggett <scott at sl.id.au>
|
||||||
Sebastien Waegeneire <sebastien@waegeneire.com>
|
scrouthtv <lennivh24 at gmail.com>
|
||||||
Subhaditya Nath <sn03.general@gmail.com>
|
Sebastien Waegeneire <sebastien at waegeneire.com>
|
||||||
Tasos Sahanidis <tasos@tasossah.com>
|
Stefan Radziuk <stefan.radziuk19 at imperial.ac.uk>
|
||||||
Thiago Kenji Okada <thiagokokada@gmail.com>
|
Subhaditya Nath <sn03.general at gmail.com>
|
||||||
Tilman Sauerbeck <tilman@code-monkey.de>
|
Tasos Sahanidis <tasos at tasossah.com>
|
||||||
Tim van Dalen <Tim@timvdalen.nl>
|
Thiago Kenji Okada <thiagokokada at gmail.com>
|
||||||
Tomas Janousek <tomi@nomi.cz>
|
Tilman Sauerbeck <tilman at code-monkey.de>
|
||||||
Tom Dörr <tomdoerr96@gmail.com>
|
Tim Siegel <siegeltr at gmail.com>
|
||||||
|
Tim van Dalen <tim at timvdalen.nl>
|
||||||
|
tokyoneon78 <mockcoder at protonmail.ch>
|
||||||
|
Tom Dörr <tomdoerr96 at gmail.com>
|
||||||
|
Tomas Janousek <tomi at nomi.cz>
|
||||||
Toni Jarjour
|
Toni Jarjour
|
||||||
Tuomas Kinnunen <tuomas.kinnunen@aalto.fi>
|
Tuomas Kinnunen <tuomas.kinnunen at aalto.fi>
|
||||||
Uli Schlachter <psychon@znc.in>
|
Uli Schlachter <psychon at znc.in>
|
||||||
Walter Lapchynski <wxl@ubuntu.com>
|
Walter Lapchynski <wxl at ubuntu.com>
|
||||||
Will Dietz <w@wdtz.org>
|
Will Dietz <w at wdtz.org>
|
||||||
XeCycle <XeCycle@Gmail.com>
|
XeCycle <xecycle at gmail.com>
|
||||||
Yuxuan Shui <yshuiv7@gmail.com>
|
Yuxuan Shui <yshuiv7 at gmail.com>
|
||||||
zilrich
|
zilrich
|
||||||
ಠ_ಠ <easteregg@verfriemelt.org>
|
ಠ_ಠ <easteregg at verfriemelt.org>
|
||||||
|
|||||||
27
README.md
27
README.md
@@ -1,11 +1,15 @@
|
|||||||
picom
|
picom
|
||||||
=====
|
=====
|
||||||
|
|
||||||
|
[](https://circleci.com/gh/yshui/picom)
|
||||||
|
[](https://codecov.io/gh/yshui/picom)
|
||||||
|
[](https://discord.gg/SY5JJzPgME)
|
||||||
|
|
||||||
__picom__ is a compositor for X, and a [fork of Compton](History.md).
|
__picom__ is a compositor for X, and a [fork of Compton](History.md).
|
||||||
|
|
||||||
**This is a development branch, bugs to be expected**
|
**This is a development branch, bugs to be expected**
|
||||||
|
|
||||||
You can leave your feedback or thoughts in the [discussion tab](https://github.com/yshui/picom/discussions).
|
You can leave your feedback or thoughts in the [discussion tab](https://github.com/yshui/picom/discussions), or chat with other users on [discord](https://discord.gg/SY5JJzPgME)!
|
||||||
|
|
||||||
## Change Log
|
## Change Log
|
||||||
|
|
||||||
@@ -22,6 +26,7 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth
|
|||||||
* libXext
|
* libXext
|
||||||
* xproto
|
* xproto
|
||||||
* xcb
|
* xcb
|
||||||
|
* xcb-util
|
||||||
* xcb-damage
|
* xcb-damage
|
||||||
* xcb-dpms
|
* xcb-dpms
|
||||||
* xcb-xfixes
|
* xcb-xfixes
|
||||||
@@ -32,12 +37,11 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth
|
|||||||
* xcb-composite
|
* xcb-composite
|
||||||
* xcb-image
|
* xcb-image
|
||||||
* xcb-present
|
* xcb-present
|
||||||
* xcb-xinerama
|
|
||||||
* xcb-glx
|
* xcb-glx
|
||||||
* pixman
|
* pixman
|
||||||
* libdbus (optional, disable with the `-Ddbus=false` meson configure flag)
|
* libdbus (optional, disable with the `-Ddbus=false` meson configure flag)
|
||||||
* libconfig (optional, disable with the `-Dconfig_file=false` meson configure flag)
|
* libconfig (optional, disable with the `-Dconfig_file=false` meson configure flag)
|
||||||
* libGL, libEGL (optional, disable with the `-Dopengl=false` meson configure flag)
|
* libGL, libEGL, libepoxy (optional, disable with the `-Dopengl=false` meson configure flag)
|
||||||
* libpcre2 (optional, disable with the `-Dregex=false` meson configure flag)
|
* libpcre2 (optional, disable with the `-Dregex=false` meson configure flag)
|
||||||
* libev
|
* libev
|
||||||
* uthash
|
* uthash
|
||||||
@@ -45,13 +49,13 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth
|
|||||||
On Debian based distributions (e.g. Ubuntu), the needed packages are
|
On Debian based distributions (e.g. Ubuntu), the needed packages are
|
||||||
|
|
||||||
```
|
```
|
||||||
libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-dpms0-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 libgl-dev libegl-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson
|
libconfig-dev libdbus-1-dev libegl-dev libev-dev libgl-dev libepoxy-dev libpcre2-dev libpixman-1-dev libx11-xcb-dev libxcb1-dev libxcb-composite0-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-glx0-dev libxcb-image0-dev libxcb-present-dev libxcb-randr0-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev libxcb-util-dev libxcb-xfixes0-dev libxext-dev meson ninja-build uthash-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
On Fedora, the needed packages are
|
On Fedora, the needed packages are
|
||||||
|
|
||||||
```
|
```
|
||||||
dbus-devel gcc git libconfig-devel libdrm-devel libev-devel libX11-devel libX11-xcb libXext-devel libxcb-devel libGL-devel libEGL-devel meson pcre2-devel pixman-devel uthash-devel xcb-util-image-devel xcb-util-renderutil-devel xorg-x11-proto-devel
|
dbus-devel gcc git libconfig-devel libdrm-devel libev-devel libX11-devel libX11-xcb libXext-devel libxcb-devel libGL-devel libEGL-devel libepoxy-devel meson pcre2-devel pixman-devel uthash-devel xcb-util-image-devel xcb-util-renderutil-devel xorg-x11-proto-devel xcb-util-devel
|
||||||
```
|
```
|
||||||
|
|
||||||
To build the documents, you need `asciidoc`
|
To build the documents, you need `asciidoc`
|
||||||
@@ -59,8 +63,7 @@ To build the documents, you need `asciidoc`
|
|||||||
### To build
|
### To build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ git submodule update --init --recursive
|
$ meson setup --buildtype=release build
|
||||||
$ meson setup --buildtype=release . build
|
|
||||||
$ ninja -C build
|
$ ninja -C build
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -71,12 +74,12 @@ If you have libraries and/or headers installed at non-default location (e.g. und
|
|||||||
You can do that by setting the `CPPFLAGS` and `LDFLAGS` environment variables when running `meson`. Like this:
|
You can do that by setting the `CPPFLAGS` and `LDFLAGS` environment variables when running `meson`. Like this:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ LDFLAGS="-L/path/to/libraries" CPPFLAGS="-I/path/to/headers" meson setup --buildtype=release . build
|
$ LDFLAGS="-L/path/to/libraries" CPPFLAGS="-I/path/to/headers" meson setup --buildtype=release build
|
||||||
```
|
```
|
||||||
|
|
||||||
As an example, on FreeBSD, you might have to run meson with:
|
As an example, on FreeBSD, you might have to run meson with:
|
||||||
```bash
|
```bash
|
||||||
$ LDFLAGS="-L/usr/local/lib" CPPFLAGS="-I/usr/local/include" meson setup --buildtype=release . build
|
$ LDFLAGS="-L/usr/local/lib" CPPFLAGS="-I/usr/local/include" meson setup --buildtype=release build
|
||||||
$ ninja -C build
|
$ ninja -C build
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -93,6 +96,12 @@ $ ninja -C build install
|
|||||||
|
|
||||||
Default install prefix is `/usr/local`, you can change it with `meson configure -Dprefix=<path> build`
|
Default install prefix is `/usr/local`, you can change it with `meson configure -Dprefix=<path> build`
|
||||||
|
|
||||||
|
## Running
|
||||||
|
To launch with all animations as a background process you can use:
|
||||||
|
`picom --animations -b`
|
||||||
|
|
||||||
|
To only have specific animations, enable them with cli flags (see `picom --help`) or add them to your picom config.
|
||||||
|
|
||||||
## How to Contribute
|
## How to Contribute
|
||||||
|
|
||||||
All contributions are welcome!
|
All contributions are welcome!
|
||||||
|
|||||||
81
flake.lock
generated
Normal file
81
flake.lock
generated
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1705309234,
|
||||||
|
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"git-ignore-nix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1703887061,
|
||||||
|
"narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"ref": "master",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1705856552,
|
||||||
|
"narHash": "sha256-JXfnuEf5Yd6bhMs/uvM67/joxYKoysyE3M2k6T3eWbg=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "612f97239e2cc474c13c9dafa0df378058c5ad8d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"git-ignore-nix": "git-ignore-nix",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
61
flake.nix
Normal file
61
flake.nix
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
flake-utils.url = github:numtide/flake-utils;
|
||||||
|
git-ignore-nix = {
|
||||||
|
url = github:hercules-ci/gitignore.nix/master;
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
outputs = {
|
||||||
|
self,
|
||||||
|
flake-utils,
|
||||||
|
nixpkgs,
|
||||||
|
git-ignore-nix,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system: let
|
||||||
|
overlay = self: super: {
|
||||||
|
picom = super.picom.overrideAttrs (oldAttrs: rec {
|
||||||
|
version = "11";
|
||||||
|
pname = "picom";
|
||||||
|
buildInputs =
|
||||||
|
[
|
||||||
|
self.pcre2
|
||||||
|
self.xorg.xcbutil
|
||||||
|
self.libepoxy
|
||||||
|
]
|
||||||
|
++ self.lib.remove self.xorg.libXinerama (
|
||||||
|
self.lib.remove self.pcre oldAttrs.buildInputs
|
||||||
|
);
|
||||||
|
src = git-ignore-nix.lib.gitignoreSource ./.;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system overlays;
|
||||||
|
config.allowBroken = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
overlays = [overlay];
|
||||||
|
in rec {
|
||||||
|
inherit
|
||||||
|
overlay
|
||||||
|
overlays
|
||||||
|
;
|
||||||
|
defaultPackage = pkgs.picom;
|
||||||
|
devShell = defaultPackage.overrideAttrs {
|
||||||
|
buildInputs =
|
||||||
|
defaultPackage.buildInputs
|
||||||
|
++ (with pkgs; [
|
||||||
|
clang-tools_17
|
||||||
|
llvmPackages_17.clang-unwrapped.python
|
||||||
|
]);
|
||||||
|
hardeningDisable = ["fortify"];
|
||||||
|
shellHook = ''
|
||||||
|
# Workaround a NixOS limitation on sanitizers:
|
||||||
|
# See: https://github.com/NixOS/nixpkgs/issues/287763
|
||||||
|
export LD_LIBRARY_PATH+=":/run/opengl-driver/lib"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -49,9 +49,6 @@ OPTIONS
|
|||||||
*-f*, *--fading*::
|
*-f*, *--fading*::
|
||||||
Fade windows in/out when opening/closing and when opacity changes, unless *--no-fading-openclose* is used.
|
Fade windows in/out when opening/closing and when opacity changes, unless *--no-fading-openclose* is used.
|
||||||
|
|
||||||
*-F*::
|
|
||||||
Equals to *-f*. Deprecated.
|
|
||||||
|
|
||||||
*-i*, *--inactive-opacity*='OPACITY'::
|
*-i*, *--inactive-opacity*='OPACITY'::
|
||||||
Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0)
|
Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0)
|
||||||
|
|
||||||
@@ -103,9 +100,15 @@ OPTIONS
|
|||||||
*--corner-radius* 'VALUE'::
|
*--corner-radius* 'VALUE'::
|
||||||
Sets the radius of rounded window corners. When > 0, the compositor will round the corners of windows. Does not interact well with *--transparent-clipping*. (defaults to 0).
|
Sets the radius of rounded window corners. When > 0, the compositor will round the corners of windows. Does not interact well with *--transparent-clipping*. (defaults to 0).
|
||||||
|
|
||||||
|
*--corner-radius-rules* 'RADIUS':'CONDITION'::
|
||||||
|
Specify a list of corner radius rules. Overrides the corner radii of matching windows. This option takes precedence over the *--rounded-corners-exclude* option, and also overrides the default exclusion of fullscreen windows. The condition has the same format as *--opacity-rule*.
|
||||||
|
|
||||||
*--rounded-corners-exclude* 'CONDITION'::
|
*--rounded-corners-exclude* 'CONDITION'::
|
||||||
Exclude conditions for rounded corners.
|
Exclude conditions for rounded corners.
|
||||||
|
|
||||||
|
*--no-frame-pacing*::
|
||||||
|
Disable vsync-aware frame pacing. By default, the compositor tries to make sure it only renders once per vblank interval, and also the render happens as late as possible to minimize the latency from updates to the screen. However this can sometimes cause stuttering, or even lowered frame rate. This option can be used to disable frame pacing.
|
||||||
|
|
||||||
*--mark-wmwin-focused*::
|
*--mark-wmwin-focused*::
|
||||||
Try to detect WM windows (a non-override-redirect window with no child that has 'WM_STATE') and mark them as active.
|
Try to detect WM windows (a non-override-redirect window with no child that has 'WM_STATE') and mark them as active.
|
||||||
|
|
||||||
@@ -217,8 +220,8 @@ May also be one of the predefined kernels: `3x3box` (default), `5x5box`, `7x7box
|
|||||||
*--shadow-exclude-reg* 'GEOMETRY'::
|
*--shadow-exclude-reg* 'GEOMETRY'::
|
||||||
Specify a X geometry that describes the region in which shadow should not be painted in, such as a dock window region. Use `--shadow-exclude-reg x10+0-0`, for example, if the 10 pixels on the bottom of the screen should not have shadows painted on.
|
Specify a X geometry that describes the region in which shadow should not be painted in, such as a dock window region. Use `--shadow-exclude-reg x10+0-0`, for example, if the 10 pixels on the bottom of the screen should not have shadows painted on.
|
||||||
|
|
||||||
*--xinerama-shadow-crop*::
|
*--crop-shadow-to-monitor*::
|
||||||
Crop shadow of a window fully on a particular Xinerama screen to the screen.
|
Crop shadow of a window fully on a particular monitor to that monitor. This is currently implemented using the X RandR extension.
|
||||||
|
|
||||||
*--backend* 'BACKEND'::
|
*--backend* 'BACKEND'::
|
||||||
Specify the backend to use: `xrender`, `glx`, or `xr_glx_hybrid`. `xrender` is the default one.
|
Specify the backend to use: `xrender`, `glx`, or `xr_glx_hybrid`. `xrender` is the default one.
|
||||||
@@ -387,6 +390,7 @@ uniform float corner_radius; // corner radius of the window (pixels)
|
|||||||
uniform float border_width; // estimated border width of the window (pixels)
|
uniform float border_width; // estimated border width of the window (pixels)
|
||||||
uniform bool invert_color; // whether to invert the color of the window
|
uniform bool invert_color; // whether to invert the color of the window
|
||||||
uniform sampler2D tex; // texture of the window
|
uniform sampler2D tex; // texture of the window
|
||||||
|
uniform vec2 effective_size; // effective dimensions of the texture (repeats pixels if larger than tex)
|
||||||
uniform sampler2D brightness; // estimated brightness of the window, 1x1 texture
|
uniform sampler2D brightness; // estimated brightness of the window, 1x1 texture
|
||||||
uniform float max_brightness; // configured maximum brightness of the window (0.0 - 1.0)
|
uniform float max_brightness; // configured maximum brightness of the window (0.0 - 1.0)
|
||||||
uniform float time; // time in milliseconds, counting from an unspecified starting point
|
uniform float time; // time in milliseconds, counting from an unspecified starting point
|
||||||
@@ -412,7 +416,8 @@ vec4 default_post_processing(vec4 c);
|
|||||||
// 1) fetch the specified pixel
|
// 1) fetch the specified pixel
|
||||||
// 2) apply default post-processing
|
// 2) apply default post-processing
|
||||||
vec4 window_shader() {
|
vec4 window_shader() {
|
||||||
vec4 c = texelFetch(tex, ivec2(texcoord), 0);
|
vec2 texsize = textureSize(tex, 0);
|
||||||
|
vec4 c = texture2D(tex, texcoord / texsize, 0);
|
||||||
return default_post_processing(c);
|
return default_post_processing(c);
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
project('picom', 'c', version: '10',
|
project('picom', 'c', version: '11',
|
||||||
default_options: ['c_std=c11', 'warning_level=1'])
|
default_options: ['c_std=c11', 'warning_level=1'])
|
||||||
|
|
||||||
cc = meson.get_compiler('c')
|
cc = meson.get_compiler('c')
|
||||||
|
|||||||
@@ -111,8 +111,9 @@ shadow-exclude = [
|
|||||||
#
|
#
|
||||||
# shadow-exclude-reg = ""
|
# shadow-exclude-reg = ""
|
||||||
|
|
||||||
# Crop shadow of a window fully on a particular Xinerama screen to the screen.
|
# Crop shadow of a window fully on a particular monitor to that monitor. This is
|
||||||
# xinerama-shadow-crop = false
|
# currently implemented using the X RandR extension.
|
||||||
|
# crop-shadow-to-monitor = false
|
||||||
|
|
||||||
|
|
||||||
#################################
|
#################################
|
||||||
@@ -232,9 +233,6 @@ dithered-present = false;
|
|||||||
# Enable/disable VSync.
|
# Enable/disable VSync.
|
||||||
# vsync = true
|
# vsync = true
|
||||||
|
|
||||||
# Enable remote control via D-Bus. See the *D-BUS API* section below for more details.
|
|
||||||
# dbus = false
|
|
||||||
|
|
||||||
# Try to detect WM windows (a non-override-redirect window with no
|
# Try to detect WM windows (a non-override-redirect window with no
|
||||||
# child that has 'WM_STATE') and mark them as active.
|
# child that has 'WM_STATE') and mark them as active.
|
||||||
#
|
#
|
||||||
|
|||||||
50
src/atom.c
50
src/atom.c
@@ -2,12 +2,20 @@
|
|||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
#include "atom.h"
|
#include "atom.h"
|
||||||
|
#include "cache.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "utils.h"
|
#include "compiler.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
static inline void *atom_getter(void *ud, const char *atom_name, int *err) {
|
struct atom_entry {
|
||||||
xcb_connection_t *c = ud;
|
struct cache_handle entry;
|
||||||
|
xcb_atom_t atom;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline int atom_getter(struct cache *cache attr_unused, const char *atom_name,
|
||||||
|
struct cache_handle **value, void *user_data) {
|
||||||
|
xcb_connection_t *c = user_data;
|
||||||
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(
|
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(
|
||||||
c, xcb_intern_atom(c, 0, to_u16_checked(strlen(atom_name)), atom_name), NULL);
|
c, xcb_intern_atom(c, 0, to_u16_checked(strlen(atom_name)), atom_name), NULL);
|
||||||
|
|
||||||
@@ -18,9 +26,32 @@ static inline void *atom_getter(void *ud, const char *atom_name, int *err) {
|
|||||||
free(reply);
|
free(reply);
|
||||||
} else {
|
} else {
|
||||||
log_error("Failed to intern atoms");
|
log_error("Failed to intern atoms");
|
||||||
*err = 1;
|
return -1;
|
||||||
}
|
}
|
||||||
return (void *)(intptr_t)atom;
|
|
||||||
|
struct atom_entry *entry = ccalloc(1, struct atom_entry);
|
||||||
|
entry->atom = atom;
|
||||||
|
*value = &entry->entry;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
atom_entry_free(struct cache *cache attr_unused, struct cache_handle *handle) {
|
||||||
|
struct atom_entry *entry = cache_entry(handle, struct atom_entry, entry);
|
||||||
|
free(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb_atom_t get_atom(struct atom *a, const char *key, xcb_connection_t *c) {
|
||||||
|
struct cache_handle *entry = NULL;
|
||||||
|
if (cache_get_or_fetch(&a->c, key, &entry, c, atom_getter) < 0) {
|
||||||
|
log_error("Failed to get atom %s", key);
|
||||||
|
return XCB_NONE;
|
||||||
|
}
|
||||||
|
return cache_entry(entry, struct atom_entry, entry)->atom;
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb_atom_t get_atom_cached(struct atom *a, const char *key) {
|
||||||
|
return cache_entry(cache_get(&a->c, key), struct atom_entry, entry)->atom;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,10 +59,15 @@ static inline void *atom_getter(void *ud, const char *atom_name, int *err) {
|
|||||||
*/
|
*/
|
||||||
struct atom *init_atoms(xcb_connection_t *c) {
|
struct atom *init_atoms(xcb_connection_t *c) {
|
||||||
auto atoms = ccalloc(1, struct atom);
|
auto atoms = ccalloc(1, struct atom);
|
||||||
atoms->c = new_cache((void *)c, atom_getter, NULL);
|
atoms->c = CACHE_INIT;
|
||||||
#define ATOM_GET(x) atoms->a##x = (xcb_atom_t)(intptr_t)cache_get(atoms->c, #x, NULL)
|
#define ATOM_GET(x) atoms->a##x = get_atom(atoms, #x, c)
|
||||||
LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST1);
|
LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST1);
|
||||||
LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST2);
|
LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST2);
|
||||||
#undef ATOM_GET
|
#undef ATOM_GET
|
||||||
return atoms;
|
return atoms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void destroy_atoms(struct atom *a) {
|
||||||
|
cache_invalidate_all(&a->c, atom_entry_free);
|
||||||
|
free(a);
|
||||||
|
}
|
||||||
|
|||||||
26
src/atom.h
26
src/atom.h
@@ -1,10 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
#include "meta.h"
|
|
||||||
#include "cache.h"
|
#include "cache.h"
|
||||||
|
#include "meta.h"
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
// Splitted into 2 lists because of the limitation of our macros
|
// Splitted into 2 lists because of the limitation of our macros
|
||||||
@@ -23,8 +21,11 @@
|
|||||||
WM_CLIENT_MACHINE, \
|
WM_CLIENT_MACHINE, \
|
||||||
_NET_ACTIVE_WINDOW, \
|
_NET_ACTIVE_WINDOW, \
|
||||||
_COMPTON_SHADOW, \
|
_COMPTON_SHADOW, \
|
||||||
|
COMPTON_VERSION, \
|
||||||
_NET_WM_WINDOW_TYPE, \
|
_NET_WM_WINDOW_TYPE, \
|
||||||
_NET_CURRENT_MON_CENTER
|
_XROOTPMAP_ID, \
|
||||||
|
ESETROOT_PMAP_ID, \
|
||||||
|
_XSETROOT_ID
|
||||||
|
|
||||||
#define ATOM_LIST2 \
|
#define ATOM_LIST2 \
|
||||||
_NET_WM_WINDOW_TYPE_DESKTOP, \
|
_NET_WM_WINDOW_TYPE_DESKTOP, \
|
||||||
@@ -50,19 +51,18 @@
|
|||||||
|
|
||||||
#define ATOM_DEF(x) xcb_atom_t a##x
|
#define ATOM_DEF(x) xcb_atom_t a##x
|
||||||
|
|
||||||
|
struct atom_entry;
|
||||||
struct atom {
|
struct atom {
|
||||||
struct cache *c;
|
struct cache c;
|
||||||
LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST1);
|
LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST1);
|
||||||
LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST2);
|
LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST2);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct atom *init_atoms(xcb_connection_t *);
|
/// Create a new atom object with a xcb connection. `struct atom` does not hold
|
||||||
|
/// a reference to the connection.
|
||||||
|
struct atom *init_atoms(xcb_connection_t *c);
|
||||||
|
|
||||||
static inline xcb_atom_t get_atom(struct atom *a, const char *key) {
|
xcb_atom_t get_atom(struct atom *a, const char *key, xcb_connection_t *c);
|
||||||
return (xcb_atom_t)(intptr_t)cache_get(a->c, key, NULL);
|
xcb_atom_t get_atom_cached(struct atom *a, const char *key);
|
||||||
}
|
|
||||||
|
|
||||||
static inline void destroy_atoms(struct atom *a) {
|
void destroy_atoms(struct atom *a);
|
||||||
cache_free(a->c);
|
|
||||||
free(a);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
||||||
|
#include <inttypes.h>
|
||||||
#include <xcb/sync.h>
|
#include <xcb/sync.h>
|
||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
@@ -133,35 +134,43 @@ void handle_device_reset(session_t *ps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// paint all windows
|
/// paint all windows
|
||||||
void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
|
///
|
||||||
|
/// Returns if any render command is issued. IOW if nothing on the screen has changed,
|
||||||
|
/// this function will return false.
|
||||||
|
bool paint_all_new(session_t *ps, struct managed_win *const t) {
|
||||||
|
struct timespec now = get_time_timespec();
|
||||||
|
auto paint_all_start_us =
|
||||||
|
(uint64_t)now.tv_sec * 1000000UL + (uint64_t)now.tv_nsec / 1000;
|
||||||
if (ps->backend_data->ops->device_status &&
|
if (ps->backend_data->ops->device_status &&
|
||||||
ps->backend_data->ops->device_status(ps->backend_data) != DEVICE_STATUS_NORMAL) {
|
ps->backend_data->ops->device_status(ps->backend_data) != DEVICE_STATUS_NORMAL) {
|
||||||
return handle_device_reset(ps);
|
handle_device_reset(ps);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (ps->o.xrender_sync_fence) {
|
if (ps->o.xrender_sync_fence) {
|
||||||
if (ps->xsync_exists && !x_fence_sync(ps->c, ps->sync_fence)) {
|
if (ps->xsync_exists && !x_fence_sync(&ps->c, ps->sync_fence)) {
|
||||||
log_error("x_fence_sync failed, xrender-sync-fence will be "
|
log_error("x_fence_sync failed, xrender-sync-fence will be "
|
||||||
"disabled from now on.");
|
"disabled from now on.");
|
||||||
xcb_sync_destroy_fence(ps->c, ps->sync_fence);
|
xcb_sync_destroy_fence(ps->c.c, ps->sync_fence);
|
||||||
ps->sync_fence = XCB_NONE;
|
ps->sync_fence = XCB_NONE;
|
||||||
ps->o.xrender_sync_fence = false;
|
ps->o.xrender_sync_fence = false;
|
||||||
ps->xsync_exists = false;
|
ps->xsync_exists = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now = get_time_timespec();
|
||||||
|
auto after_sync_fence_us =
|
||||||
|
(uint64_t)now.tv_sec * 1000000UL + (uint64_t)now.tv_nsec / 1000;
|
||||||
|
log_trace("Time spent on sync fence: %" PRIu64 " us",
|
||||||
|
after_sync_fence_us - paint_all_start_us);
|
||||||
// All painting will be limited to the damage, if _some_ of
|
// All painting will be limited to the damage, if _some_ of
|
||||||
// the paints bleed out of the damage region, it will destroy
|
// the paints bleed out of the damage region, it will destroy
|
||||||
// part of the image we want to reuse
|
// part of the image we want to reuse
|
||||||
region_t reg_damage;
|
region_t reg_damage;
|
||||||
if (!ignore_damage) {
|
reg_damage = get_damage(ps, ps->o.monitor_repaint || !ps->o.use_damage);
|
||||||
reg_damage = get_damage(ps, ps->o.monitor_repaint || !ps->o.use_damage);
|
|
||||||
} else {
|
|
||||||
pixman_region32_init(®_damage);
|
|
||||||
pixman_region32_copy(®_damage, &ps->screen_reg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pixman_region32_not_empty(®_damage)) {
|
if (!pixman_region32_not_empty(®_damage)) {
|
||||||
pixman_region32_fini(®_damage);
|
pixman_region32_fini(®_damage);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_REPAINT
|
#ifdef DEBUG_REPAINT
|
||||||
@@ -201,7 +210,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
|
|||||||
// TODO(yshui): maybe we don't need to resize reg_damage, only reg_paint?
|
// TODO(yshui): maybe we don't need to resize reg_damage, only reg_paint?
|
||||||
int resize_factor = 1;
|
int resize_factor = 1;
|
||||||
if (t) {
|
if (t) {
|
||||||
resize_factor = t->stacking_rank;
|
resize_factor = t->stacking_rank + 1;
|
||||||
}
|
}
|
||||||
resize_region_in_place(®_damage, blur_width * resize_factor,
|
resize_region_in_place(®_damage, blur_width * resize_factor,
|
||||||
blur_height * resize_factor);
|
blur_height * resize_factor);
|
||||||
@@ -233,6 +242,20 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
|
|||||||
region_t reg_shadow_clip;
|
region_t reg_shadow_clip;
|
||||||
pixman_region32_init(®_shadow_clip);
|
pixman_region32_init(®_shadow_clip);
|
||||||
|
|
||||||
|
now = get_time_timespec();
|
||||||
|
auto after_damage_us = (uint64_t)now.tv_sec * 1000000UL + (uint64_t)now.tv_nsec / 1000;
|
||||||
|
log_trace("Getting damage took %" PRIu64 " us", after_damage_us - after_sync_fence_us);
|
||||||
|
if (ps->next_render > 0) {
|
||||||
|
log_verbose("Render schedule deviation: %ld us (%s) %" PRIu64 " %" PRIu64,
|
||||||
|
labs((long)after_damage_us - (long)ps->next_render),
|
||||||
|
after_damage_us < ps->next_render ? "early" : "late",
|
||||||
|
after_damage_us, ps->next_render);
|
||||||
|
ps->last_schedule_delay = 0;
|
||||||
|
if (after_damage_us > ps->next_render) {
|
||||||
|
ps->last_schedule_delay = after_damage_us - ps->next_render;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ps->backend_data->ops->prepare) {
|
if (ps->backend_data->ops->prepare) {
|
||||||
ps->backend_data->ops->prepare(ps->backend_data, ®_paint);
|
ps->backend_data->ops->prepare(ps->backend_data, ®_paint);
|
||||||
}
|
}
|
||||||
@@ -251,7 +274,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
|
|||||||
// on top of that window. This is used to reduce the number of pixels painted.
|
// on top of that window. This is used to reduce the number of pixels painted.
|
||||||
//
|
//
|
||||||
// Whether this is beneficial is to be determined XXX
|
// Whether this is beneficial is to be determined XXX
|
||||||
for (auto w = t; w; w = w->prev_trans) {
|
for (struct managed_win *w = t; w; w = w->prev_trans) {
|
||||||
pixman_region32_subtract(®_visible, &ps->screen_reg, w->reg_ignore);
|
pixman_region32_subtract(®_visible, &ps->screen_reg, w->reg_ignore);
|
||||||
assert(!(w->flags & WIN_FLAGS_IMAGE_ERROR));
|
assert(!(w->flags & WIN_FLAGS_IMAGE_ERROR));
|
||||||
assert(!(w->flags & WIN_FLAGS_PIXMAP_STALE));
|
assert(!(w->flags & WIN_FLAGS_PIXMAP_STALE));
|
||||||
@@ -380,18 +403,18 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
|
|||||||
®_shadow_clip);
|
®_shadow_clip);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps->o.xinerama_shadow_crop && w->xinerama_scr >= 0 &&
|
if (ps->o.crop_shadow_to_monitor && w->randr_monitor >= 0 &&
|
||||||
w->xinerama_scr < ps->xinerama_nscrs) {
|
w->randr_monitor < ps->monitors.count) {
|
||||||
// There can be a window where number of screens is
|
// There can be a window where number of monitors is
|
||||||
// updated, but the screen number attached to the windows
|
// updated, but the monitor number attached to the window
|
||||||
// have not.
|
// have not.
|
||||||
//
|
//
|
||||||
// Window screen number will be updated eventually, so
|
// Window monitor number will be updated eventually, so
|
||||||
// here we just check to make sure we don't access out of
|
// here we just check to make sure we don't access out of
|
||||||
// bounds.
|
// bounds.
|
||||||
pixman_region32_intersect(
|
pixman_region32_intersect(
|
||||||
®_shadow, ®_shadow,
|
®_shadow, ®_shadow,
|
||||||
&ps->xinerama_scr_regs[w->xinerama_scr]);
|
&ps->monitors.regions[w->randr_monitor]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps->o.transparent_clipping) {
|
if (ps->o.transparent_clipping) {
|
||||||
@@ -497,14 +520,14 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw window on target
|
// Draw window on target
|
||||||
bool is_animating = 0 < w->animation_progress && w->animation_progress < 1.0;
|
bool is_animating = 0 <= w->animation_progress && w->animation_progress < 1.0;
|
||||||
if (w->frame_opacity == 1 && !is_animating) {
|
if (w->frame_opacity == 1 && !is_animating) {
|
||||||
ps->backend_data->ops->compose(ps->backend_data, w->win_image,
|
ps->backend_data->ops->compose(ps->backend_data, w->win_image,
|
||||||
window_coord, NULL, dest_coord,
|
window_coord, NULL, dest_coord,
|
||||||
®_paint_in_bound, ®_visible, true);
|
®_paint_in_bound, ®_visible, true);
|
||||||
} else {
|
} else {
|
||||||
if (is_animating && w->old_win_image) {
|
if (is_animating && w->old_win_image) {
|
||||||
bool is_focused = win_is_focused_raw(ps, w);
|
bool is_focused = win_is_focused_raw(w);
|
||||||
if (!is_focused && w->focused && w->opacity_is_set)
|
if (!is_focused && w->focused && w->opacity_is_set)
|
||||||
is_focused = true;
|
is_focused = true;
|
||||||
assert(w->old_win_image);
|
assert(w->old_win_image);
|
||||||
@@ -575,6 +598,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
|
|||||||
for (win *w = t; w; w = w->prev_trans)
|
for (win *w = t; w; w = w->prev_trans)
|
||||||
log_trace(" %#010lx", w->id);
|
log_trace(" %#010lx", w->id);
|
||||||
#endif
|
#endif
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// vim: set noet sw=8 ts=8 :
|
// vim: set noet sw=8 ts=8 :
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ struct backend_operations;
|
|||||||
|
|
||||||
typedef struct backend_base {
|
typedef struct backend_base {
|
||||||
struct backend_operations *ops;
|
struct backend_operations *ops;
|
||||||
xcb_connection_t *c;
|
struct x_connection *c;
|
||||||
xcb_window_t root;
|
|
||||||
struct ev_loop *loop;
|
struct ev_loop *loop;
|
||||||
|
|
||||||
/// Whether the backend can accept new render request at the moment
|
/// Whether the backend can accept new render request at the moment
|
||||||
@@ -116,15 +115,15 @@ struct dual_kawase_blur_args {
|
|||||||
int strength;
|
int strength;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
// Intentionally left blank
|
||||||
|
} *image_handle;
|
||||||
|
|
||||||
struct backend_operations {
|
struct backend_operations {
|
||||||
// =========== Initialization ===========
|
// =========== Initialization ===========
|
||||||
|
|
||||||
/// Initialize the backend, prepare for rendering to the target window.
|
/// Initialize the backend, prepare for rendering to the target window.
|
||||||
/// Here is how you should choose target window:
|
backend_t *(*init)(session_t *, xcb_window_t)attr_nonnull(1);
|
||||||
/// 1) if ps->overlay is not XCB_NONE, use that
|
|
||||||
/// 2) use ps->root otherwise
|
|
||||||
// TODO(yshui) make the target window a parameter
|
|
||||||
backend_t *(*init)(session_t *)attr_nonnull(1);
|
|
||||||
void (*deinit)(backend_t *backend_data) attr_nonnull(1);
|
void (*deinit)(backend_t *backend_data) attr_nonnull(1);
|
||||||
|
|
||||||
/// Called when rendering will be stopped for an unknown amount of
|
/// Called when rendering will be stopped for an unknown amount of
|
||||||
@@ -172,32 +171,28 @@ struct backend_operations {
|
|||||||
* Paint the content of an image onto the rendering buffer.
|
* Paint the content of an image onto the rendering buffer.
|
||||||
*
|
*
|
||||||
* @param backend_data the backend data
|
* @param backend_data the backend data
|
||||||
* @param image_data the image to paint
|
* @param image the image to paint, cannot be NULL
|
||||||
* @param dst_x, dst_y the top left corner of the image in the target
|
* @param dst_x, dst_y the top left corner of the image in the target
|
||||||
* @param mask the mask image, the top left of the mask is aligned with
|
* @param mask the mask image, the top left of the mask is aligned with
|
||||||
* the top left of the image
|
* the top left of the image. Optional, can be
|
||||||
|
* NULL.
|
||||||
* @param reg_paint the clip region, in target coordinates
|
* @param reg_paint the clip region, in target coordinates
|
||||||
* @param reg_visible the visible region, in target coordinates
|
* @param reg_visible the visible region, in target coordinates
|
||||||
*/
|
*/
|
||||||
void (*compose)(backend_t *backend_data, void *image_data, coord_t image_dst,
|
void (*compose)(backend_t *backend_data, image_handle image, coord_t image_dst,
|
||||||
void *mask, coord_t mask_dst, const region_t *reg_paint,
|
image_handle mask, coord_t mask_dst, const region_t *reg_paint,
|
||||||
const region_t *reg_visible, bool lerp);
|
const region_t *reg_visible, bool lerp) attr_nonnull(1, 2, 6, 7);
|
||||||
|
|
||||||
void (*_compose)(backend_t *backend_data, void *image_data,
|
|
||||||
int dst_x1, int dst_y1, int dst_x2, int dst_y2,
|
|
||||||
const region_t *reg_paint, const region_t *reg_visible);
|
|
||||||
|
|
||||||
|
|
||||||
/// Fill rectangle of the rendering buffer, mostly for debug purposes, optional.
|
/// Fill rectangle of the rendering buffer, mostly for debug purposes, optional.
|
||||||
void (*fill)(backend_t *backend_data, struct color, const region_t *clip);
|
void (*fill)(backend_t *backend_data, struct color, const region_t *clip);
|
||||||
|
|
||||||
/// Blur a given region of the rendering buffer.
|
/// Blur a given region of the rendering buffer.
|
||||||
///
|
///
|
||||||
/// The blur is limited by `mask`. `mask_dst` specifies the top left corner of the
|
/// The blur can be limited by `mask`. `mask_dst` specifies the top left corner of
|
||||||
/// mask is.
|
/// the mask. `mask` can be NULL.
|
||||||
bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx, void *mask,
|
bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx,
|
||||||
coord_t mask_dst, const region_t *reg_blur,
|
image_handle mask, coord_t mask_dst, const region_t *reg_blur,
|
||||||
const region_t *reg_visible) attr_nonnull(1, 3, 4, 6, 7);
|
const region_t *reg_visible) attr_nonnull(1, 3, 6, 7);
|
||||||
|
|
||||||
/// Update part of the back buffer with the rendering buffer, then present the
|
/// Update part of the back buffer with the rendering buffer, then present the
|
||||||
/// back buffer onto the target window (if not back buffered, update part of the
|
/// back buffer onto the target window (if not back buffered, update part of the
|
||||||
@@ -212,39 +207,47 @@ struct backend_operations {
|
|||||||
* Bind a X pixmap to the backend's internal image data structure.
|
* Bind a X pixmap to the backend's internal image data structure.
|
||||||
*
|
*
|
||||||
* @param backend_data backend data
|
* @param backend_data backend data
|
||||||
* @param pixmap X pixmap to bind
|
* @param pixmap X pixmap to bind
|
||||||
* @param fmt information of the pixmap's visual
|
* @param fmt information of the pixmap's visual
|
||||||
* @param owned whether the ownership of the pixmap is transfered to the backend
|
* @param owned whether the ownership of the pixmap is transferred to the
|
||||||
* @return backend internal data structure bound with this pixmap
|
* backend.
|
||||||
|
* @return backend specific image handle for the pixmap. May be
|
||||||
|
* NULL.
|
||||||
*/
|
*/
|
||||||
void *(*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap,
|
image_handle (*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap,
|
||||||
struct xvisual_info fmt, bool owned);
|
struct xvisual_info fmt, bool owned);
|
||||||
|
|
||||||
/// Create a shadow context for rendering shadows with radius `radius`.
|
/// Create a shadow context for rendering shadows with radius `radius`.
|
||||||
/// Default implementation: default_backend_create_shadow_context
|
/// Default implementation: default_create_shadow_context
|
||||||
struct backend_shadow_context *(*create_shadow_context)(backend_t *backend_data,
|
struct backend_shadow_context *(*create_shadow_context)(backend_t *backend_data,
|
||||||
double radius);
|
double radius);
|
||||||
/// Destroy a shadow context
|
/// Destroy a shadow context
|
||||||
/// Default implementation: default_backend_destroy_shadow_context
|
/// Default implementation: default_destroy_shadow_context
|
||||||
void (*destroy_shadow_context)(backend_t *backend_data,
|
void (*destroy_shadow_context)(backend_t *backend_data,
|
||||||
struct backend_shadow_context *ctx);
|
struct backend_shadow_context *ctx);
|
||||||
|
|
||||||
/// Create a shadow image based on the parameters. Resulting image should have a
|
/// Create a shadow image based on the parameters. Resulting image should have a
|
||||||
/// size of `width + radisu * 2` x `height + radius * 2`. Radius is set when the
|
/// size of `width + radius * 2` x `height + radius * 2`. Radius is set when the
|
||||||
/// shadow context is created.
|
/// shadow context is created.
|
||||||
/// Default implementation: default_backend_render_shadow
|
/// Default implementation: default_render_shadow
|
||||||
|
///
|
||||||
|
/// @return the shadow image, may be NULL.
|
||||||
///
|
///
|
||||||
/// Required.
|
/// Required.
|
||||||
void *(*render_shadow)(backend_t *backend_data, int width, int height,
|
image_handle (*render_shadow)(backend_t *backend_data, int width, int height,
|
||||||
struct backend_shadow_context *ctx, struct color color);
|
struct backend_shadow_context *ctx, struct color color);
|
||||||
|
|
||||||
/// Create a shadow by blurring a mask. `size` is the size of the blur. The
|
/// Create a shadow by blurring a mask. `size` is the size of the blur. The
|
||||||
/// backend can use whichever blur method is the fastest. The shadow produced
|
/// backend can use whichever blur method is the fastest. The shadow produced
|
||||||
/// shoule be consistent with `render_shadow`.
|
/// shoule be consistent with `render_shadow`.
|
||||||
///
|
///
|
||||||
|
/// @param mask the input mask, must not be NULL.
|
||||||
|
/// @return the shadow image, may be NULL.
|
||||||
|
///
|
||||||
/// Optional.
|
/// Optional.
|
||||||
void *(*shadow_from_mask)(backend_t *backend_data, void *mask,
|
image_handle (*shadow_from_mask)(backend_t *backend_data, image_handle mask,
|
||||||
struct backend_shadow_context *ctx, struct color color);
|
struct backend_shadow_context *ctx,
|
||||||
|
struct color color);
|
||||||
|
|
||||||
/// Create a mask image from region `reg`. This region can be used to create
|
/// Create a mask image from region `reg`. This region can be used to create
|
||||||
/// shadow, or used as a mask for composing. When used as a mask, it should mask
|
/// shadow, or used as a mask for composing. When used as a mask, it should mask
|
||||||
@@ -255,13 +258,18 @@ struct backend_operations {
|
|||||||
/// and outside of the mask. Corner radius should exclude the corners from the
|
/// and outside of the mask. Corner radius should exclude the corners from the
|
||||||
/// mask. Corner radius should be applied before the inversion.
|
/// mask. Corner radius should be applied before the inversion.
|
||||||
///
|
///
|
||||||
|
/// @return the mask image, may be NULL.
|
||||||
|
///
|
||||||
/// Required.
|
/// Required.
|
||||||
void *(*make_mask)(backend_t *backend_data, geometry_t size, const region_t *reg);
|
image_handle (*make_mask)(backend_t *backend_data, geometry_t size,
|
||||||
|
const region_t *reg);
|
||||||
|
|
||||||
// ============ Resource management ===========
|
// ============ Resource management ===========
|
||||||
|
|
||||||
/// Free resources associated with an image data structure
|
/// Free resources associated with an image data structure
|
||||||
void (*release_image)(backend_t *backend_data, void *img_data) attr_nonnull(1, 2);
|
///
|
||||||
|
/// @param image the image to be released, cannot be NULL.
|
||||||
|
void (*release_image)(backend_t *backend_data, image_handle image) attr_nonnull(1, 2);
|
||||||
|
|
||||||
/// Create a shader object from a shader source.
|
/// Create a shader object from a shader source.
|
||||||
///
|
///
|
||||||
@@ -286,17 +294,27 @@ struct backend_operations {
|
|||||||
/// This function is needed because some backend might change the content of the
|
/// This function is needed because some backend might change the content of the
|
||||||
/// window (e.g. when using a custom shader with the glx backend), so only the
|
/// window (e.g. when using a custom shader with the glx backend), so only the
|
||||||
/// backend knows if an image is transparent.
|
/// backend knows if an image is transparent.
|
||||||
bool (*is_image_transparent)(backend_t *backend_data, void *image_data)
|
///
|
||||||
|
/// @param image the image to be checked, must not be NULL.
|
||||||
|
bool (*is_image_transparent)(backend_t *backend_data, image_handle image)
|
||||||
attr_nonnull(1, 2);
|
attr_nonnull(1, 2);
|
||||||
|
|
||||||
/// Get the age of the buffer content we are currently rendering ontop
|
/// Get the age of the buffer content we are currently rendering on top
|
||||||
/// of. The buffer that has just been `present`ed has a buffer age of 1.
|
/// of. The buffer that has just been `present`ed has a buffer age of 1.
|
||||||
/// Everytime `present` is called, buffers get older. Return -1 if the
|
/// Every time `present` is called, buffers get older. Return -1 if the
|
||||||
/// buffer is empty.
|
/// buffer is empty.
|
||||||
///
|
///
|
||||||
/// Optional
|
/// Optional
|
||||||
int (*buffer_age)(backend_t *backend_data);
|
int (*buffer_age)(backend_t *backend_data);
|
||||||
|
|
||||||
|
/// Get the render time of the last frame. If the render is still in progress,
|
||||||
|
/// returns false. The time is returned in `ts`. Frames are delimited by the
|
||||||
|
/// present() calls. i.e. after a present() call, last_render_time() should start
|
||||||
|
/// reporting the time of the just presented frame.
|
||||||
|
///
|
||||||
|
/// Optional, if not available, the most conservative estimation will be used.
|
||||||
|
bool (*last_render_time)(backend_t *backend_data, struct timespec *ts);
|
||||||
|
|
||||||
/// The maximum number buffer_age might return.
|
/// The maximum number buffer_age might return.
|
||||||
int max_buffer_age;
|
int max_buffer_age;
|
||||||
|
|
||||||
@@ -313,35 +331,39 @@ struct backend_operations {
|
|||||||
*
|
*
|
||||||
* @param backend_data backend data
|
* @param backend_data backend data
|
||||||
* @param prop the property to change
|
* @param prop the property to change
|
||||||
* @param image_data an image data structure returned by the backend
|
* @param image an image handle, cannot be NULL.
|
||||||
* @param args property value
|
* @param args property value
|
||||||
* @return whether the operation is successful
|
* @return whether the operation is successful
|
||||||
*/
|
*/
|
||||||
bool (*set_image_property)(backend_t *backend_data, enum image_properties prop,
|
bool (*set_image_property)(backend_t *backend_data, enum image_properties prop,
|
||||||
void *image_data, void *args);
|
image_handle image, void *args) attr_nonnull(1, 3);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manipulate an image. Image properties are untouched.
|
* Manipulate an image. Image properties are untouched.
|
||||||
*
|
*
|
||||||
* @param backend_data backend data
|
* @param backend_data backend data
|
||||||
* @param op the operation to perform
|
* @param op the operation to perform
|
||||||
* @param image_data an image data structure returned by the backend
|
* @param image an image handle, cannot be NULL.
|
||||||
* @param reg_op the clip region, define the part of the image to be
|
* @param reg_op the clip region, define the part of the image to be
|
||||||
* operated on.
|
* operated on.
|
||||||
* @param reg_visible define the part of the image that will eventually
|
* @param reg_visible define the part of the image that will eventually
|
||||||
* be visible on target. this is a hint to the backend
|
* be visible on target. this is a hint to the backend
|
||||||
* for optimization purposes.
|
* for optimization purposes.
|
||||||
* @param args extra arguments, operation specific
|
* @param args extra arguments, operation specific
|
||||||
* @return whether the operation is successful
|
* @return whether the operation is successful
|
||||||
*/
|
*/
|
||||||
bool (*image_op)(backend_t *backend_data, enum image_operations op, void *image_data,
|
bool (*image_op)(backend_t *backend_data, enum image_operations op,
|
||||||
const region_t *reg_op, const region_t *reg_visible, void *args);
|
image_handle image, const region_t *reg_op,
|
||||||
|
const region_t *reg_visible, void *args) attr_nonnull(1, 3, 4, 5);
|
||||||
|
|
||||||
/// Create another instance of the `image_data`. All `image_op` and
|
/// Create another instance of the `image`. The newly created image
|
||||||
/// `set_image_property` calls on the returned image should not affect the
|
/// inherits its content and all image properties from the image being
|
||||||
/// original image
|
/// cloned. All `image_op` and `set_image_property` calls on the
|
||||||
void *(*clone_image)(backend_t *base, const void *image_data,
|
/// returned image should not affect the original image.
|
||||||
const region_t *reg_visible);
|
///
|
||||||
|
/// @param image the image to be cloned, must not be NULL.
|
||||||
|
image_handle (*clone_image)(backend_t *base, image_handle image,
|
||||||
|
const region_t *reg_visible) attr_nonnull_all;
|
||||||
|
|
||||||
/// Create a blur context that can be used to call `blur`
|
/// Create a blur context that can be used to call `blur`
|
||||||
void *(*create_blur_context)(backend_t *base, enum blur_method, void *args);
|
void *(*create_blur_context)(backend_t *base, enum blur_method, void *args);
|
||||||
@@ -368,5 +390,8 @@ struct backend_operations {
|
|||||||
|
|
||||||
extern struct backend_operations *backend_list[];
|
extern struct backend_operations *backend_list[];
|
||||||
|
|
||||||
void paint_all_new(session_t *ps, struct managed_win *const t, bool ignore_damage)
|
/// paint all windows
|
||||||
attr_nonnull(1);
|
///
|
||||||
|
/// Returns if any render command is issued. IOW if nothing on the screen has changed,
|
||||||
|
/// this function will return false.
|
||||||
|
bool paint_all_new(session_t *ps, struct managed_win *t) attr_nonnull(1);
|
||||||
|
|||||||
@@ -19,17 +19,18 @@
|
|||||||
/**
|
/**
|
||||||
* Generate a 1x1 <code>Picture</code> of a particular color.
|
* Generate a 1x1 <code>Picture</code> of a particular color.
|
||||||
*/
|
*/
|
||||||
xcb_render_picture_t solid_picture(xcb_connection_t *c, xcb_drawable_t d, bool argb,
|
xcb_render_picture_t
|
||||||
double a, double r, double g, double b) {
|
solid_picture(struct x_connection *c, bool argb, double a, double r, double g, double b) {
|
||||||
xcb_pixmap_t pixmap;
|
xcb_pixmap_t pixmap;
|
||||||
xcb_render_picture_t picture;
|
xcb_render_picture_t picture;
|
||||||
xcb_render_create_picture_value_list_t pa;
|
xcb_render_create_picture_value_list_t pa;
|
||||||
xcb_render_color_t col;
|
xcb_render_color_t col;
|
||||||
xcb_rectangle_t rect;
|
xcb_rectangle_t rect;
|
||||||
|
|
||||||
pixmap = x_create_pixmap(c, argb ? 32 : 8, d, 1, 1);
|
pixmap = x_create_pixmap(c, argb ? 32 : 8, 1, 1);
|
||||||
if (!pixmap)
|
if (!pixmap) {
|
||||||
return XCB_NONE;
|
return XCB_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
pa.repeat = 1;
|
pa.repeat = 1;
|
||||||
picture = x_create_picture_with_standard_and_pixmap(
|
picture = x_create_picture_with_standard_and_pixmap(
|
||||||
@@ -37,7 +38,7 @@ xcb_render_picture_t solid_picture(xcb_connection_t *c, xcb_drawable_t d, bool a
|
|||||||
XCB_RENDER_CP_REPEAT, &pa);
|
XCB_RENDER_CP_REPEAT, &pa);
|
||||||
|
|
||||||
if (!picture) {
|
if (!picture) {
|
||||||
xcb_free_pixmap(c, pixmap);
|
xcb_free_pixmap(c->c, pixmap);
|
||||||
return XCB_NONE;
|
return XCB_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,14 +52,14 @@ xcb_render_picture_t solid_picture(xcb_connection_t *c, xcb_drawable_t d, bool a
|
|||||||
rect.width = 1;
|
rect.width = 1;
|
||||||
rect.height = 1;
|
rect.height = 1;
|
||||||
|
|
||||||
xcb_render_fill_rectangles(c, XCB_RENDER_PICT_OP_SRC, picture, col, 1, &rect);
|
xcb_render_fill_rectangles(c->c, XCB_RENDER_PICT_OP_SRC, picture, col, 1, &rect);
|
||||||
xcb_free_pixmap(c, pixmap);
|
xcb_free_pixmap(c->c, pixmap);
|
||||||
|
|
||||||
return picture;
|
return picture;
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_image_t *
|
xcb_image_t *make_shadow(struct x_connection *c, const conv *kernel, double opacity,
|
||||||
make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, int height) {
|
int width, int height) {
|
||||||
/*
|
/*
|
||||||
* We classify shadows into 4 kinds of regions
|
* We classify shadows into 4 kinds of regions
|
||||||
* r = shadow radius
|
* r = shadow radius
|
||||||
@@ -84,8 +85,9 @@ make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width,
|
|||||||
assert(d % 2 == 1);
|
assert(d % 2 == 1);
|
||||||
assert(d > 0);
|
assert(d > 0);
|
||||||
|
|
||||||
ximage = xcb_image_create_native(c, to_u16_checked(swidth), to_u16_checked(sheight),
|
ximage =
|
||||||
XCB_IMAGE_FORMAT_Z_PIXMAP, 8, 0, 0, NULL);
|
xcb_image_create_native(c->c, to_u16_checked(swidth), to_u16_checked(sheight),
|
||||||
|
XCB_IMAGE_FORMAT_Z_PIXMAP, 8, 0, 0, NULL);
|
||||||
if (!ximage) {
|
if (!ximage) {
|
||||||
log_error("failed to create an X image");
|
log_error("failed to create an X image");
|
||||||
return 0;
|
return 0;
|
||||||
@@ -193,7 +195,7 @@ make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width,
|
|||||||
/**
|
/**
|
||||||
* Generate shadow <code>Picture</code> for a window.
|
* Generate shadow <code>Picture</code> for a window.
|
||||||
*/
|
*/
|
||||||
bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const int width,
|
bool build_shadow(struct x_connection *c, double opacity, const int width,
|
||||||
const int height, const conv *kernel, xcb_render_picture_t shadow_pixel,
|
const int height, const conv *kernel, xcb_render_picture_t shadow_pixel,
|
||||||
xcb_pixmap_t *pixmap, xcb_render_picture_t *pict) {
|
xcb_pixmap_t *pixmap, xcb_render_picture_t *pict) {
|
||||||
xcb_image_t *shadow_image = NULL;
|
xcb_image_t *shadow_image = NULL;
|
||||||
@@ -207,9 +209,9 @@ bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const i
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadow_pixmap = x_create_pixmap(c, 8, d, shadow_image->width, shadow_image->height);
|
shadow_pixmap = x_create_pixmap(c, 8, shadow_image->width, shadow_image->height);
|
||||||
shadow_pixmap_argb =
|
shadow_pixmap_argb =
|
||||||
x_create_pixmap(c, 32, d, shadow_image->width, shadow_image->height);
|
x_create_pixmap(c, 32, shadow_image->width, shadow_image->height);
|
||||||
|
|
||||||
if (!shadow_pixmap || !shadow_pixmap_argb) {
|
if (!shadow_pixmap || !shadow_pixmap_argb) {
|
||||||
log_error("Failed to create shadow pixmaps");
|
log_error("Failed to create shadow pixmaps");
|
||||||
@@ -225,11 +227,11 @@ bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const i
|
|||||||
}
|
}
|
||||||
|
|
||||||
gc = x_new_id(c);
|
gc = x_new_id(c);
|
||||||
xcb_create_gc(c, gc, shadow_pixmap, 0, NULL);
|
xcb_create_gc(c->c, gc, shadow_pixmap, 0, NULL);
|
||||||
|
|
||||||
// We need to make room for protocol metadata in the request. The metadata should
|
// We need to make room for protocol metadata in the request. The metadata should
|
||||||
// be 24 bytes plus padding, let's be generous and give it 1kb
|
// be 24 bytes plus padding, let's be generous and give it 1kb
|
||||||
auto maximum_image_size = xcb_get_maximum_request_length(c) * 4 - 1024;
|
auto maximum_image_size = xcb_get_maximum_request_length(c->c) * 4 - 1024;
|
||||||
auto maximum_row =
|
auto maximum_row =
|
||||||
to_u16_checked(clamp(maximum_image_size / shadow_image->stride, 0, UINT16_MAX));
|
to_u16_checked(clamp(maximum_image_size / shadow_image->stride, 0, UINT16_MAX));
|
||||||
if (maximum_row <= 0) {
|
if (maximum_row <= 0) {
|
||||||
@@ -248,23 +250,23 @@ bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const i
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t offset = row * shadow_image->stride / sizeof(*shadow_image->data);
|
uint32_t offset = row * shadow_image->stride / sizeof(*shadow_image->data);
|
||||||
xcb_put_image(c, (uint8_t)shadow_image->format, shadow_pixmap, gc,
|
xcb_put_image(c->c, (uint8_t)shadow_image->format, shadow_pixmap, gc,
|
||||||
shadow_image->width, batch_height, 0, to_i16_checked(row),
|
shadow_image->width, batch_height, 0, to_i16_checked(row),
|
||||||
0, shadow_image->depth, shadow_image->stride * batch_height,
|
0, shadow_image->depth, shadow_image->stride * batch_height,
|
||||||
shadow_image->data + offset);
|
shadow_image->data + offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, shadow_pixel, shadow_picture,
|
xcb_render_composite(c->c, XCB_RENDER_PICT_OP_SRC, shadow_pixel, shadow_picture,
|
||||||
shadow_picture_argb, 0, 0, 0, 0, 0, 0, shadow_image->width,
|
shadow_picture_argb, 0, 0, 0, 0, 0, 0, shadow_image->width,
|
||||||
shadow_image->height);
|
shadow_image->height);
|
||||||
|
|
||||||
*pixmap = shadow_pixmap_argb;
|
*pixmap = shadow_pixmap_argb;
|
||||||
*pict = shadow_picture_argb;
|
*pict = shadow_picture_argb;
|
||||||
|
|
||||||
xcb_free_gc(c, gc);
|
xcb_free_gc(c->c, gc);
|
||||||
xcb_image_destroy(shadow_image);
|
xcb_image_destroy(shadow_image);
|
||||||
xcb_free_pixmap(c, shadow_pixmap);
|
xcb_free_pixmap(c->c, shadow_pixmap);
|
||||||
xcb_render_free_picture(c, shadow_picture);
|
x_free_picture(c, shadow_picture);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -273,55 +275,57 @@ shadow_picture_err:
|
|||||||
xcb_image_destroy(shadow_image);
|
xcb_image_destroy(shadow_image);
|
||||||
}
|
}
|
||||||
if (shadow_pixmap) {
|
if (shadow_pixmap) {
|
||||||
xcb_free_pixmap(c, shadow_pixmap);
|
xcb_free_pixmap(c->c, shadow_pixmap);
|
||||||
}
|
}
|
||||||
if (shadow_pixmap_argb) {
|
if (shadow_pixmap_argb) {
|
||||||
xcb_free_pixmap(c, shadow_pixmap_argb);
|
xcb_free_pixmap(c->c, shadow_pixmap_argb);
|
||||||
}
|
}
|
||||||
if (shadow_picture) {
|
if (shadow_picture) {
|
||||||
xcb_render_free_picture(c, shadow_picture);
|
x_free_picture(c, shadow_picture);
|
||||||
}
|
}
|
||||||
if (shadow_picture_argb) {
|
if (shadow_picture_argb) {
|
||||||
xcb_render_free_picture(c, shadow_picture_argb);
|
x_free_picture(c, shadow_picture_argb);
|
||||||
}
|
}
|
||||||
if (gc) {
|
if (gc) {
|
||||||
xcb_free_gc(c, gc);
|
xcb_free_gc(c->c, gc);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *default_backend_render_shadow(backend_t *backend_data, int width, int height,
|
image_handle default_render_shadow(backend_t *backend_data, int width, int height,
|
||||||
struct backend_shadow_context *sctx, struct color color) {
|
struct backend_shadow_context *sctx, struct color color) {
|
||||||
const conv *kernel = (void *)sctx;
|
const conv *kernel = (void *)sctx;
|
||||||
xcb_pixmap_t shadow_pixel = solid_picture(backend_data->c, backend_data->root, true,
|
xcb_render_picture_t shadow_pixel =
|
||||||
1, color.red, color.green, color.blue),
|
solid_picture(backend_data->c, true, 1, color.red, color.green, color.blue);
|
||||||
shadow = XCB_NONE;
|
xcb_pixmap_t shadow = XCB_NONE;
|
||||||
xcb_render_picture_t pict = XCB_NONE;
|
xcb_render_picture_t pict = XCB_NONE;
|
||||||
|
|
||||||
if (!build_shadow(backend_data->c, backend_data->root, color.alpha, width, height,
|
if (!build_shadow(backend_data->c, color.alpha, width, height, kernel,
|
||||||
kernel, shadow_pixel, &shadow, &pict)) {
|
shadow_pixel, &shadow, &pict)) {
|
||||||
|
x_free_picture(backend_data->c, shadow_pixel);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto visual = x_get_visual_for_standard(backend_data->c, XCB_PICT_STANDARD_ARGB_32);
|
auto visual = x_get_visual_for_standard(backend_data->c, XCB_PICT_STANDARD_ARGB_32);
|
||||||
void *ret = backend_data->ops->bind_pixmap(
|
auto ret = backend_data->ops->bind_pixmap(
|
||||||
backend_data, shadow, x_get_visual_info(backend_data->c, visual), true);
|
backend_data, shadow, x_get_visual_info(backend_data->c, visual), true);
|
||||||
xcb_render_free_picture(backend_data->c, pict);
|
x_free_picture(backend_data->c, pict);
|
||||||
|
x_free_picture(backend_data->c, shadow_pixel);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implement render_shadow with shadow_from_mask
|
/// Implement render_shadow with shadow_from_mask
|
||||||
void *
|
image_handle
|
||||||
backend_render_shadow_from_mask(backend_t *backend_data, int width, int height,
|
backend_render_shadow_from_mask(backend_t *backend_data, int width, int height,
|
||||||
struct backend_shadow_context *sctx, struct color color) {
|
struct backend_shadow_context *sctx, struct color color) {
|
||||||
region_t reg;
|
region_t reg;
|
||||||
pixman_region32_init_rect(®, 0, 0, (unsigned int)width, (unsigned int)height);
|
pixman_region32_init_rect(®, 0, 0, (unsigned int)width, (unsigned int)height);
|
||||||
void *mask = backend_data->ops->make_mask(
|
auto mask = backend_data->ops->make_mask(
|
||||||
backend_data, (geometry_t){.width = width, .height = height}, ®);
|
backend_data, (geometry_t){.width = width, .height = height}, ®);
|
||||||
pixman_region32_fini(®);
|
pixman_region32_fini(®);
|
||||||
|
|
||||||
void *shadow = backend_data->ops->shadow_from_mask(backend_data, mask, sctx, color);
|
auto shadow = backend_data->ops->shadow_from_mask(backend_data, mask, sctx, color);
|
||||||
backend_data->ops->release_image(backend_data, mask);
|
backend_data->ops->release_image(backend_data, mask);
|
||||||
return shadow;
|
return shadow;
|
||||||
}
|
}
|
||||||
@@ -401,31 +405,31 @@ struct dual_kawase_params *generate_dual_kawase_params(void *args) {
|
|||||||
int min_radius; /// Approximate gauss-blur with at least this
|
int min_radius; /// Approximate gauss-blur with at least this
|
||||||
/// radius and std-deviation
|
/// radius and std-deviation
|
||||||
} strength_levels[20] = {
|
} strength_levels[20] = {
|
||||||
{.iterations = 1, .offset = 1.25f, .min_radius = 1}, // LVL 1
|
{.iterations = 1, .offset = 1.25F, .min_radius = 1}, // LVL 1
|
||||||
{.iterations = 1, .offset = 2.25f, .min_radius = 6}, // LVL 2
|
{.iterations = 1, .offset = 2.25F, .min_radius = 6}, // LVL 2
|
||||||
{.iterations = 2, .offset = 2.00f, .min_radius = 11}, // LVL 3
|
{.iterations = 2, .offset = 2.00F, .min_radius = 11}, // LVL 3
|
||||||
{.iterations = 2, .offset = 3.00f, .min_radius = 17}, // LVL 4
|
{.iterations = 2, .offset = 3.00F, .min_radius = 17}, // LVL 4
|
||||||
{.iterations = 2, .offset = 4.25f, .min_radius = 24}, // LVL 5
|
{.iterations = 2, .offset = 4.25F, .min_radius = 24}, // LVL 5
|
||||||
{.iterations = 3, .offset = 2.50f, .min_radius = 32}, // LVL 6
|
{.iterations = 3, .offset = 2.50F, .min_radius = 32}, // LVL 6
|
||||||
{.iterations = 3, .offset = 3.25f, .min_radius = 40}, // LVL 7
|
{.iterations = 3, .offset = 3.25F, .min_radius = 40}, // LVL 7
|
||||||
{.iterations = 3, .offset = 4.25f, .min_radius = 51}, // LVL 8
|
{.iterations = 3, .offset = 4.25F, .min_radius = 51}, // LVL 8
|
||||||
{.iterations = 3, .offset = 5.50f, .min_radius = 67}, // LVL 9
|
{.iterations = 3, .offset = 5.50F, .min_radius = 67}, // LVL 9
|
||||||
{.iterations = 4, .offset = 3.25f, .min_radius = 83}, // LVL 10
|
{.iterations = 4, .offset = 3.25F, .min_radius = 83}, // LVL 10
|
||||||
{.iterations = 4, .offset = 4.00f, .min_radius = 101}, // LVL 11
|
{.iterations = 4, .offset = 4.00F, .min_radius = 101}, // LVL 11
|
||||||
{.iterations = 4, .offset = 5.00f, .min_radius = 123}, // LVL 12
|
{.iterations = 4, .offset = 5.00F, .min_radius = 123}, // LVL 12
|
||||||
{.iterations = 4, .offset = 6.00f, .min_radius = 148}, // LVL 13
|
{.iterations = 4, .offset = 6.00F, .min_radius = 148}, // LVL 13
|
||||||
{.iterations = 4, .offset = 7.25f, .min_radius = 178}, // LVL 14
|
{.iterations = 4, .offset = 7.25F, .min_radius = 178}, // LVL 14
|
||||||
{.iterations = 4, .offset = 8.25f, .min_radius = 208}, // LVL 15
|
{.iterations = 4, .offset = 8.25F, .min_radius = 208}, // LVL 15
|
||||||
{.iterations = 5, .offset = 4.50f, .min_radius = 236}, // LVL 16
|
{.iterations = 5, .offset = 4.50F, .min_radius = 236}, // LVL 16
|
||||||
{.iterations = 5, .offset = 5.25f, .min_radius = 269}, // LVL 17
|
{.iterations = 5, .offset = 5.25F, .min_radius = 269}, // LVL 17
|
||||||
{.iterations = 5, .offset = 6.25f, .min_radius = 309}, // LVL 18
|
{.iterations = 5, .offset = 6.25F, .min_radius = 309}, // LVL 18
|
||||||
{.iterations = 5, .offset = 7.25f, .min_radius = 357}, // LVL 19
|
{.iterations = 5, .offset = 7.25F, .min_radius = 357}, // LVL 19
|
||||||
{.iterations = 5, .offset = 8.50f, .min_radius = 417}, // LVL 20
|
{.iterations = 5, .offset = 8.50F, .min_radius = 417}, // LVL 20
|
||||||
};
|
};
|
||||||
|
|
||||||
auto params = ccalloc(1, struct dual_kawase_params);
|
auto params = ccalloc(1, struct dual_kawase_params);
|
||||||
params->iterations = 0;
|
params->iterations = 0;
|
||||||
params->offset = 1.0f;
|
params->offset = 1.0F;
|
||||||
|
|
||||||
if (blur_args->strength <= 0 && blur_args->size) {
|
if (blur_args->strength <= 0 && blur_args->size) {
|
||||||
// find highest level that approximates blur-strength with the selected
|
// find highest level that approximates blur-strength with the selected
|
||||||
@@ -449,22 +453,22 @@ struct dual_kawase_params *generate_dual_kawase_params(void *args) {
|
|||||||
// - Smallest texture dimensions are halved `iterations`-times
|
// - Smallest texture dimensions are halved `iterations`-times
|
||||||
// - Upsample needs pixels two-times `offset` away from the border
|
// - Upsample needs pixels two-times `offset` away from the border
|
||||||
// - Plus one for interpolation differences
|
// - Plus one for interpolation differences
|
||||||
params->expand = (1 << params->iterations) * 2 * (int)ceil(params->offset) + 1;
|
params->expand = (1 << params->iterations) * 2 * (int)ceilf(params->offset) + 1;
|
||||||
|
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *default_clone_image(backend_t *base attr_unused, const void *image_data,
|
image_handle default_clone_image(backend_t *base attr_unused, image_handle image,
|
||||||
const region_t *reg_visible attr_unused) {
|
const region_t *reg_visible attr_unused) {
|
||||||
auto new_img = ccalloc(1, struct backend_image);
|
auto new_img = ccalloc(1, struct backend_image);
|
||||||
*new_img = *(struct backend_image *)image_data;
|
*new_img = *(struct backend_image *)image;
|
||||||
new_img->inner->refcount++;
|
new_img->inner->refcount++;
|
||||||
return new_img;
|
return (image_handle)new_img;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool default_set_image_property(backend_t *base attr_unused, enum image_properties op,
|
bool default_set_image_property(backend_t *base attr_unused, enum image_properties op,
|
||||||
void *image_data, void *arg) {
|
image_handle image, void *arg) {
|
||||||
struct backend_image *tex = image_data;
|
auto tex = (struct backend_image *)image;
|
||||||
int *iargs = arg;
|
int *iargs = arg;
|
||||||
bool *bargs = arg;
|
bool *bargs = arg;
|
||||||
double *dargs = arg;
|
double *dargs = arg;
|
||||||
@@ -486,8 +490,8 @@ bool default_set_image_property(backend_t *base attr_unused, enum image_properti
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool default_is_image_transparent(backend_t *base attr_unused, void *image_data) {
|
bool default_is_image_transparent(backend_t *base attr_unused, image_handle image) {
|
||||||
struct backend_image *img = image_data;
|
auto img = (struct backend_image *)image;
|
||||||
return img->opacity < 1 || img->inner->has_alpha;
|
return img->opacity < 1 || img->inner->has_alpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,9 +508,8 @@ struct backend_image *default_new_backend_image(int w, int h) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void init_backend_base(struct backend_base *base, session_t *ps) {
|
void init_backend_base(struct backend_base *base, session_t *ps) {
|
||||||
base->c = ps->c;
|
base->c = &ps->c;
|
||||||
base->loop = ps->loop;
|
base->loop = ps->loop;
|
||||||
base->root = ps->root;
|
|
||||||
base->busy = false;
|
base->busy = false;
|
||||||
base->ops = NULL;
|
base->ops = NULL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,29 +44,21 @@ struct backend_image {
|
|||||||
int border_width;
|
int border_width;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool build_shadow(xcb_connection_t *, xcb_drawable_t, double opacity, int width,
|
bool build_shadow(struct x_connection *, double opacity, int width, int height,
|
||||||
int height, const conv *kernel, xcb_render_picture_t shadow_pixel,
|
const conv *kernel, xcb_render_picture_t shadow_pixel,
|
||||||
xcb_pixmap_t *pixmap, xcb_render_picture_t *pict);
|
xcb_pixmap_t *pixmap, xcb_render_picture_t *pict);
|
||||||
|
|
||||||
xcb_render_picture_t solid_picture(xcb_connection_t *, xcb_drawable_t, bool argb,
|
xcb_render_picture_t
|
||||||
double a, double r, double g, double b);
|
solid_picture(struct x_connection *, bool argb, double a, double r, double g, double b);
|
||||||
|
|
||||||
xcb_image_t *
|
xcb_image_t *make_shadow(struct x_connection *c, const conv *kernel, double opacity,
|
||||||
make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, int height);
|
int width, int height);
|
||||||
|
|
||||||
/// The default implementation of `is_win_transparent`, it simply looks at win::mode. So
|
image_handle default_render_shadow(backend_t *backend_data, int width, int height,
|
||||||
/// this is not suitable for backends that alter the content of windows
|
struct backend_shadow_context *sctx, struct color color);
|
||||||
bool default_is_win_transparent(void *, win *, void *);
|
|
||||||
|
|
||||||
/// The default implementation of `is_frame_transparent`, it uses win::frame_opacity. Same
|
|
||||||
/// caveat as `default_is_win_transparent` applies.
|
|
||||||
bool default_is_frame_transparent(void *, win *, void *);
|
|
||||||
|
|
||||||
void *default_backend_render_shadow(backend_t *backend_data, int width, int height,
|
|
||||||
struct backend_shadow_context *sctx, struct color color);
|
|
||||||
|
|
||||||
/// Implement `render_shadow` with `shadow_from_mask`.
|
/// Implement `render_shadow` with `shadow_from_mask`.
|
||||||
void *
|
image_handle
|
||||||
backend_render_shadow_from_mask(backend_t *backend_data, int width, int height,
|
backend_render_shadow_from_mask(backend_t *backend_data, int width, int height,
|
||||||
struct backend_shadow_context *sctx, struct color color);
|
struct backend_shadow_context *sctx, struct color color);
|
||||||
struct backend_shadow_context *
|
struct backend_shadow_context *
|
||||||
@@ -80,8 +72,8 @@ void init_backend_base(struct backend_base *base, session_t *ps);
|
|||||||
struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count);
|
struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count);
|
||||||
struct dual_kawase_params *generate_dual_kawase_params(void *args);
|
struct dual_kawase_params *generate_dual_kawase_params(void *args);
|
||||||
|
|
||||||
void *default_clone_image(backend_t *base, const void *image_data, const region_t *reg);
|
image_handle default_clone_image(backend_t *base, image_handle image, const region_t *reg);
|
||||||
bool default_is_image_transparent(backend_t *base attr_unused, void *image_data);
|
bool default_is_image_transparent(backend_t *base attr_unused, image_handle image);
|
||||||
bool default_set_image_property(backend_t *base attr_unused, enum image_properties op,
|
bool default_set_image_property(backend_t *base attr_unused, enum image_properties op,
|
||||||
void *image_data, void *arg);
|
image_handle image, void *arg);
|
||||||
struct backend_image *default_new_backend_image(int w, int h);
|
struct backend_image *default_new_backend_image(int w, int h);
|
||||||
|
|||||||
@@ -15,12 +15,20 @@
|
|||||||
/// Apply driver specified global workarounds. It's safe to call this multiple times.
|
/// Apply driver specified global workarounds. It's safe to call this multiple times.
|
||||||
void apply_driver_workarounds(struct session *ps, enum driver driver) {
|
void apply_driver_workarounds(struct session *ps, enum driver driver) {
|
||||||
if (driver & DRIVER_NVIDIA) {
|
if (driver & DRIVER_NVIDIA) {
|
||||||
// setenv("__GL_YIELD", "usleep", true);
|
|
||||||
setenv("__GL_MaxFramesAllowed", "1", true);
|
|
||||||
ps->o.xrender_sync_fence = true;
|
ps->o.xrender_sync_fence = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver attr_unused) {
|
||||||
|
enum vblank_scheduler_type type = VBLANK_SCHEDULER_PRESENT;
|
||||||
|
#ifdef CONFIG_OPENGL
|
||||||
|
if (driver & DRIVER_NVIDIA) {
|
||||||
|
type = VBLANK_SCHEDULER_SGI_VIDEO_SYNC;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) {
|
enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) {
|
||||||
enum driver ret = 0;
|
enum driver ret = 0;
|
||||||
// First we try doing backend agnostic detection using RANDR
|
// First we try doing backend agnostic detection using RANDR
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
struct session;
|
struct session;
|
||||||
@@ -41,13 +42,15 @@ enum driver detect_driver(xcb_connection_t *, struct backend_base *, xcb_window_
|
|||||||
|
|
||||||
/// Apply driver specified global workarounds. It's safe to call this multiple times.
|
/// Apply driver specified global workarounds. It's safe to call this multiple times.
|
||||||
void apply_driver_workarounds(struct session *ps, enum driver);
|
void apply_driver_workarounds(struct session *ps, enum driver);
|
||||||
|
/// Choose a vblank scheduler based on the driver.
|
||||||
|
enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver);
|
||||||
|
|
||||||
// Print driver names to stdout, for diagnostics
|
// Print driver names to stdout, for diagnostics
|
||||||
static inline void print_drivers(enum driver drivers) {
|
static inline void print_drivers(enum driver drivers) {
|
||||||
const char *seen_drivers[ARR_SIZE(driver_names)];
|
const char *seen_drivers[ARR_SIZE(driver_names)];
|
||||||
int driver_count = 0;
|
int driver_count = 0;
|
||||||
for (size_t i = 0; i < ARR_SIZE(driver_names); i++) {
|
for (size_t i = 0; i < ARR_SIZE(driver_names); i++) {
|
||||||
if (drivers & (1ul << i)) {
|
if (drivers & (1UL << i)) {
|
||||||
seen_drivers[driver_count++] = driver_names[i];
|
seen_drivers[driver_count++] = driver_names[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ struct dummy_image {
|
|||||||
xcb_pixmap_t pixmap;
|
xcb_pixmap_t pixmap;
|
||||||
bool transparent;
|
bool transparent;
|
||||||
int *refcount;
|
int *refcount;
|
||||||
|
bool owned;
|
||||||
UT_hash_handle hh;
|
UT_hash_handle hh;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,11 +28,10 @@ struct dummy_data {
|
|||||||
struct backend_image mask;
|
struct backend_image mask;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct backend_base *dummy_init(struct session *ps attr_unused) {
|
struct backend_base *dummy_init(session_t *ps attr_unused, xcb_window_t target attr_unused) {
|
||||||
auto ret = (struct backend_base *)ccalloc(1, struct dummy_data);
|
auto ret = (struct backend_base *)ccalloc(1, struct dummy_data);
|
||||||
ret->c = ps->c;
|
ret->c = &ps->c;
|
||||||
ret->loop = ps->loop;
|
ret->loop = ps->loop;
|
||||||
ret->root = ps->root;
|
|
||||||
ret->busy = false;
|
ret->busy = false;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -42,13 +42,17 @@ void dummy_deinit(struct backend_base *data) {
|
|||||||
log_warn("Backend image for pixmap %#010x is not freed", img->pixmap);
|
log_warn("Backend image for pixmap %#010x is not freed", img->pixmap);
|
||||||
HASH_DEL(dummy->images, img);
|
HASH_DEL(dummy->images, img);
|
||||||
free(img->refcount);
|
free(img->refcount);
|
||||||
|
if (img->owned) {
|
||||||
|
xcb_free_pixmap(data->c->c, img->pixmap);
|
||||||
|
}
|
||||||
free(img);
|
free(img);
|
||||||
}
|
}
|
||||||
free(dummy);
|
free(dummy);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dummy_check_image(struct backend_base *base, const struct dummy_image *img) {
|
static void dummy_check_image(struct backend_base *base, image_handle image) {
|
||||||
auto dummy = (struct dummy_data *)base;
|
auto dummy = (struct dummy_data *)base;
|
||||||
|
auto img = (struct dummy_image *)image;
|
||||||
if (img == (struct dummy_image *)&dummy->mask) {
|
if (img == (struct dummy_image *)&dummy->mask) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -61,13 +65,13 @@ static void dummy_check_image(struct backend_base *base, const struct dummy_imag
|
|||||||
assert(*tmp->refcount > 0);
|
assert(*tmp->refcount > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dummy_compose(struct backend_base *base, void *image, coord_t dst attr_unused,
|
void dummy_compose(struct backend_base *base, image_handle image, coord_t dst attr_unused,
|
||||||
void *mask attr_unused, coord_t mask_dst attr_unused,
|
image_handle mask attr_unused, coord_t mask_dst attr_unused,
|
||||||
const region_t *reg_paint attr_unused,
|
const region_t *reg_paint attr_unused,
|
||||||
const region_t *reg_visible attr_unused, bool lerp attr_unused) {
|
const region_t *reg_visible attr_unused, bool lerp attr_unused) {
|
||||||
auto dummy attr_unused = (struct dummy_data *)base;
|
auto dummy attr_unused = (struct dummy_data *)base;
|
||||||
dummy_check_image(base, image);
|
dummy_check_image(base, image);
|
||||||
assert(mask == NULL || mask == &dummy->mask);
|
assert(mask == NULL || (struct backend_image *)mask == &dummy->mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dummy_fill(struct backend_base *backend_data attr_unused, struct color c attr_unused,
|
void dummy_fill(struct backend_base *backend_data attr_unused, struct color c attr_unused,
|
||||||
@@ -75,20 +79,20 @@ void dummy_fill(struct backend_base *backend_data attr_unused, struct color c at
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool dummy_blur(struct backend_base *backend_data attr_unused, double opacity attr_unused,
|
bool dummy_blur(struct backend_base *backend_data attr_unused, double opacity attr_unused,
|
||||||
void *blur_ctx attr_unused, void *mask attr_unused,
|
void *blur_ctx attr_unused, image_handle mask attr_unused,
|
||||||
coord_t mask_dst attr_unused, const region_t *reg_blur attr_unused,
|
coord_t mask_dst attr_unused, const region_t *reg_blur attr_unused,
|
||||||
const region_t *reg_visible attr_unused) {
|
const region_t *reg_visible attr_unused) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap,
|
image_handle dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap,
|
||||||
struct xvisual_info fmt, bool owned attr_unused) {
|
struct xvisual_info fmt, bool owned) {
|
||||||
auto dummy = (struct dummy_data *)base;
|
auto dummy = (struct dummy_data *)base;
|
||||||
struct dummy_image *img = NULL;
|
struct dummy_image *img = NULL;
|
||||||
HASH_FIND_INT(dummy->images, &pixmap, img);
|
HASH_FIND_INT(dummy->images, &pixmap, img);
|
||||||
if (img) {
|
if (img) {
|
||||||
(*img->refcount)++;
|
(*img->refcount)++;
|
||||||
return img;
|
return (image_handle)img;
|
||||||
}
|
}
|
||||||
|
|
||||||
img = ccalloc(1, struct dummy_image);
|
img = ccalloc(1, struct dummy_image);
|
||||||
@@ -96,14 +100,15 @@ void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap,
|
|||||||
img->transparent = fmt.alpha_size != 0;
|
img->transparent = fmt.alpha_size != 0;
|
||||||
img->refcount = ccalloc(1, int);
|
img->refcount = ccalloc(1, int);
|
||||||
*img->refcount = 1;
|
*img->refcount = 1;
|
||||||
|
img->owned = owned;
|
||||||
|
|
||||||
HASH_ADD_INT(dummy->images, pixmap, img);
|
HASH_ADD_INT(dummy->images, pixmap, img);
|
||||||
return (void *)img;
|
return (image_handle)img;
|
||||||
}
|
}
|
||||||
|
|
||||||
void dummy_release_image(backend_t *base, void *image) {
|
void dummy_release_image(backend_t *base, image_handle image) {
|
||||||
auto dummy = (struct dummy_data *)base;
|
auto dummy = (struct dummy_data *)base;
|
||||||
if (image == &dummy->mask) {
|
if ((struct backend_image *)image == &dummy->mask) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto img = (struct dummy_image *)image;
|
auto img = (struct dummy_image *)image;
|
||||||
@@ -112,14 +117,16 @@ void dummy_release_image(backend_t *base, void *image) {
|
|||||||
if (*img->refcount == 0) {
|
if (*img->refcount == 0) {
|
||||||
HASH_DEL(dummy->images, img);
|
HASH_DEL(dummy->images, img);
|
||||||
free(img->refcount);
|
free(img->refcount);
|
||||||
|
if (img->owned) {
|
||||||
|
xcb_free_pixmap(base->c->c, img->pixmap);
|
||||||
|
}
|
||||||
free(img);
|
free(img);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool dummy_is_image_transparent(struct backend_base *base, void *image) {
|
bool dummy_is_image_transparent(struct backend_base *base, image_handle image) {
|
||||||
auto img = (struct dummy_image *)image;
|
dummy_check_image(base, image);
|
||||||
dummy_check_image(base, img);
|
return ((struct dummy_image *)image)->transparent;
|
||||||
return img->transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int dummy_buffer_age(struct backend_base *base attr_unused) {
|
int dummy_buffer_age(struct backend_base *base attr_unused) {
|
||||||
@@ -127,29 +134,31 @@ int dummy_buffer_age(struct backend_base *base attr_unused) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool dummy_image_op(struct backend_base *base, enum image_operations op attr_unused,
|
bool dummy_image_op(struct backend_base *base, enum image_operations op attr_unused,
|
||||||
void *image, const region_t *reg_op attr_unused,
|
image_handle image, const region_t *reg_op attr_unused,
|
||||||
const region_t *reg_visible attr_unused, void *args attr_unused) {
|
const region_t *reg_visible attr_unused, void *args attr_unused) {
|
||||||
dummy_check_image(base, image);
|
dummy_check_image(base, image);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *dummy_make_mask(struct backend_base *base, geometry_t size attr_unused,
|
image_handle dummy_make_mask(struct backend_base *base, geometry_t size attr_unused,
|
||||||
const region_t *reg attr_unused) {
|
const region_t *reg attr_unused) {
|
||||||
return &(((struct dummy_data *)base)->mask);
|
auto dummy = (struct dummy_data *)base;
|
||||||
|
auto mask = &dummy->mask;
|
||||||
|
return (image_handle)mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool dummy_set_image_property(struct backend_base *base, enum image_properties prop attr_unused,
|
bool dummy_set_image_property(struct backend_base *base, enum image_properties prop attr_unused,
|
||||||
void *image, void *arg attr_unused) {
|
image_handle image, void *arg attr_unused) {
|
||||||
dummy_check_image(base, image);
|
dummy_check_image(base, image);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *dummy_clone_image(struct backend_base *base, const void *image,
|
image_handle dummy_clone_image(struct backend_base *base, image_handle image,
|
||||||
const region_t *reg_visible attr_unused) {
|
const region_t *reg_visible attr_unused) {
|
||||||
auto img = (const struct dummy_image *)image;
|
dummy_check_image(base, image);
|
||||||
dummy_check_image(base, img);
|
auto image_impl = (struct dummy_image *)image;
|
||||||
(*img->refcount)++;
|
(*image_impl->refcount)++;
|
||||||
return (void *)img;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *dummy_create_blur_context(struct backend_base *base attr_unused,
|
void *dummy_create_blur_context(struct backend_base *base attr_unused,
|
||||||
@@ -162,7 +171,7 @@ void dummy_destroy_blur_context(struct backend_base *base attr_unused, void *ctx
|
|||||||
}
|
}
|
||||||
|
|
||||||
void dummy_get_blur_size(void *ctx attr_unused, int *width, int *height) {
|
void dummy_get_blur_size(void *ctx attr_unused, int *width, int *height) {
|
||||||
// These numbers are arbitrary, to make sure the reisze_region code path is
|
// These numbers are arbitrary, to make sure the resize_region code path is
|
||||||
// covered.
|
// covered.
|
||||||
*width = 5;
|
*width = 5;
|
||||||
*height = 5;
|
*height = 5;
|
||||||
@@ -177,7 +186,7 @@ struct backend_operations dummy_ops = {
|
|||||||
.bind_pixmap = dummy_bind_pixmap,
|
.bind_pixmap = dummy_bind_pixmap,
|
||||||
.create_shadow_context = default_create_shadow_context,
|
.create_shadow_context = default_create_shadow_context,
|
||||||
.destroy_shadow_context = default_destroy_shadow_context,
|
.destroy_shadow_context = default_destroy_shadow_context,
|
||||||
.render_shadow = default_backend_render_shadow,
|
.render_shadow = default_render_shadow,
|
||||||
.make_mask = dummy_make_mask,
|
.make_mask = dummy_make_mask,
|
||||||
.release_image = dummy_release_image,
|
.release_image = dummy_release_image,
|
||||||
.is_image_transparent = dummy_is_image_transparent,
|
.is_image_transparent = dummy_is_image_transparent,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ struct gl_blur_context {
|
|||||||
struct texture_size {
|
struct texture_size {
|
||||||
int width;
|
int width;
|
||||||
int height;
|
int height;
|
||||||
} * texture_sizes;
|
} *texture_sizes;
|
||||||
|
|
||||||
/// Cached dimensions of the offscreen framebuffer. It's the same size as the
|
/// Cached dimensions of the offscreen framebuffer. It's the same size as the
|
||||||
/// target but is expanded in either direction by resize_width / resize_height.
|
/// target but is expanded in either direction by resize_width / resize_height.
|
||||||
@@ -150,6 +150,9 @@ bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rec
|
|||||||
|
|
||||||
glUniform2f(down_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord);
|
glUniform2f(down_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord);
|
||||||
|
|
||||||
|
glBindVertexArray(vao[1]);
|
||||||
|
int nelems = vao_nelems[1];
|
||||||
|
|
||||||
for (int i = 0; i < iterations; ++i) {
|
for (int i = 0; i < iterations; ++i) {
|
||||||
// Scale output width / height by half in each iteration
|
// Scale output width / height by half in each iteration
|
||||||
scale_factor <<= 1;
|
scale_factor <<= 1;
|
||||||
@@ -174,8 +177,6 @@ bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rec
|
|||||||
assert(bctx->blur_fbos[i]);
|
assert(bctx->blur_fbos[i]);
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, src_texture);
|
glBindTexture(GL_TEXTURE_2D, src_texture);
|
||||||
glBindVertexArray(vao[1]);
|
|
||||||
auto nelems = vao_nelems[1];
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]);
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]);
|
||||||
glDrawBuffer(GL_COLOR_ATTACHMENT0);
|
glDrawBuffer(GL_COLOR_ATTACHMENT0);
|
||||||
|
|
||||||
@@ -194,6 +195,15 @@ bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rec
|
|||||||
|
|
||||||
glUniform2f(up_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord);
|
glUniform2f(up_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord);
|
||||||
|
|
||||||
|
glActiveTexture(GL_TEXTURE1);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, default_mask);
|
||||||
|
|
||||||
|
glUniform1i(up_pass->uniform_mask_tex, 1);
|
||||||
|
glUniform2f(up_pass->uniform_mask_offset, 0.0F, 0.0F);
|
||||||
|
glUniform1i(up_pass->uniform_mask_inverted, 0);
|
||||||
|
glUniform1f(up_pass->uniform_mask_corner_radius, 0.0F);
|
||||||
|
glUniform1f(up_pass->uniform_opacity, 1.0F);
|
||||||
|
|
||||||
for (int i = iterations - 1; i >= 0; --i) {
|
for (int i = iterations - 1; i >= 0; --i) {
|
||||||
// Scale output width / height back by two in each iteration
|
// Scale output width / height back by two in each iteration
|
||||||
scale_factor >>= 1;
|
scale_factor >>= 1;
|
||||||
@@ -206,28 +216,15 @@ bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rec
|
|||||||
int tex_width = src_size.width;
|
int tex_width = src_size.width;
|
||||||
int tex_height = src_size.height;
|
int tex_height = src_size.height;
|
||||||
|
|
||||||
// The number of indices in the selected vertex array
|
|
||||||
GLsizei nelems;
|
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
glBindTexture(GL_TEXTURE_2D, src_texture);
|
glBindTexture(GL_TEXTURE_2D, src_texture);
|
||||||
glActiveTexture(GL_TEXTURE1);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, default_mask);
|
|
||||||
|
|
||||||
glUniform1i(up_pass->uniform_mask_tex, 1);
|
|
||||||
glUniform2f(up_pass->uniform_mask_offset, 0.0F, 0.0F);
|
|
||||||
glUniform1i(up_pass->uniform_mask_inverted, 0);
|
|
||||||
glUniform1f(up_pass->uniform_mask_corner_radius, 0.0F);
|
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
assert(bctx->blur_fbos[i - 1]);
|
assert(bctx->blur_fbos[i - 1]);
|
||||||
|
|
||||||
// not last pass, draw into next framebuffer
|
// not last pass, draw into next framebuffer
|
||||||
glBindVertexArray(vao[1]);
|
|
||||||
nelems = vao_nelems[1];
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]);
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]);
|
||||||
glDrawBuffer(GL_COLOR_ATTACHMENT0);
|
glDrawBuffer(GL_COLOR_ATTACHMENT0);
|
||||||
|
|
||||||
glUniform1f(up_pass->uniform_opacity, (GLfloat)1);
|
|
||||||
} else {
|
} else {
|
||||||
// last pass, draw directly into the back buffer
|
// last pass, draw directly into the back buffer
|
||||||
if (mask) {
|
if (mask) {
|
||||||
@@ -259,10 +256,11 @@ bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rec
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, coord_t mask_dst,
|
bool gl_blur_impl(double opacity, struct gl_blur_context *bctx,
|
||||||
const region_t *reg_blur, const region_t *reg_visible attr_unused,
|
struct backend_image *mask, coord_t mask_dst, const region_t *reg_blur,
|
||||||
GLuint source_texture, geometry_t source_size, GLuint target_fbo,
|
const region_t *reg_visible attr_unused, GLuint source_texture,
|
||||||
GLuint default_mask, bool high_precision) {
|
geometry_t source_size, GLuint target_fbo, GLuint default_mask,
|
||||||
|
bool high_precision) {
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
if (source_size.width != bctx->fb_width || source_size.height != bctx->fb_height) {
|
if (source_size.width != bctx->fb_width || source_size.height != bctx->fb_height) {
|
||||||
@@ -347,9 +345,9 @@ bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, coor
|
|||||||
glBindVertexArray(vao[0]);
|
glBindVertexArray(vao[0]);
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
|
glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
|
||||||
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW);
|
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STREAM_DRAW);
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6,
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6,
|
||||||
indices, GL_STATIC_DRAW);
|
indices, GL_STREAM_DRAW);
|
||||||
glEnableVertexAttribArray(vert_coord_loc);
|
glEnableVertexAttribArray(vert_coord_loc);
|
||||||
glEnableVertexAttribArray(vert_in_texcoord_loc);
|
glEnableVertexAttribArray(vert_in_texcoord_loc);
|
||||||
glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
|
glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
|
||||||
@@ -360,10 +358,10 @@ bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, coor
|
|||||||
glBindBuffer(GL_ARRAY_BUFFER, bo[2]);
|
glBindBuffer(GL_ARRAY_BUFFER, bo[2]);
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[3]);
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[3]);
|
||||||
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord_resized) * nrects_resized * 16,
|
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord_resized) * nrects_resized * 16,
|
||||||
coord_resized, GL_STATIC_DRAW);
|
coord_resized, GL_STREAM_DRAW);
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||||
(long)sizeof(*indices_resized) * nrects_resized * 6, indices_resized,
|
(long)sizeof(*indices_resized) * nrects_resized * 6, indices_resized,
|
||||||
GL_STATIC_DRAW);
|
GL_STREAM_DRAW);
|
||||||
glEnableVertexAttribArray(vert_coord_loc);
|
glEnableVertexAttribArray(vert_coord_loc);
|
||||||
glEnableVertexAttribArray(vert_in_texcoord_loc);
|
glEnableVertexAttribArray(vert_in_texcoord_loc);
|
||||||
glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
|
glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
|
||||||
@@ -403,12 +401,12 @@ bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, coor
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst,
|
bool gl_blur(backend_t *base, double opacity, void *ctx, image_handle mask, coord_t mask_dst,
|
||||||
const region_t *reg_blur, const region_t *reg_visible attr_unused) {
|
const region_t *reg_blur, const region_t *reg_visible attr_unused) {
|
||||||
auto gd = (struct gl_data *)base;
|
auto gd = (struct gl_data *)base;
|
||||||
auto bctx = (struct gl_blur_context *)ctx;
|
auto bctx = (struct gl_blur_context *)ctx;
|
||||||
return gl_blur_impl(opacity, bctx, mask, mask_dst, reg_blur, reg_visible,
|
return gl_blur_impl(opacity, bctx, (struct backend_image *)mask, mask_dst,
|
||||||
gd->back_texture,
|
reg_blur, reg_visible, gd->back_texture,
|
||||||
(geometry_t){.width = gd->width, .height = gd->height},
|
(geometry_t){.width = gd->width, .height = gd->height},
|
||||||
gd->back_fbo, gd->default_mask_texture, gd->dithered_present);
|
gd->back_fbo, gd->default_mask_texture, gd->dithered_present);
|
||||||
}
|
}
|
||||||
@@ -607,9 +605,9 @@ bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection,
|
|||||||
bind_uniform(pass, mask_offset);
|
bind_uniform(pass, mask_offset);
|
||||||
bind_uniform(pass, mask_inverted);
|
bind_uniform(pass, mask_inverted);
|
||||||
bind_uniform(pass, mask_corner_radius);
|
bind_uniform(pass, mask_corner_radius);
|
||||||
log_info("Uniform locations: %d %d %d %d %d", pass->uniform_mask_tex,
|
log_debug("Uniform locations: %d %d %d %d %d", pass->uniform_mask_tex,
|
||||||
pass->uniform_mask_offset, pass->uniform_mask_inverted,
|
pass->uniform_mask_offset, pass->uniform_mask_inverted,
|
||||||
pass->uniform_mask_corner_radius, pass->uniform_opacity);
|
pass->uniform_mask_corner_radius, pass->uniform_opacity);
|
||||||
pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig");
|
pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig");
|
||||||
|
|
||||||
// Setup projection matrix
|
// Setup projection matrix
|
||||||
|
|||||||
@@ -36,26 +36,44 @@ struct egl_data {
|
|||||||
EGLContext ctx;
|
EGLContext ctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
static PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC glEGLImageTargetTexStorage = NULL;
|
const char *eglGetErrorString(EGLint error) {
|
||||||
static PFNEGLCREATEIMAGEKHRPROC eglCreateImageProc = NULL;
|
#define CASE_STR(value) \
|
||||||
static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageProc = NULL;
|
case value: return #value;
|
||||||
static PFNEGLGETPLATFORMDISPLAYPROC eglGetPlatformDisplayProc = NULL;
|
switch (error) {
|
||||||
static PFNEGLCREATEPLATFORMWINDOWSURFACEPROC eglCreatePlatformWindowSurfaceProc = NULL;
|
CASE_STR(EGL_SUCCESS)
|
||||||
|
CASE_STR(EGL_NOT_INITIALIZED)
|
||||||
|
CASE_STR(EGL_BAD_ACCESS)
|
||||||
|
CASE_STR(EGL_BAD_ALLOC)
|
||||||
|
CASE_STR(EGL_BAD_ATTRIBUTE)
|
||||||
|
CASE_STR(EGL_BAD_CONTEXT)
|
||||||
|
CASE_STR(EGL_BAD_CONFIG)
|
||||||
|
CASE_STR(EGL_BAD_CURRENT_SURFACE)
|
||||||
|
CASE_STR(EGL_BAD_DISPLAY)
|
||||||
|
CASE_STR(EGL_BAD_SURFACE)
|
||||||
|
CASE_STR(EGL_BAD_MATCH)
|
||||||
|
CASE_STR(EGL_BAD_PARAMETER)
|
||||||
|
CASE_STR(EGL_BAD_NATIVE_PIXMAP)
|
||||||
|
CASE_STR(EGL_BAD_NATIVE_WINDOW)
|
||||||
|
CASE_STR(EGL_CONTEXT_LOST)
|
||||||
|
default: return "Unknown";
|
||||||
|
}
|
||||||
|
#undef CASE_STR
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Free a glx_texture_t.
|
* Free a gl_texture_t.
|
||||||
*/
|
*/
|
||||||
static void egl_release_image(backend_t *base, struct gl_texture *tex) {
|
static void egl_release_image(backend_t *base, struct gl_texture *tex) {
|
||||||
struct egl_data *gd = (void *)base;
|
struct egl_data *gd = (void *)base;
|
||||||
struct egl_pixmap *p = tex->user_data;
|
struct egl_pixmap *p = tex->user_data;
|
||||||
// Release binding
|
// Release binding
|
||||||
if (p->image != EGL_NO_IMAGE) {
|
if (p->image != EGL_NO_IMAGE) {
|
||||||
eglDestroyImageProc(gd->display, p->image);
|
eglDestroyImage(gd->display, p->image);
|
||||||
p->image = EGL_NO_IMAGE;
|
p->image = EGL_NO_IMAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p->owned) {
|
if (p->owned) {
|
||||||
xcb_free_pixmap(base->c, p->pixmap);
|
xcb_free_pixmap(base->c->c, p->pixmap);
|
||||||
p->pixmap = XCB_NONE;
|
p->pixmap = XCB_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,14 +82,14 @@ static void egl_release_image(backend_t *base, struct gl_texture *tex) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy GLX related resources.
|
* Destroy EGL related resources.
|
||||||
*/
|
*/
|
||||||
void egl_deinit(backend_t *base) {
|
void egl_deinit(backend_t *base) {
|
||||||
struct egl_data *gd = (void *)base;
|
struct egl_data *gd = (void *)base;
|
||||||
|
|
||||||
gl_deinit(&gd->gl);
|
gl_deinit(&gd->gl);
|
||||||
|
|
||||||
// Destroy GLX context
|
// Destroy EGL context
|
||||||
if (gd->ctx != EGL_NO_CONTEXT) {
|
if (gd->ctx != EGL_NO_CONTEXT) {
|
||||||
eglMakeCurrent(gd->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
eglMakeCurrent(gd->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||||
eglDestroyContext(gd->display, gd->ctx);
|
eglDestroyContext(gd->display, gd->ctx);
|
||||||
@@ -106,22 +124,10 @@ static bool egl_set_swap_interval(int interval, EGLDisplay dpy) {
|
|||||||
/**
|
/**
|
||||||
* Initialize OpenGL.
|
* Initialize OpenGL.
|
||||||
*/
|
*/
|
||||||
static backend_t *egl_init(session_t *ps) {
|
static backend_t *egl_init(session_t *ps, xcb_window_t target) {
|
||||||
bool success = false;
|
bool success = false;
|
||||||
struct egl_data *gd = NULL;
|
struct egl_data *gd = NULL;
|
||||||
|
|
||||||
#define get_proc(name, type) \
|
|
||||||
name##Proc = (type)eglGetProcAddress(#name); \
|
|
||||||
if (!name##Proc) { \
|
|
||||||
log_error("Failed to get EGL function " #name); \
|
|
||||||
goto end; \
|
|
||||||
}
|
|
||||||
get_proc(eglCreateImage, PFNEGLCREATEIMAGEKHRPROC);
|
|
||||||
get_proc(eglDestroyImage, PFNEGLDESTROYIMAGEKHRPROC);
|
|
||||||
get_proc(eglGetPlatformDisplay, PFNEGLGETPLATFORMDISPLAYPROC);
|
|
||||||
get_proc(eglCreatePlatformWindowSurface, PFNEGLCREATEPLATFORMWINDOWSURFACEPROC);
|
|
||||||
#undef get_proc
|
|
||||||
|
|
||||||
// Check if we have the X11 platform
|
// Check if we have the X11 platform
|
||||||
const char *exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
|
const char *exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
|
||||||
if (strstr(exts, "EGL_EXT_platform_x11") == NULL) {
|
if (strstr(exts, "EGL_EXT_platform_x11") == NULL) {
|
||||||
@@ -130,12 +136,12 @@ static backend_t *egl_init(session_t *ps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gd = ccalloc(1, struct egl_data);
|
gd = ccalloc(1, struct egl_data);
|
||||||
gd->display = eglGetPlatformDisplayProc(EGL_PLATFORM_X11_EXT, ps->dpy,
|
gd->display = eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT, ps->c.dpy,
|
||||||
(EGLAttrib[]){
|
(EGLint[]){
|
||||||
EGL_PLATFORM_X11_SCREEN_EXT,
|
EGL_PLATFORM_X11_SCREEN_EXT,
|
||||||
ps->scr,
|
ps->c.screen,
|
||||||
EGL_NONE,
|
EGL_NONE,
|
||||||
});
|
});
|
||||||
if (gd->display == EGL_NO_DISPLAY) {
|
if (gd->display == EGL_NO_DISPLAY) {
|
||||||
log_error("Failed to get EGL display.");
|
log_error("Failed to get EGL display.");
|
||||||
goto end;
|
goto end;
|
||||||
@@ -166,7 +172,7 @@ static backend_t *egl_init(session_t *ps) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto visual_info = x_get_visual_info(ps->c, ps->vis);
|
auto visual_info = x_get_visual_info(&ps->c, ps->c.screen_info->root_visual);
|
||||||
EGLConfig config = NULL;
|
EGLConfig config = NULL;
|
||||||
int nconfigs = 1;
|
int nconfigs = 1;
|
||||||
// clang-format off
|
// clang-format off
|
||||||
@@ -187,8 +193,8 @@ static backend_t *egl_init(session_t *ps) {
|
|||||||
}
|
}
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
gd->target_win = eglCreatePlatformWindowSurfaceProc(
|
gd->target_win =
|
||||||
gd->display, config, (xcb_window_t[]){session_get_target_window(ps)}, NULL);
|
eglCreatePlatformWindowSurfaceEXT(gd->display, config, &target, NULL);
|
||||||
if (gd->target_win == EGL_NO_SURFACE) {
|
if (gd->target_win == EGL_NO_SURFACE) {
|
||||||
log_error("Failed to create EGL surface.");
|
log_error("Failed to create EGL surface.");
|
||||||
goto end;
|
goto end;
|
||||||
@@ -201,12 +207,12 @@ static backend_t *egl_init(session_t *ps) {
|
|||||||
|
|
||||||
gd->ctx = eglCreateContext(gd->display, config, NULL, NULL);
|
gd->ctx = eglCreateContext(gd->display, config, NULL, NULL);
|
||||||
if (gd->ctx == EGL_NO_CONTEXT) {
|
if (gd->ctx == EGL_NO_CONTEXT) {
|
||||||
log_error("Failed to get GLX context.");
|
log_error("Failed to get EGL context.");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!eglMakeCurrent(gd->display, gd->target_win, gd->target_win, gd->ctx)) {
|
if (!eglMakeCurrent(gd->display, gd->target_win, gd->target_win, gd->ctx)) {
|
||||||
log_error("Failed to attach GLX context.");
|
log_error("Failed to attach EGL context.");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,14 +225,6 @@ static backend_t *egl_init(session_t *ps) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
glEGLImageTargetTexStorage =
|
|
||||||
(PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC)eglGetProcAddress("glEGLImageTargetTexS"
|
|
||||||
"torageEXT");
|
|
||||||
if (glEGLImageTargetTexStorage == NULL) {
|
|
||||||
log_error("Failed to get glEGLImageTargetTexStorageEXT.");
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
gd->gl.decouple_texture_user_data = egl_decouple_user_data;
|
gd->gl.decouple_texture_user_data = egl_decouple_user_data;
|
||||||
gd->gl.release_user_data = egl_release_image;
|
gd->gl.release_user_data = egl_release_image;
|
||||||
|
|
||||||
@@ -251,12 +249,13 @@ end:
|
|||||||
return &gd->gl.base;
|
return &gd->gl.base;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *
|
static image_handle
|
||||||
egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
|
egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
|
||||||
struct egl_data *gd = (void *)base;
|
struct egl_data *gd = (void *)base;
|
||||||
struct egl_pixmap *eglpixmap = NULL;
|
struct egl_pixmap *eglpixmap = NULL;
|
||||||
|
|
||||||
auto r = xcb_get_geometry_reply(base->c, xcb_get_geometry(base->c, pixmap), NULL);
|
auto r =
|
||||||
|
xcb_get_geometry_reply(base->c->c, xcb_get_geometry(base->c->c, pixmap), NULL);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
log_error("Invalid pixmap %#010x", pixmap);
|
log_error("Invalid pixmap %#010x", pixmap);
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -277,13 +276,13 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
|
|||||||
|
|
||||||
eglpixmap = cmalloc(struct egl_pixmap);
|
eglpixmap = cmalloc(struct egl_pixmap);
|
||||||
eglpixmap->pixmap = pixmap;
|
eglpixmap->pixmap = pixmap;
|
||||||
eglpixmap->image =
|
eglpixmap->image = eglCreateImage(gd->display, EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR,
|
||||||
eglCreateImageProc(gd->display, EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR,
|
(EGLClientBuffer)(uintptr_t)pixmap, NULL);
|
||||||
(EGLClientBuffer)(uintptr_t)pixmap, NULL);
|
|
||||||
eglpixmap->owned = owned;
|
eglpixmap->owned = owned;
|
||||||
|
|
||||||
if (eglpixmap->image == EGL_NO_IMAGE) {
|
if (eglpixmap->image == EGL_NO_IMAGE) {
|
||||||
log_error("Failed to create eglpixmap for pixmap %#010x", pixmap);
|
log_error("Failed to create eglpixmap for pixmap %#010x: %s", pixmap,
|
||||||
|
eglGetErrorString(eglGetError()));
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,19 +297,19 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
|
|||||||
wd->dim = 0;
|
wd->dim = 0;
|
||||||
wd->inner->refcount = 1;
|
wd->inner->refcount = 1;
|
||||||
glBindTexture(GL_TEXTURE_2D, inner->texture);
|
glBindTexture(GL_TEXTURE_2D, inner->texture);
|
||||||
glEGLImageTargetTexStorage(GL_TEXTURE_2D, eglpixmap->image, NULL);
|
glEGLImageTargetTexStorageEXT(GL_TEXTURE_2D, eglpixmap->image, NULL);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
gl_check_err();
|
gl_check_err();
|
||||||
return wd;
|
return (image_handle)wd;
|
||||||
err:
|
err:
|
||||||
if (eglpixmap && eglpixmap->image) {
|
if (eglpixmap && eglpixmap->image) {
|
||||||
eglDestroyImageProc(gd->display, eglpixmap->image);
|
eglDestroyImage(gd->display, eglpixmap->image);
|
||||||
}
|
}
|
||||||
free(eglpixmap);
|
free(eglpixmap);
|
||||||
|
|
||||||
if (owned) {
|
if (owned) {
|
||||||
xcb_free_pixmap(base->c, pixmap);
|
xcb_free_pixmap(base->c->c, pixmap);
|
||||||
}
|
}
|
||||||
free(wd);
|
free(wd);
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -320,9 +319,6 @@ static void egl_present(backend_t *base, const region_t *region attr_unused) {
|
|||||||
struct egl_data *gd = (void *)base;
|
struct egl_data *gd = (void *)base;
|
||||||
gl_present(base, region);
|
gl_present(base, region);
|
||||||
eglSwapBuffers(gd->display, gd->target_win);
|
eglSwapBuffers(gd->display, gd->target_win);
|
||||||
if (!gd->gl.is_nvidia) {
|
|
||||||
glFinish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int egl_buffer_age(backend_t *base) {
|
static int egl_buffer_age(backend_t *base) {
|
||||||
@@ -372,6 +368,7 @@ struct backend_operations egl_ops = {
|
|||||||
.deinit = egl_deinit,
|
.deinit = egl_deinit,
|
||||||
.bind_pixmap = egl_bind_pixmap,
|
.bind_pixmap = egl_bind_pixmap,
|
||||||
.release_image = gl_release_image,
|
.release_image = gl_release_image,
|
||||||
|
.prepare = gl_prepare,
|
||||||
.compose = gl_compose,
|
.compose = gl_compose,
|
||||||
.image_op = gl_image_op,
|
.image_op = gl_image_op,
|
||||||
.set_image_property = gl_set_image_property,
|
.set_image_property = gl_set_image_property,
|
||||||
@@ -380,6 +377,7 @@ struct backend_operations egl_ops = {
|
|||||||
.is_image_transparent = default_is_image_transparent,
|
.is_image_transparent = default_is_image_transparent,
|
||||||
.present = egl_present,
|
.present = egl_present,
|
||||||
.buffer_age = egl_buffer_age,
|
.buffer_age = egl_buffer_age,
|
||||||
|
.last_render_time = gl_last_render_time,
|
||||||
.create_shadow_context = gl_create_shadow_context,
|
.create_shadow_context = gl_create_shadow_context,
|
||||||
.destroy_shadow_context = gl_destroy_shadow_context,
|
.destroy_shadow_context = gl_destroy_shadow_context,
|
||||||
.render_shadow = backend_render_shadow_from_mask,
|
.render_shadow = backend_render_shadow_from_mask,
|
||||||
@@ -397,41 +395,6 @@ struct backend_operations egl_ops = {
|
|||||||
.max_buffer_age = 5, // Why?
|
.max_buffer_age = 5, // Why?
|
||||||
};
|
};
|
||||||
|
|
||||||
PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName;
|
|
||||||
/**
|
|
||||||
* Check if a GLX extension exists.
|
|
||||||
*/
|
|
||||||
static inline bool egl_has_extension(EGLDisplay dpy, const char *ext) {
|
|
||||||
const char *egl_exts = eglQueryString(dpy, EGL_EXTENSIONS);
|
|
||||||
if (!egl_exts) {
|
|
||||||
log_error("Failed get EGL extension list.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto inlen = strlen(ext);
|
|
||||||
const char *curr = egl_exts;
|
|
||||||
bool match = false;
|
|
||||||
while (curr && !match) {
|
|
||||||
const char *end = strchr(curr, ' ');
|
|
||||||
if (!end) {
|
|
||||||
// Last extension string
|
|
||||||
match = strcmp(ext, curr) == 0;
|
|
||||||
} else if (curr + inlen == end) {
|
|
||||||
// Length match, do match string
|
|
||||||
match = strncmp(ext, curr, (unsigned long)(end - curr)) == 0;
|
|
||||||
}
|
|
||||||
curr = end ? end + 1 : NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!match) {
|
|
||||||
log_info("Missing EGL extension %s.", ext);
|
|
||||||
} else {
|
|
||||||
log_info("Found EGL extension %s.", ext);
|
|
||||||
}
|
|
||||||
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct eglext_info eglext = {0};
|
struct eglext_info eglext = {0};
|
||||||
|
|
||||||
void eglext_init(EGLDisplay dpy) {
|
void eglext_init(EGLDisplay dpy) {
|
||||||
@@ -439,7 +402,10 @@ void eglext_init(EGLDisplay dpy) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
eglext.initialized = true;
|
eglext.initialized = true;
|
||||||
#define check_ext(name) eglext.has_##name = egl_has_extension(dpy, #name)
|
#define check_ext(name) \
|
||||||
|
eglext.has_##name = epoxy_has_egl_extension(dpy, #name); \
|
||||||
|
log_info("Extension " #name " - %s", eglext.has_##name ? "present" : "absent")
|
||||||
|
|
||||||
check_ext(EGL_EXT_buffer_age);
|
check_ext(EGL_EXT_buffer_age);
|
||||||
check_ext(EGL_EXT_create_context_robustness);
|
check_ext(EGL_EXT_create_context_robustness);
|
||||||
check_ext(EGL_KHR_image_pixmap);
|
check_ext(EGL_KHR_image_pixmap);
|
||||||
@@ -447,16 +413,4 @@ void eglext_init(EGLDisplay dpy) {
|
|||||||
check_ext(EGL_MESA_query_driver);
|
check_ext(EGL_MESA_query_driver);
|
||||||
#endif
|
#endif
|
||||||
#undef check_ext
|
#undef check_ext
|
||||||
|
|
||||||
// Checking if the returned function pointer is NULL is not really necessary,
|
|
||||||
// or maybe not even useful, since eglGetProcAddress might always return
|
|
||||||
// something. We are doing it just for completeness' sake.
|
|
||||||
|
|
||||||
#ifdef EGL_MESA_query_driver
|
|
||||||
eglGetDisplayDriverName =
|
|
||||||
(PFNEGLGETDISPLAYDRIVERNAMEPROC)eglGetProcAddress("eglGetDisplayDriverName");
|
|
||||||
if (!eglGetDisplayDriverName) {
|
|
||||||
eglext.has_EGL_MESA_query_driver = false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <epoxy/egl.h>
|
||||||
|
#include <epoxy/gl.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
// Older version of glx.h defines function prototypes for these extensions...
|
|
||||||
// Rename them to avoid conflicts
|
|
||||||
#include <EGL/egl.h>
|
|
||||||
#include <EGL/eglext.h>
|
|
||||||
#include <GL/gl.h>
|
|
||||||
#include <GL/glext.h>
|
|
||||||
#include <xcb/render.h>
|
#include <xcb/render.h>
|
||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
@@ -26,8 +22,4 @@ struct eglext_info {
|
|||||||
|
|
||||||
extern struct eglext_info eglext;
|
extern struct eglext_info eglext;
|
||||||
|
|
||||||
#ifdef EGL_MESA_query_driver
|
|
||||||
extern PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void eglext_init(EGLDisplay);
|
void eglext_init(EGLDisplay);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
||||||
#include <GL/gl.h>
|
#include <epoxy/gl.h>
|
||||||
#include <GL/glext.h>
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -22,6 +21,11 @@
|
|||||||
#include "backend/backend_common.h"
|
#include "backend/backend_common.h"
|
||||||
#include "backend/gl/gl_common.h"
|
#include "backend/gl/gl_common.h"
|
||||||
|
|
||||||
|
void gl_prepare(backend_t *base, const region_t *reg attr_unused) {
|
||||||
|
auto gd = (struct gl_data *)base;
|
||||||
|
glBeginQuery(GL_TIME_ELAPSED, gd->frame_timing[gd->current_frame_timing]);
|
||||||
|
}
|
||||||
|
|
||||||
GLuint gl_create_shader(GLenum shader_type, const char *shader_str) {
|
GLuint gl_create_shader(GLenum shader_type, const char *shader_str) {
|
||||||
log_trace("===\n%s\n===", shader_str);
|
log_trace("===\n%s\n===", shader_str);
|
||||||
|
|
||||||
@@ -180,7 +184,7 @@ void gl_destroy_window_shader(backend_t *backend_data attr_unused, void *shader)
|
|||||||
* @note In order to reduce number of textures which needs to be
|
* @note In order to reduce number of textures which needs to be
|
||||||
* allocated and deleted during this recursive render
|
* allocated and deleted during this recursive render
|
||||||
* we reuse the same two textures for render source and
|
* we reuse the same two textures for render source and
|
||||||
* destination simply by alterating between them.
|
* destination simply by alternating between them.
|
||||||
* Unfortunately on first iteration source_texture might
|
* Unfortunately on first iteration source_texture might
|
||||||
* be read-only. In this case we will select auxiliary_texture as
|
* be read-only. In this case we will select auxiliary_texture as
|
||||||
* destination_texture in order not to touch that read-only source
|
* destination_texture in order not to touch that read-only source
|
||||||
@@ -248,7 +252,7 @@ _gl_average_texture_color(backend_t *base, GLuint source_texture, GLuint destina
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* @brief Builds a 1x1 texture which has color corresponding to the average of all
|
* @brief Builds a 1x1 texture which has color corresponding to the average of all
|
||||||
* pixels of img by recursively rendering into texture of quorter the size (half
|
* pixels of img by recursively rendering into texture of quarter the size (half
|
||||||
* width and half height).
|
* width and half height).
|
||||||
* Returned texture must not be deleted, since it's owned by the gl_image. It will be
|
* Returned texture must not be deleted, since it's owned by the gl_image. It will be
|
||||||
* deleted when the gl_image is released.
|
* deleted when the gl_image is released.
|
||||||
@@ -304,9 +308,9 @@ static GLuint gl_average_texture_color(backend_t *base, struct backend_image *im
|
|||||||
// Allocate buffers for render input
|
// Allocate buffers for render input
|
||||||
GLint coord[16] = {0};
|
GLint coord[16] = {0};
|
||||||
GLuint indices[] = {0, 1, 2, 2, 3, 0};
|
GLuint indices[] = {0, 1, 2, 2, 3, 0};
|
||||||
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_DYNAMIC_DRAW);
|
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_STREAM_DRAW);
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices,
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices,
|
||||||
GL_STATIC_DRAW);
|
GL_STREAM_DRAW);
|
||||||
|
|
||||||
// Do actual recursive render to 1x1 texture
|
// Do actual recursive render to 1x1 texture
|
||||||
GLuint result_texture = _gl_average_texture_color(
|
GLuint result_texture = _gl_average_texture_color(
|
||||||
@@ -385,6 +389,10 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe
|
|||||||
if (win_shader->uniform_tex >= 0) {
|
if (win_shader->uniform_tex >= 0) {
|
||||||
glUniform1i(win_shader->uniform_tex, 0);
|
glUniform1i(win_shader->uniform_tex, 0);
|
||||||
}
|
}
|
||||||
|
if (win_shader->uniform_effective_size >= 0) {
|
||||||
|
glUniform2f(win_shader->uniform_effective_size, (float)img->ewidth,
|
||||||
|
(float)img->eheight);
|
||||||
|
}
|
||||||
if (win_shader->uniform_dim >= 0) {
|
if (win_shader->uniform_dim >= 0) {
|
||||||
glUniform1f(win_shader->uniform_dim, (float)img->dim);
|
glUniform1f(win_shader->uniform_dim, (float)img->dim);
|
||||||
}
|
}
|
||||||
@@ -443,9 +451,9 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe
|
|||||||
glGenBuffers(2, bo);
|
glGenBuffers(2, bo);
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
|
glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
|
||||||
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW);
|
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STREAM_DRAW);
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6,
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6,
|
||||||
indices, GL_STATIC_DRAW);
|
indices, GL_STREAM_DRAW);
|
||||||
|
|
||||||
glEnableVertexAttribArray(vert_coord_loc);
|
glEnableVertexAttribArray(vert_coord_loc);
|
||||||
glEnableVertexAttribArray(vert_in_texcoord_loc);
|
glEnableVertexAttribArray(vert_in_texcoord_loc);
|
||||||
@@ -477,8 +485,6 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe
|
|||||||
glUseProgram(0);
|
glUseProgram(0);
|
||||||
|
|
||||||
gl_check_err();
|
gl_check_err();
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert rectangles in X coordinates to OpenGL vertex and texture coordinates
|
/// Convert rectangles in X coordinates to OpenGL vertex and texture coordinates
|
||||||
@@ -494,7 +500,7 @@ void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst,
|
|||||||
int extent_height, int texture_height, int root_height,
|
int extent_height, int texture_height, int root_height,
|
||||||
bool y_inverted, GLint *coord, GLuint *indices) {
|
bool y_inverted, GLint *coord, GLuint *indices) {
|
||||||
image_dst.y = root_height - image_dst.y;
|
image_dst.y = root_height - image_dst.y;
|
||||||
image_dst.y -= extent_height;
|
image_dst.y -= extent_height;
|
||||||
|
|
||||||
|
|
||||||
for (int i = 0; i < nrects; i++) {
|
for (int i = 0; i < nrects; i++) {
|
||||||
@@ -546,11 +552,12 @@ void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO(yshui) make use of reg_visible
|
// TODO(yshui) make use of reg_visible
|
||||||
void gl_compose(backend_t *base, void *image_data, coord_t image_dst, void *mask,
|
void gl_compose(backend_t *base, image_handle image_data, coord_t image_dst, image_handle mask_,
|
||||||
coord_t mask_dst, const region_t *reg_tgt,
|
coord_t mask_dst, const region_t *reg_tgt,
|
||||||
const region_t *reg_visible attr_unused, bool lerp) {
|
const region_t *reg_visible attr_unused, bool lerp) {
|
||||||
auto gd = (struct gl_data *)base;
|
auto gd = (struct gl_data *)base;
|
||||||
struct backend_image *img = image_data;
|
auto img = (struct backend_image *)image_data;
|
||||||
|
auto mask = (struct backend_image *)mask_;
|
||||||
auto inner = (struct gl_texture *)img->inner;
|
auto inner = (struct gl_texture *)img->inner;
|
||||||
|
|
||||||
// Painting
|
// Painting
|
||||||
@@ -605,6 +612,7 @@ static bool gl_win_shader_from_stringv(const char **vshader_strv,
|
|||||||
bind_uniform(ret, opacity);
|
bind_uniform(ret, opacity);
|
||||||
bind_uniform(ret, invert_color);
|
bind_uniform(ret, invert_color);
|
||||||
bind_uniform(ret, tex);
|
bind_uniform(ret, tex);
|
||||||
|
bind_uniform(ret, effective_size);
|
||||||
bind_uniform(ret, dim);
|
bind_uniform(ret, dim);
|
||||||
bind_uniform(ret, brightness);
|
bind_uniform(ret, brightness);
|
||||||
bind_uniform(ret, max_brightness);
|
bind_uniform(ret, max_brightness);
|
||||||
@@ -711,7 +719,7 @@ void gl_fill(backend_t *base, struct color c, const region_t *clip) {
|
|||||||
return _gl_fill(base, c, clip, gd->back_fbo, gd->height, true);
|
return _gl_fill(base, c, clip, gd->back_fbo, gd->height, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void *gl_make_mask(backend_t *base, geometry_t size, const region_t *reg) {
|
image_handle gl_make_mask(backend_t *base, geometry_t size, const region_t *reg) {
|
||||||
auto tex = ccalloc(1, struct gl_texture);
|
auto tex = ccalloc(1, struct gl_texture);
|
||||||
auto img = default_new_backend_image(size.width, size.height);
|
auto img = default_new_backend_image(size.width, size.height);
|
||||||
tex->width = size.width;
|
tex->width = size.width;
|
||||||
@@ -742,7 +750,7 @@ void *gl_make_mask(backend_t *base, geometry_t size, const region_t *reg) {
|
|||||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||||
glDeleteFramebuffers(1, &fbo);
|
glDeleteFramebuffers(1, &fbo);
|
||||||
return img;
|
return (image_handle)img;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gl_release_image_inner(backend_t *base, struct gl_texture *inner) {
|
static void gl_release_image_inner(backend_t *base, struct gl_texture *inner) {
|
||||||
@@ -758,8 +766,8 @@ static void gl_release_image_inner(backend_t *base, struct gl_texture *inner) {
|
|||||||
gl_check_err();
|
gl_check_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
void gl_release_image(backend_t *base, void *image_data) {
|
void gl_release_image(backend_t *base, image_handle image) {
|
||||||
struct backend_image *wd = image_data;
|
auto wd = (struct backend_image *)image;
|
||||||
auto inner = (struct gl_texture *)wd->inner;
|
auto inner = (struct gl_texture *)wd->inner;
|
||||||
inner->refcount--;
|
inner->refcount--;
|
||||||
assert(inner->refcount >= 0);
|
assert(inner->refcount >= 0);
|
||||||
@@ -809,7 +817,10 @@ uint64_t gl_get_shader_attributes(backend_t *backend_data attr_unused, void *sha
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool gl_init(struct gl_data *gd, session_t *ps) {
|
bool gl_init(struct gl_data *gd, session_t *ps) {
|
||||||
// Initialize GLX data structure
|
glGenQueries(2, gd->frame_timing);
|
||||||
|
gd->current_frame_timing = 0;
|
||||||
|
|
||||||
|
// Initialize GL data structure
|
||||||
glDisable(GL_DEPTH_TEST);
|
glDisable(GL_DEPTH_TEST);
|
||||||
glDepthMask(GL_FALSE);
|
glDepthMask(GL_FALSE);
|
||||||
|
|
||||||
@@ -883,24 +894,35 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
|
|||||||
glUseProgram(0);
|
glUseProgram(0);
|
||||||
|
|
||||||
gd->dithered_present = ps->o.dithered_present;
|
gd->dithered_present = ps->o.dithered_present;
|
||||||
|
gd->dummy_prog =
|
||||||
|
gl_create_program_from_strv((const char *[]){present_vertex_shader, NULL},
|
||||||
|
(const char *[]){dummy_frag, NULL});
|
||||||
|
if (!gd->dummy_prog) {
|
||||||
|
log_error("Failed to create the dummy shader");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (gd->dithered_present) {
|
if (gd->dithered_present) {
|
||||||
gd->present_prog = gl_create_program_from_strv(
|
gd->present_prog = gl_create_program_from_strv(
|
||||||
(const char *[]){present_vertex_shader, NULL},
|
(const char *[]){present_vertex_shader, NULL},
|
||||||
(const char *[]){present_frag, dither_glsl, NULL});
|
(const char *[]){present_frag, dither_glsl, NULL});
|
||||||
} else {
|
} else {
|
||||||
gd->present_prog = gl_create_program_from_strv(
|
gd->present_prog = gd->dummy_prog;
|
||||||
(const char *[]){present_vertex_shader, NULL},
|
|
||||||
(const char *[]){dummy_frag, NULL});
|
|
||||||
}
|
}
|
||||||
if (!gd->present_prog) {
|
if (!gd->present_prog) {
|
||||||
log_error("Failed to create the present shader");
|
log_error("Failed to create the present shader");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
pml = glGetUniformLocationChecked(gd->present_prog, "projection");
|
|
||||||
glUseProgram(gd->present_prog);
|
pml = glGetUniformLocationChecked(gd->dummy_prog, "projection");
|
||||||
glUniform1i(glGetUniformLocationChecked(gd->present_prog, "tex"), 0);
|
glUseProgram(gd->dummy_prog);
|
||||||
|
glUniform1i(glGetUniformLocationChecked(gd->dummy_prog, "tex"), 0);
|
||||||
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
|
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
|
||||||
glUseProgram(0);
|
if (gd->present_prog != gd->dummy_prog) {
|
||||||
|
pml = glGetUniformLocationChecked(gd->present_prog, "projection");
|
||||||
|
glUseProgram(gd->present_prog);
|
||||||
|
glUniform1i(glGetUniformLocationChecked(gd->present_prog, "tex"), 0);
|
||||||
|
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
|
||||||
|
}
|
||||||
|
|
||||||
gd->shadow_shader.prog =
|
gd->shadow_shader.prog =
|
||||||
gl_create_program_from_str(present_vertex_shader, shadow_colorization_frag);
|
gl_create_program_from_str(present_vertex_shader, shadow_colorization_frag);
|
||||||
@@ -910,7 +932,6 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
|
|||||||
glUseProgram(gd->shadow_shader.prog);
|
glUseProgram(gd->shadow_shader.prog);
|
||||||
glUniform1i(glGetUniformLocationChecked(gd->shadow_shader.prog, "tex"), 0);
|
glUniform1i(glGetUniformLocationChecked(gd->shadow_shader.prog, "tex"), 0);
|
||||||
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
|
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
|
||||||
glUseProgram(0);
|
|
||||||
glBindFragDataLocation(gd->shadow_shader.prog, 0, "out_color");
|
glBindFragDataLocation(gd->shadow_shader.prog, 0, "out_color");
|
||||||
|
|
||||||
gd->brightness_shader.prog =
|
gd->brightness_shader.prog =
|
||||||
@@ -923,6 +944,7 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
|
|||||||
glUseProgram(gd->brightness_shader.prog);
|
glUseProgram(gd->brightness_shader.prog);
|
||||||
glUniform1i(glGetUniformLocationChecked(gd->brightness_shader.prog, "tex"), 0);
|
glUniform1i(glGetUniformLocationChecked(gd->brightness_shader.prog, "tex"), 0);
|
||||||
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
|
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
|
||||||
|
|
||||||
glUseProgram(0);
|
glUseProgram(0);
|
||||||
|
|
||||||
// Set up the size and format of the back texture
|
// Set up the size and format of the back texture
|
||||||
@@ -954,13 +976,13 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
|
|||||||
const char *vendor = (const char *)glGetString(GL_VENDOR);
|
const char *vendor = (const char *)glGetString(GL_VENDOR);
|
||||||
log_debug("GL_VENDOR = %s", vendor);
|
log_debug("GL_VENDOR = %s", vendor);
|
||||||
if (strcmp(vendor, "NVIDIA Corporation") == 0) {
|
if (strcmp(vendor, "NVIDIA Corporation") == 0) {
|
||||||
log_info("GL vendor is NVIDIA, don't use glFinish");
|
log_info("GL vendor is NVIDIA, enable xrender sync fence.");
|
||||||
gd->is_nvidia = true;
|
gd->is_nvidia = true;
|
||||||
} else {
|
} else {
|
||||||
gd->is_nvidia = false;
|
gd->is_nvidia = false;
|
||||||
}
|
}
|
||||||
gd->has_robustness = gl_has_extension("GL_ARB_robustness");
|
gd->has_robustness = epoxy_has_gl_extension("GL_ARB_robustness");
|
||||||
gd->has_egl_image_storage = gl_has_extension("GL_EXT_EGL_image_storage");
|
gd->has_egl_image_storage = epoxy_has_gl_extension("GL_EXT_EGL_image_storage");
|
||||||
gl_check_err();
|
gl_check_err();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -976,6 +998,24 @@ void gl_deinit(struct gl_data *gd) {
|
|||||||
gl_destroy_window_shader(&gd->base, gd->default_shader);
|
gl_destroy_window_shader(&gd->base, gd->default_shader);
|
||||||
gd->default_shader = NULL;
|
gd->default_shader = NULL;
|
||||||
}
|
}
|
||||||
|
glDeleteProgram(gd->dummy_prog);
|
||||||
|
if (gd->present_prog != gd->dummy_prog) {
|
||||||
|
glDeleteProgram(gd->present_prog);
|
||||||
|
}
|
||||||
|
gd->dummy_prog = 0;
|
||||||
|
gd->present_prog = 0;
|
||||||
|
|
||||||
|
glDeleteProgram(gd->fill_shader.prog);
|
||||||
|
glDeleteProgram(gd->brightness_shader.prog);
|
||||||
|
glDeleteProgram(gd->shadow_shader.prog);
|
||||||
|
gd->fill_shader.prog = 0;
|
||||||
|
gd->brightness_shader.prog = 0;
|
||||||
|
gd->shadow_shader.prog = 0;
|
||||||
|
|
||||||
|
glDeleteTextures(1, &gd->default_mask_texture);
|
||||||
|
glDeleteTextures(1, &gd->back_texture);
|
||||||
|
|
||||||
|
glDeleteQueries(2, gd->frame_timing);
|
||||||
|
|
||||||
gl_check_err();
|
gl_check_err();
|
||||||
}
|
}
|
||||||
@@ -1008,9 +1048,11 @@ static inline void gl_image_decouple(backend_t *base, struct backend_image *img)
|
|||||||
auto new_tex = ccalloc(1, struct gl_texture);
|
auto new_tex = ccalloc(1, struct gl_texture);
|
||||||
|
|
||||||
new_tex->texture = gl_new_texture(GL_TEXTURE_2D);
|
new_tex->texture = gl_new_texture(GL_TEXTURE_2D);
|
||||||
new_tex->y_inverted = true;
|
new_tex->y_inverted = inner->y_inverted;
|
||||||
|
new_tex->has_alpha = inner->has_alpha;
|
||||||
new_tex->height = inner->height;
|
new_tex->height = inner->height;
|
||||||
new_tex->width = inner->width;
|
new_tex->width = inner->width;
|
||||||
|
new_tex->shader = inner->shader;
|
||||||
new_tex->refcount = 1;
|
new_tex->refcount = 1;
|
||||||
new_tex->user_data = gd->decouple_texture_user_data(base, inner->user_data);
|
new_tex->user_data = gd->decouple_texture_user_data(base, inner->user_data);
|
||||||
|
|
||||||
@@ -1019,8 +1061,7 @@ static inline void gl_image_decouple(backend_t *base, struct backend_image *img)
|
|||||||
GL_BGRA, GL_UNSIGNED_BYTE, NULL);
|
GL_BGRA, GL_UNSIGNED_BYTE, NULL);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
assert(gd->present_prog);
|
glUseProgram(gd->dummy_prog);
|
||||||
glUseProgram(gd->present_prog);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, inner->texture);
|
glBindTexture(GL_TEXTURE_2D, inner->texture);
|
||||||
|
|
||||||
GLuint fbo;
|
GLuint fbo;
|
||||||
@@ -1063,9 +1104,9 @@ static inline void gl_image_decouple(backend_t *base, struct backend_image *img)
|
|||||||
glGenBuffers(2, bo);
|
glGenBuffers(2, bo);
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
|
glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
|
||||||
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_STATIC_DRAW);
|
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_STREAM_DRAW);
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices,
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices,
|
||||||
GL_STATIC_DRAW);
|
GL_STREAM_DRAW);
|
||||||
|
|
||||||
glEnableVertexAttribArray(vert_coord_loc);
|
glEnableVertexAttribArray(vert_coord_loc);
|
||||||
glEnableVertexAttribArray(vert_in_texcoord_loc);
|
glEnableVertexAttribArray(vert_in_texcoord_loc);
|
||||||
@@ -1163,13 +1204,36 @@ void gl_present(backend_t *base, const region_t *region) {
|
|||||||
glDeleteBuffers(2, bo);
|
glDeleteBuffers(2, bo);
|
||||||
glDeleteVertexArrays(1, &vao);
|
glDeleteVertexArrays(1, &vao);
|
||||||
|
|
||||||
|
glEndQuery(GL_TIME_ELAPSED);
|
||||||
|
gd->current_frame_timing ^= 1;
|
||||||
|
|
||||||
|
gl_check_err();
|
||||||
|
|
||||||
free(coord);
|
free(coord);
|
||||||
free(indices);
|
free(indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
|
bool gl_last_render_time(backend_t *base, struct timespec *ts) {
|
||||||
|
auto gd = (struct gl_data *)base;
|
||||||
|
GLint available = 0;
|
||||||
|
glGetQueryObjectiv(gd->frame_timing[gd->current_frame_timing ^ 1],
|
||||||
|
GL_QUERY_RESULT_AVAILABLE, &available);
|
||||||
|
if (!available) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint64 time;
|
||||||
|
glGetQueryObjectui64v(gd->frame_timing[gd->current_frame_timing ^ 1],
|
||||||
|
GL_QUERY_RESULT, &time);
|
||||||
|
ts->tv_sec = (long)(time / 1000000000);
|
||||||
|
ts->tv_nsec = (long)(time % 1000000000);
|
||||||
|
gl_check_err();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gl_image_op(backend_t *base, enum image_operations op, image_handle image,
|
||||||
const region_t *reg_op, const region_t *reg_visible attr_unused, void *arg) {
|
const region_t *reg_op, const region_t *reg_visible attr_unused, void *arg) {
|
||||||
struct backend_image *tex = image_data;
|
auto tex = (struct backend_image *)image;
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case IMAGE_OP_APPLY_ALPHA:
|
case IMAGE_OP_APPLY_ALPHA:
|
||||||
gl_image_decouple(base, tex);
|
gl_image_decouple(base, tex);
|
||||||
@@ -1182,12 +1246,12 @@ bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool gl_set_image_property(backend_t *backend_data, enum image_properties prop,
|
bool gl_set_image_property(backend_t *backend_data, enum image_properties prop,
|
||||||
void *image_data, void *args) {
|
image_handle image, void *args) {
|
||||||
if (prop != IMAGE_PROPERTY_CUSTOM_SHADER) {
|
if (prop != IMAGE_PROPERTY_CUSTOM_SHADER) {
|
||||||
return default_set_image_property(backend_data, prop, image_data, args);
|
return default_set_image_property(backend_data, prop, image, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct backend_image *img = image_data;
|
auto img = (struct backend_image *)image;
|
||||||
auto inner = (struct gl_texture *)img->inner;
|
auto inner = (struct gl_texture *)img->inner;
|
||||||
inner->shader = args;
|
inner->shader = args;
|
||||||
return true;
|
return true;
|
||||||
@@ -1226,12 +1290,12 @@ void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shado
|
|||||||
free(ctx_);
|
free(ctx_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void *gl_shadow_from_mask(backend_t *base, void *mask,
|
image_handle gl_shadow_from_mask(backend_t *base, image_handle mask_,
|
||||||
struct backend_shadow_context *sctx, struct color color) {
|
struct backend_shadow_context *sctx, struct color color) {
|
||||||
log_debug("Create shadow from mask");
|
log_debug("Create shadow from mask");
|
||||||
auto gd = (struct gl_data *)base;
|
auto gd = (struct gl_data *)base;
|
||||||
auto img = (struct backend_image *)mask;
|
auto mask = (struct backend_image *)mask_;
|
||||||
auto inner = (struct gl_texture *)img->inner;
|
auto inner = (struct gl_texture *)mask->inner;
|
||||||
auto gsctx = (struct gl_shadow_context *)sctx;
|
auto gsctx = (struct gl_shadow_context *)sctx;
|
||||||
int radius = (int)gsctx->radius;
|
int radius = (int)gsctx->radius;
|
||||||
|
|
||||||
@@ -1261,7 +1325,7 @@ void *gl_shadow_from_mask(backend_t *base, void *mask,
|
|||||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||||
source_texture, 0);
|
source_texture, 0);
|
||||||
glDrawBuffer(GL_COLOR_ATTACHMENT0);
|
glDrawBuffer(GL_COLOR_ATTACHMENT0);
|
||||||
if (img->color_inverted) {
|
if (mask->color_inverted) {
|
||||||
// If the mask is inverted, clear the source_texture to white, so the
|
// If the mask is inverted, clear the source_texture to white, so the
|
||||||
// "outside" of the mask would be correct
|
// "outside" of the mask would be correct
|
||||||
glClearColor(1, 1, 1, 1);
|
glClearColor(1, 1, 1, 1);
|
||||||
@@ -1324,8 +1388,11 @@ void *gl_shadow_from_mask(backend_t *base, void *mask,
|
|||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, tmp_texture);
|
glBindTexture(GL_TEXTURE_2D, tmp_texture);
|
||||||
glUseProgram(gd->shadow_shader.prog);
|
glUseProgram(gd->shadow_shader.prog);
|
||||||
glUniform4f(gd->shadow_shader.uniform_color, (GLfloat)color.red,
|
// The shadow color is converted to the premultiplied format to respect the
|
||||||
(GLfloat)color.green, (GLfloat)color.blue, (GLfloat)color.alpha);
|
// globally set glBlendFunc and thus get the correct and expected result.
|
||||||
|
glUniform4f(gd->shadow_shader.uniform_color, (GLfloat)(color.red * color.alpha),
|
||||||
|
(GLfloat)(color.green * color.alpha),
|
||||||
|
(GLfloat)(color.blue * color.alpha), (GLfloat)color.alpha);
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
GLuint indices[] = {0, 1, 2, 2, 3, 0};
|
GLuint indices[] = {0, 1, 2, 2, 3, 0};
|
||||||
@@ -1343,9 +1410,9 @@ void *gl_shadow_from_mask(backend_t *base, void *mask,
|
|||||||
glGenBuffers(2, bo);
|
glGenBuffers(2, bo);
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
|
glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
|
||||||
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 8, coord, GL_STATIC_DRAW);
|
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 8, coord, GL_STREAM_DRAW);
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices,
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices,
|
||||||
GL_STATIC_DRAW);
|
GL_STREAM_DRAW);
|
||||||
|
|
||||||
glEnableVertexAttribArray(vert_coord_loc);
|
glEnableVertexAttribArray(vert_coord_loc);
|
||||||
glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 2, NULL);
|
glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 2, NULL);
|
||||||
@@ -1367,7 +1434,7 @@ void *gl_shadow_from_mask(backend_t *base, void *mask,
|
|||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||||
glDeleteFramebuffers(1, &fbo);
|
glDeleteFramebuffers(1, &fbo);
|
||||||
gl_check_err();
|
gl_check_err();
|
||||||
return new_img;
|
return (image_handle)new_img;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum device_status gl_device_status(backend_t *base) {
|
enum device_status gl_device_status(backend_t *base) {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <GL/gl.h>
|
#include <epoxy/gl.h>
|
||||||
#include <GL/glext.h>
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "backend/backend.h"
|
#include "backend/backend.h"
|
||||||
|
#include "backend/backend_common.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "region.h"
|
#include "region.h"
|
||||||
|
|
||||||
@@ -29,12 +29,11 @@ static inline GLint glGetUniformLocationChecked(GLuint p, const char *name) {
|
|||||||
|
|
||||||
// Program and uniforms for window shader
|
// Program and uniforms for window shader
|
||||||
typedef struct {
|
typedef struct {
|
||||||
UT_hash_handle hh;
|
|
||||||
uint32_t id;
|
|
||||||
GLuint prog;
|
GLuint prog;
|
||||||
GLint uniform_opacity;
|
GLint uniform_opacity;
|
||||||
GLint uniform_invert_color;
|
GLint uniform_invert_color;
|
||||||
GLint uniform_tex;
|
GLint uniform_tex;
|
||||||
|
GLint uniform_effective_size;
|
||||||
GLint uniform_dim;
|
GLint uniform_dim;
|
||||||
GLint uniform_brightness;
|
GLint uniform_brightness;
|
||||||
GLint uniform_max_brightness;
|
GLint uniform_max_brightness;
|
||||||
@@ -77,7 +76,7 @@ typedef struct {
|
|||||||
GLint color_loc;
|
GLint color_loc;
|
||||||
} gl_fill_shader_t;
|
} gl_fill_shader_t;
|
||||||
|
|
||||||
/// @brief Wrapper of a binded GLX texture.
|
/// @brief Wrapper of a bound GL texture.
|
||||||
struct gl_texture {
|
struct gl_texture {
|
||||||
int refcount;
|
int refcount;
|
||||||
bool has_alpha;
|
bool has_alpha;
|
||||||
@@ -108,7 +107,10 @@ struct gl_data {
|
|||||||
gl_shadow_shader_t shadow_shader;
|
gl_shadow_shader_t shadow_shader;
|
||||||
GLuint back_texture, back_fbo;
|
GLuint back_texture, back_fbo;
|
||||||
GLint back_format;
|
GLint back_format;
|
||||||
|
GLuint frame_timing[2];
|
||||||
|
int current_frame_timing;
|
||||||
GLuint present_prog;
|
GLuint present_prog;
|
||||||
|
GLuint dummy_prog;
|
||||||
|
|
||||||
bool dithered_present;
|
bool dithered_present;
|
||||||
|
|
||||||
@@ -129,6 +131,7 @@ typedef struct session session_t;
|
|||||||
#define GL_PROG_MAIN_INIT \
|
#define GL_PROG_MAIN_INIT \
|
||||||
{ .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, .unifm_tex = -1, }
|
{ .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, .unifm_tex = -1, }
|
||||||
|
|
||||||
|
void gl_prepare(backend_t *base, const region_t *reg);
|
||||||
void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst,
|
void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst,
|
||||||
int extent_height, int texture_height, int root_height,
|
int extent_height, int texture_height, int root_height,
|
||||||
bool y_inverted, GLint *coord, GLuint *indices);
|
bool y_inverted, GLint *coord, GLuint *indices);
|
||||||
@@ -141,12 +144,13 @@ void *gl_create_window_shader(backend_t *backend_data, const char *source);
|
|||||||
void gl_destroy_window_shader(backend_t *backend_data, void *shader);
|
void gl_destroy_window_shader(backend_t *backend_data, void *shader);
|
||||||
uint64_t gl_get_shader_attributes(backend_t *backend_data, void *shader);
|
uint64_t gl_get_shader_attributes(backend_t *backend_data, void *shader);
|
||||||
bool gl_set_image_property(backend_t *backend_data, enum image_properties prop,
|
bool gl_set_image_property(backend_t *backend_data, enum image_properties prop,
|
||||||
void *image_data, void *args);
|
image_handle image, void *args);
|
||||||
|
bool gl_last_render_time(backend_t *backend_data, struct timespec *time);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Render a region with texture data.
|
* @brief Render a region with texture data.
|
||||||
*/
|
*/
|
||||||
void gl_compose(backend_t *, void *image_data, coord_t image_dst, void *mask,
|
void gl_compose(backend_t *, image_handle image_data, coord_t image_dst, image_handle mask_,
|
||||||
coord_t mask_dst, const region_t *reg_tgt, const region_t *reg_visible, bool lerp);
|
coord_t mask_dst, const region_t *reg_tgt, const region_t *reg_visible, bool lerp);
|
||||||
|
|
||||||
void gl_resize(struct gl_data *, int width, int height);
|
void gl_resize(struct gl_data *, int width, int height);
|
||||||
@@ -156,38 +160,34 @@ void gl_deinit(struct gl_data *gd);
|
|||||||
|
|
||||||
GLuint gl_new_texture(GLenum target);
|
GLuint gl_new_texture(GLenum target);
|
||||||
|
|
||||||
bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
|
bool gl_image_op(backend_t *base, enum image_operations op, image_handle image,
|
||||||
const region_t *reg_op, const region_t *reg_visible, void *arg);
|
const region_t *reg_op, const region_t *reg_visible, void *arg);
|
||||||
|
|
||||||
void gl_release_image(backend_t *base, void *image_data);
|
void gl_release_image(backend_t *base, image_handle image);
|
||||||
void *gl_make_mask(backend_t *base, geometry_t size, const region_t *reg);
|
image_handle gl_make_mask(backend_t *base, geometry_t size, const region_t *reg);
|
||||||
|
|
||||||
void *gl_clone(backend_t *base, const void *image_data, const region_t *reg_visible);
|
image_handle gl_clone(backend_t *base, image_handle image, const region_t *reg_visible);
|
||||||
|
|
||||||
bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst,
|
bool gl_blur(backend_t *base, double opacity, void *ctx, image_handle mask,
|
||||||
const region_t *reg_blur, const region_t *reg_visible);
|
coord_t mask_dst, const region_t *reg_blur, const region_t *reg_visible);
|
||||||
bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, coord_t mask_dst,
|
bool gl_blur_impl(double opacity, struct gl_blur_context *bctx,
|
||||||
const region_t *reg_blur, const region_t *reg_visible attr_unused,
|
struct backend_image *mask, coord_t mask_dst, const region_t *reg_blur,
|
||||||
GLuint source_texture, geometry_t source_size, GLuint target_fbo,
|
const region_t *reg_visible attr_unused, GLuint source_texture,
|
||||||
GLuint default_mask, bool high_precision);
|
geometry_t source_size, GLuint target_fbo, GLuint default_mask,
|
||||||
|
bool high_precision);
|
||||||
void *gl_create_blur_context(backend_t *base, enum blur_method, void *args);
|
void *gl_create_blur_context(backend_t *base, enum blur_method, void *args);
|
||||||
void gl_destroy_blur_context(backend_t *base, void *ctx);
|
void gl_destroy_blur_context(backend_t *base, void *ctx);
|
||||||
struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double radius);
|
struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double radius);
|
||||||
void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shadow_context *ctx);
|
void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shadow_context *ctx);
|
||||||
void *gl_shadow_from_mask(backend_t *base, void *mask,
|
image_handle gl_shadow_from_mask(backend_t *base, image_handle mask,
|
||||||
struct backend_shadow_context *sctx, struct color color);
|
struct backend_shadow_context *sctx, struct color color);
|
||||||
void gl_get_blur_size(void *blur_context, int *width, int *height);
|
void gl_get_blur_size(void *blur_context, int *width, int *height);
|
||||||
|
|
||||||
void gl_fill(backend_t *base, struct color, const region_t *clip);
|
void gl_fill(backend_t *base, struct color, const region_t *clip);
|
||||||
|
|
||||||
void gl_present(backend_t *base, const region_t *);
|
void gl_present(backend_t *base, const region_t *);
|
||||||
bool gl_read_pixel(backend_t *base, void *image_data, int x, int y, struct color *output);
|
|
||||||
enum device_status gl_device_status(backend_t *base);
|
enum device_status gl_device_status(backend_t *base);
|
||||||
|
|
||||||
static inline void gl_delete_texture(GLuint texture) {
|
|
||||||
glDeleteTextures(1, &texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a textual representation of an OpenGL error.
|
* Get a textual representation of an OpenGL error.
|
||||||
*/
|
*/
|
||||||
@@ -214,7 +214,7 @@ static inline const char *gl_get_err_str(GLenum err) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for GLX error.
|
* Check for GL error.
|
||||||
*
|
*
|
||||||
* http://blog.nobel-joergensen.com/2013/01/29/debugging-opengl-using-glgeterror/
|
* http://blog.nobel-joergensen.com/2013/01/29/debugging-opengl-using-glgeterror/
|
||||||
*/
|
*/
|
||||||
@@ -225,10 +225,10 @@ static inline void gl_check_err_(const char *func, int line) {
|
|||||||
const char *errtext = gl_get_err_str(err);
|
const char *errtext = gl_get_err_str(err);
|
||||||
if (errtext) {
|
if (errtext) {
|
||||||
log_printf(tls_logger, LOG_LEVEL_ERROR, func,
|
log_printf(tls_logger, LOG_LEVEL_ERROR, func,
|
||||||
"GLX error at line %d: %s", line, errtext);
|
"GL error at line %d: %s", line, errtext);
|
||||||
} else {
|
} else {
|
||||||
log_printf(tls_logger, LOG_LEVEL_ERROR, func,
|
log_printf(tls_logger, LOG_LEVEL_ERROR, func,
|
||||||
"GLX error at line %d: %d", line, err);
|
"GL error at line %d: %d", line, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,26 +264,6 @@ static inline bool gl_check_fb_complete_(const char *func, int line, GLenum fb)
|
|||||||
|
|
||||||
#define gl_check_fb_complete(fb) gl_check_fb_complete_(__func__, __LINE__, (fb))
|
#define gl_check_fb_complete(fb) gl_check_fb_complete_(__func__, __LINE__, (fb))
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a GLX extension exists.
|
|
||||||
*/
|
|
||||||
static inline bool gl_has_extension(const char *ext) {
|
|
||||||
int nexts = 0;
|
|
||||||
glGetIntegerv(GL_NUM_EXTENSIONS, &nexts);
|
|
||||||
for (int i = 0; i < nexts || !nexts; i++) {
|
|
||||||
const char *exti = (const char *)glGetStringi(GL_EXTENSIONS, (GLuint)i);
|
|
||||||
if (exti == NULL) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (strcmp(ext, exti) == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gl_clear_err();
|
|
||||||
log_info("Missing GL extension %s.", ext);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const GLuint vert_coord_loc = 0;
|
static const GLuint vert_coord_loc = 0;
|
||||||
static const GLuint vert_in_texcoord_loc = 1;
|
static const GLuint vert_in_texcoord_loc = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,10 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <uthash.h>
|
||||||
#include <xcb/composite.h>
|
#include <xcb/composite.h>
|
||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
|
#include <xcb/xcb_aux.h>
|
||||||
|
|
||||||
#include "backend/backend.h"
|
#include "backend/backend.h"
|
||||||
#include "backend/backend_common.h"
|
#include "backend/backend_common.h"
|
||||||
@@ -42,28 +44,36 @@ struct _glx_pixmap {
|
|||||||
|
|
||||||
struct _glx_data {
|
struct _glx_data {
|
||||||
struct gl_data gl;
|
struct gl_data gl;
|
||||||
Display *display;
|
|
||||||
int screen;
|
|
||||||
xcb_window_t target_win;
|
xcb_window_t target_win;
|
||||||
GLXContext ctx;
|
GLXContext ctx;
|
||||||
|
struct glx_fbconfig_cache *cached_fbconfigs;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct glx_fbconfig_cache {
|
||||||
|
UT_hash_handle hh;
|
||||||
|
struct xvisual_info visual_info;
|
||||||
|
struct glx_fbconfig_info info;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define glXGetFBConfigAttribChecked(a, b, attr, c) \
|
#define glXGetFBConfigAttribChecked(a, b, attr, c) \
|
||||||
do { \
|
do { \
|
||||||
if (glXGetFBConfigAttrib(a, b, attr, c)) { \
|
if (glXGetFBConfigAttrib(a, b, attr, c)) { \
|
||||||
log_info("Cannot get FBConfig attribute " #attr); \
|
log_info("Cannot get FBConfig attribute " #attr); \
|
||||||
continue; \
|
break; \
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvisual_info m) {
|
bool glx_find_fbconfig(struct x_connection *c, struct xvisual_info m,
|
||||||
log_debug("Looking for FBConfig for RGBA%d%d%d%d, depth %d", m.red_size,
|
struct glx_fbconfig_info *info) {
|
||||||
m.blue_size, m.green_size, m.alpha_size, m.visual_depth);
|
log_debug("Looking for FBConfig for RGBA%d%d%d%d, depth: %d, visual id: %#x", m.red_size,
|
||||||
|
m.blue_size, m.green_size, m.alpha_size, m.visual_depth, m.visual);
|
||||||
|
|
||||||
|
info->cfg = NULL;
|
||||||
|
|
||||||
int ncfg;
|
int ncfg;
|
||||||
// clang-format off
|
// clang-format off
|
||||||
GLXFBConfig *cfg =
|
GLXFBConfig *cfg =
|
||||||
glXChooseFBConfig(dpy, screen, (int[]){
|
glXChooseFBConfig(c->dpy, c->screen, (int[]){
|
||||||
GLX_RENDER_TYPE, GLX_RGBA_BIT,
|
GLX_RENDER_TYPE, GLX_RGBA_BIT,
|
||||||
GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
|
GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
|
||||||
GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
|
GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
|
||||||
@@ -87,25 +97,26 @@ struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvi
|
|||||||
GLXFBConfig ret;
|
GLXFBConfig ret;
|
||||||
for (int i = 0; i < ncfg; i++) {
|
for (int i = 0; i < ncfg; i++) {
|
||||||
int depthbuf, stencil, doublebuf, bufsize;
|
int depthbuf, stencil, doublebuf, bufsize;
|
||||||
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BUFFER_SIZE, &bufsize);
|
glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_BUFFER_SIZE, &bufsize);
|
||||||
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_DEPTH_SIZE, &depthbuf);
|
glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_DEPTH_SIZE, &depthbuf);
|
||||||
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_STENCIL_SIZE, &stencil);
|
glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_STENCIL_SIZE, &stencil);
|
||||||
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_DOUBLEBUFFER, &doublebuf);
|
glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_DOUBLEBUFFER, &doublebuf);
|
||||||
if (depthbuf + stencil + bufsize * (doublebuf + 1) >= min_cost) {
|
if (depthbuf + stencil + bufsize * (doublebuf + 1) >= min_cost) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int red, green, blue;
|
int red, green, blue;
|
||||||
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_RED_SIZE, &red);
|
glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_RED_SIZE, &red);
|
||||||
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BLUE_SIZE, &blue);
|
glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_BLUE_SIZE, &blue);
|
||||||
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_GREEN_SIZE, &green);
|
glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_GREEN_SIZE, &green);
|
||||||
if (red != m.red_size || green != m.green_size || blue != m.blue_size) {
|
if (red != m.red_size || green != m.green_size || blue != m.blue_size) {
|
||||||
// Color size doesn't match, this cannot work
|
// Color size doesn't match, this cannot work
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int rgb, rgba;
|
int rgb, rgba;
|
||||||
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &rgb);
|
glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &rgb);
|
||||||
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &rgba);
|
glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_BIND_TO_TEXTURE_RGBA_EXT,
|
||||||
|
&rgba);
|
||||||
if (!rgb && !rgba) {
|
if (!rgb && !rgba) {
|
||||||
log_info("FBConfig is neither RGBA nor RGB, we cannot "
|
log_info("FBConfig is neither RGBA nor RGB, we cannot "
|
||||||
"handle this setup.");
|
"handle this setup.");
|
||||||
@@ -113,9 +124,9 @@ struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvi
|
|||||||
}
|
}
|
||||||
|
|
||||||
int visual;
|
int visual;
|
||||||
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_VISUAL_ID, &visual);
|
glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_VISUAL_ID, &visual);
|
||||||
if (m.visual_depth != -1 &&
|
if (m.visual_depth != -1 &&
|
||||||
x_get_visual_depth(XGetXCBConnection(dpy), (xcb_visualid_t)visual) !=
|
xcb_aux_get_depth_of_visual(c->screen_info, (xcb_visualid_t)visual) !=
|
||||||
m.visual_depth) {
|
m.visual_depth) {
|
||||||
// FBConfig and the correspondent X Visual might not have the same
|
// FBConfig and the correspondent X Visual might not have the same
|
||||||
// depth. (e.g. 32 bit FBConfig with a 24 bit Visual). This is
|
// depth. (e.g. 32 bit FBConfig with a 24 bit Visual). This is
|
||||||
@@ -129,9 +140,9 @@ struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvi
|
|||||||
// All check passed, we are using this one.
|
// All check passed, we are using this one.
|
||||||
found = true;
|
found = true;
|
||||||
ret = cfg[i];
|
ret = cfg[i];
|
||||||
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BIND_TO_TEXTURE_TARGETS_EXT,
|
glXGetFBConfigAttribChecked(
|
||||||
&texture_tgts);
|
c->dpy, cfg[i], GLX_BIND_TO_TEXTURE_TARGETS_EXT, &texture_tgts);
|
||||||
glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_Y_INVERTED_EXT, &y_inverted);
|
glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_Y_INVERTED_EXT, &y_inverted);
|
||||||
|
|
||||||
// Prefer the texture format with matching alpha, with the other one as
|
// Prefer the texture format with matching alpha, with the other one as
|
||||||
// fallback
|
// fallback
|
||||||
@@ -145,40 +156,35 @@ struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvi
|
|||||||
min_cost = depthbuf + stencil + bufsize * (doublebuf + 1);
|
min_cost = depthbuf + stencil + bufsize * (doublebuf + 1);
|
||||||
}
|
}
|
||||||
free(cfg);
|
free(cfg);
|
||||||
if (!found) {
|
if (found) {
|
||||||
return NULL;
|
info->cfg = ret;
|
||||||
|
info->texture_tgts = texture_tgts;
|
||||||
|
info->texture_fmt = texture_fmt;
|
||||||
|
info->y_inverted = y_inverted;
|
||||||
}
|
}
|
||||||
|
return found;
|
||||||
auto info = cmalloc(struct glx_fbconfig_info);
|
|
||||||
info->cfg = ret;
|
|
||||||
info->texture_tgts = texture_tgts;
|
|
||||||
info->texture_fmt = texture_fmt;
|
|
||||||
info->y_inverted = y_inverted;
|
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Free a glx_texture_t.
|
* Free a glx_texture_t.
|
||||||
*/
|
*/
|
||||||
static void glx_release_image(backend_t *base, struct gl_texture *tex) {
|
static void glx_release_image(backend_t *base, struct gl_texture *tex) {
|
||||||
struct _glx_data *gd = (void *)base;
|
|
||||||
|
|
||||||
struct _glx_pixmap *p = tex->user_data;
|
struct _glx_pixmap *p = tex->user_data;
|
||||||
// Release binding
|
// Release binding
|
||||||
if (p->glpixmap && tex->texture) {
|
if (p->glpixmap && tex->texture) {
|
||||||
glBindTexture(GL_TEXTURE_2D, tex->texture);
|
glBindTexture(GL_TEXTURE_2D, tex->texture);
|
||||||
glXReleaseTexImageEXT(gd->display, p->glpixmap, GLX_FRONT_LEFT_EXT);
|
glXReleaseTexImageEXT(base->c->dpy, p->glpixmap, GLX_FRONT_LEFT_EXT);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free GLX Pixmap
|
// Free GLX Pixmap
|
||||||
if (p->glpixmap) {
|
if (p->glpixmap) {
|
||||||
glXDestroyPixmap(gd->display, p->glpixmap);
|
glXDestroyPixmap(base->c->dpy, p->glpixmap);
|
||||||
p->glpixmap = 0;
|
p->glpixmap = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p->owned) {
|
if (p->owned) {
|
||||||
xcb_free_pixmap(base->c, p->pixmap);
|
xcb_free_pixmap(base->c->c, p->pixmap);
|
||||||
p->pixmap = XCB_NONE;
|
p->pixmap = XCB_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,11 +202,17 @@ void glx_deinit(backend_t *base) {
|
|||||||
|
|
||||||
// Destroy GLX context
|
// Destroy GLX context
|
||||||
if (gd->ctx) {
|
if (gd->ctx) {
|
||||||
glXMakeCurrent(gd->display, None, NULL);
|
glXMakeCurrent(base->c->dpy, None, NULL);
|
||||||
glXDestroyContext(gd->display, gd->ctx);
|
glXDestroyContext(base->c->dpy, gd->ctx);
|
||||||
gd->ctx = 0;
|
gd->ctx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct glx_fbconfig_cache *cached_fbconfig = NULL, *tmp = NULL;
|
||||||
|
HASH_ITER(hh, gd->cached_fbconfigs, cached_fbconfig, tmp) {
|
||||||
|
HASH_DEL(gd->cached_fbconfigs, cached_fbconfig);
|
||||||
|
free(cached_fbconfig);
|
||||||
|
}
|
||||||
|
|
||||||
free(gd);
|
free(gd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,15 +243,13 @@ static bool glx_set_swap_interval(int interval, Display *dpy, GLXDrawable drawab
|
|||||||
/**
|
/**
|
||||||
* Initialize OpenGL.
|
* Initialize OpenGL.
|
||||||
*/
|
*/
|
||||||
static backend_t *glx_init(session_t *ps) {
|
static backend_t *glx_init(session_t *ps, xcb_window_t target) {
|
||||||
bool success = false;
|
bool success = false;
|
||||||
glxext_init(ps->dpy, ps->scr);
|
glxext_init(ps->c.dpy, ps->c.screen);
|
||||||
auto gd = ccalloc(1, struct _glx_data);
|
auto gd = ccalloc(1, struct _glx_data);
|
||||||
init_backend_base(&gd->gl.base, ps);
|
init_backend_base(&gd->gl.base, ps);
|
||||||
|
|
||||||
gd->display = ps->dpy;
|
gd->target_win = target;
|
||||||
gd->screen = ps->scr;
|
|
||||||
gd->target_win = session_get_target_window(ps);
|
|
||||||
|
|
||||||
XVisualInfo *pvis = NULL;
|
XVisualInfo *pvis = NULL;
|
||||||
|
|
||||||
@@ -251,8 +261,8 @@ static backend_t *glx_init(session_t *ps) {
|
|||||||
|
|
||||||
// Get XVisualInfo
|
// Get XVisualInfo
|
||||||
int nitems = 0;
|
int nitems = 0;
|
||||||
XVisualInfo vreq = {.visualid = ps->vis};
|
XVisualInfo vreq = {.visualid = ps->c.screen_info->root_visual};
|
||||||
pvis = XGetVisualInfo(ps->dpy, VisualIDMask, &vreq, &nitems);
|
pvis = XGetVisualInfo(ps->c.dpy, VisualIDMask, &vreq, &nitems);
|
||||||
if (!pvis) {
|
if (!pvis) {
|
||||||
log_error("Failed to acquire XVisualInfo for current visual.");
|
log_error("Failed to acquire XVisualInfo for current visual.");
|
||||||
goto end;
|
goto end;
|
||||||
@@ -260,22 +270,22 @@ static backend_t *glx_init(session_t *ps) {
|
|||||||
|
|
||||||
// Ensure the visual is double-buffered
|
// Ensure the visual is double-buffered
|
||||||
int value = 0;
|
int value = 0;
|
||||||
if (glXGetConfig(ps->dpy, pvis, GLX_USE_GL, &value) || !value) {
|
if (glXGetConfig(ps->c.dpy, pvis, GLX_USE_GL, &value) || !value) {
|
||||||
log_error("Root visual is not a GL visual.");
|
log_error("Root visual is not a GL visual.");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (glXGetConfig(ps->dpy, pvis, GLX_STENCIL_SIZE, &value) || !value) {
|
if (glXGetConfig(ps->c.dpy, pvis, GLX_STENCIL_SIZE, &value) || !value) {
|
||||||
log_error("Root visual lacks stencil buffer.");
|
log_error("Root visual lacks stencil buffer.");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (glXGetConfig(ps->dpy, pvis, GLX_DOUBLEBUFFER, &value) || !value) {
|
if (glXGetConfig(ps->c.dpy, pvis, GLX_DOUBLEBUFFER, &value) || !value) {
|
||||||
log_error("Root visual is not a double buffered GL visual.");
|
log_error("Root visual is not a double buffered GL visual.");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (glXGetConfig(ps->dpy, pvis, GLX_RGBA, &value) || !value) {
|
if (glXGetConfig(ps->c.dpy, pvis, GLX_RGBA, &value) || !value) {
|
||||||
log_error("Root visual is a color index visual, not supported");
|
log_error("Root visual is a color index visual, not supported");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
@@ -293,11 +303,11 @@ static backend_t *glx_init(session_t *ps) {
|
|||||||
// Find a fbconfig with visualid matching the one from the target win, so we can
|
// Find a fbconfig with visualid matching the one from the target win, so we can
|
||||||
// be sure that the fbconfig is compatible with our target window.
|
// be sure that the fbconfig is compatible with our target window.
|
||||||
int ncfgs;
|
int ncfgs;
|
||||||
GLXFBConfig *cfg = glXGetFBConfigs(gd->display, gd->screen, &ncfgs);
|
GLXFBConfig *cfg = glXGetFBConfigs(ps->c.dpy, ps->c.screen, &ncfgs);
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (int i = 0; i < ncfgs; i++) {
|
for (int i = 0; i < ncfgs; i++) {
|
||||||
int visualid;
|
int visualid;
|
||||||
glXGetFBConfigAttribChecked(gd->display, cfg[i], GLX_VISUAL_ID, &visualid);
|
glXGetFBConfigAttribChecked(ps->c.dpy, cfg[i], GLX_VISUAL_ID, &visualid);
|
||||||
if ((VisualID)visualid != pvis->visualid) {
|
if ((VisualID)visualid != pvis->visualid) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -316,7 +326,7 @@ static backend_t *glx_init(session_t *ps) {
|
|||||||
attributes[7] = GLX_LOSE_CONTEXT_ON_RESET_ARB;
|
attributes[7] = GLX_LOSE_CONTEXT_ON_RESET_ARB;
|
||||||
}
|
}
|
||||||
|
|
||||||
gd->ctx = glXCreateContextAttribsARB(ps->dpy, cfg[i], 0, true, attributes);
|
gd->ctx = glXCreateContextAttribsARB(ps->c.dpy, cfg[i], 0, true, attributes);
|
||||||
free(cfg);
|
free(cfg);
|
||||||
|
|
||||||
if (!gd->ctx) {
|
if (!gd->ctx) {
|
||||||
@@ -334,7 +344,7 @@ static backend_t *glx_init(session_t *ps) {
|
|||||||
|
|
||||||
// Attach GLX context
|
// Attach GLX context
|
||||||
GLXDrawable tgt = gd->target_win;
|
GLXDrawable tgt = gd->target_win;
|
||||||
if (!glXMakeCurrent(ps->dpy, tgt, gd->ctx)) {
|
if (!glXMakeCurrent(ps->c.dpy, tgt, gd->ctx)) {
|
||||||
log_error("Failed to attach GLX context.");
|
log_error("Failed to attach GLX context.");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
@@ -348,11 +358,11 @@ static backend_t *glx_init(session_t *ps) {
|
|||||||
gd->gl.release_user_data = glx_release_image;
|
gd->gl.release_user_data = glx_release_image;
|
||||||
|
|
||||||
if (ps->o.vsync) {
|
if (ps->o.vsync) {
|
||||||
if (!glx_set_swap_interval(1, ps->dpy, tgt)) {
|
if (!glx_set_swap_interval(1, ps->c.dpy, tgt)) {
|
||||||
log_error("Failed to enable vsync.");
|
log_error("Failed to enable vsync.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
glx_set_swap_interval(0, ps->dpy, tgt);
|
glx_set_swap_interval(0, ps->c.dpy, tgt);
|
||||||
}
|
}
|
||||||
|
|
||||||
success = true;
|
success = true;
|
||||||
@@ -370,23 +380,24 @@ end:
|
|||||||
return &gd->gl.base;
|
return &gd->gl.base;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *
|
static image_handle
|
||||||
glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
|
glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
|
||||||
struct _glx_data *gd = (void *)base;
|
|
||||||
struct _glx_pixmap *glxpixmap = NULL;
|
struct _glx_pixmap *glxpixmap = NULL;
|
||||||
|
auto gd = (struct _glx_data *)base;
|
||||||
// Retrieve pixmap parameters, if they aren't provided
|
// Retrieve pixmap parameters, if they aren't provided
|
||||||
if (fmt.visual_depth > OPENGL_MAX_DEPTH) {
|
if (fmt.visual_depth > OPENGL_MAX_DEPTH) {
|
||||||
log_error("Requested depth %d higher than max possible depth %d.",
|
log_error("Requested depth %d higher than max possible depth %d.",
|
||||||
fmt.visual_depth, OPENGL_MAX_DEPTH);
|
fmt.visual_depth, OPENGL_MAX_DEPTH);
|
||||||
return false;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fmt.visual_depth < 0) {
|
if (fmt.visual_depth < 0) {
|
||||||
log_error("Pixmap %#010x with invalid depth %d", pixmap, fmt.visual_depth);
|
log_error("Pixmap %#010x with invalid depth %d", pixmap, fmt.visual_depth);
|
||||||
return false;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto r = xcb_get_geometry_reply(base->c, xcb_get_geometry(base->c, pixmap), NULL);
|
auto r =
|
||||||
|
xcb_get_geometry_reply(base->c->c, xcb_get_geometry(base->c->c, pixmap), NULL);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
log_error("Invalid pixmap %#010x", pixmap);
|
log_error("Invalid pixmap %#010x", pixmap);
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -400,38 +411,52 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
|
|||||||
wd->inner = (struct backend_image_inner_base *)inner;
|
wd->inner = (struct backend_image_inner_base *)inner;
|
||||||
free(r);
|
free(r);
|
||||||
|
|
||||||
auto fbcfg = glx_find_fbconfig(gd->display, gd->screen, fmt);
|
struct glx_fbconfig_cache *cached_fbconfig = NULL;
|
||||||
if (!fbcfg) {
|
HASH_FIND(hh, gd->cached_fbconfigs, &fmt, sizeof(fmt), cached_fbconfig);
|
||||||
log_error("Couldn't find FBConfig with requested visual %x", fmt.visual);
|
if (!cached_fbconfig) {
|
||||||
goto err;
|
struct glx_fbconfig_info fbconfig;
|
||||||
|
if (!glx_find_fbconfig(base->c, fmt, &fbconfig)) {
|
||||||
|
log_error("Couldn't find FBConfig with requested visual %#x",
|
||||||
|
fmt.visual);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
cached_fbconfig = cmalloc(struct glx_fbconfig_cache);
|
||||||
|
cached_fbconfig->visual_info = fmt;
|
||||||
|
cached_fbconfig->info = fbconfig;
|
||||||
|
HASH_ADD(hh, gd->cached_fbconfigs, visual_info, sizeof(fmt), cached_fbconfig);
|
||||||
|
} else {
|
||||||
|
log_debug("Found cached FBConfig for RGBA%d%d%d%d, depth: %d, visual id: "
|
||||||
|
"%#x",
|
||||||
|
fmt.red_size, fmt.blue_size, fmt.green_size, fmt.alpha_size,
|
||||||
|
fmt.visual_depth, fmt.visual);
|
||||||
}
|
}
|
||||||
|
struct glx_fbconfig_info *fbconfig = &cached_fbconfig->info;
|
||||||
|
|
||||||
// Choose a suitable texture target for our pixmap.
|
// Choose a suitable texture target for our pixmap.
|
||||||
// Refer to GLX_EXT_texture_om_pixmap spec to see what are the mean
|
// Refer to GLX_EXT_texture_om_pixmap spec to see what are the mean
|
||||||
// of the bits in texture_tgts
|
// of the bits in texture_tgts
|
||||||
if (!(fbcfg->texture_tgts & GLX_TEXTURE_2D_BIT_EXT)) {
|
if (!(fbconfig->texture_tgts & GLX_TEXTURE_2D_BIT_EXT)) {
|
||||||
log_error("Cannot bind pixmap to GL_TEXTURE_2D, giving up");
|
log_error("Cannot bind pixmap to GL_TEXTURE_2D, giving up");
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
log_debug("depth %d, rgba %d", fmt.visual_depth,
|
log_debug("depth %d, rgba %d", fmt.visual_depth,
|
||||||
(fbcfg->texture_fmt == GLX_TEXTURE_FORMAT_RGBA_EXT));
|
(fbconfig->texture_fmt == GLX_TEXTURE_FORMAT_RGBA_EXT));
|
||||||
|
|
||||||
GLint attrs[] = {
|
GLint attrs[] = {
|
||||||
GLX_TEXTURE_FORMAT_EXT,
|
GLX_TEXTURE_FORMAT_EXT,
|
||||||
fbcfg->texture_fmt,
|
fbconfig->texture_fmt,
|
||||||
GLX_TEXTURE_TARGET_EXT,
|
GLX_TEXTURE_TARGET_EXT,
|
||||||
GLX_TEXTURE_2D_EXT,
|
GLX_TEXTURE_2D_EXT,
|
||||||
0,
|
0,
|
||||||
};
|
};
|
||||||
|
|
||||||
inner->y_inverted = fbcfg->y_inverted;
|
inner->y_inverted = fbconfig->y_inverted;
|
||||||
|
|
||||||
glxpixmap = cmalloc(struct _glx_pixmap);
|
glxpixmap = cmalloc(struct _glx_pixmap);
|
||||||
glxpixmap->pixmap = pixmap;
|
glxpixmap->pixmap = pixmap;
|
||||||
glxpixmap->glpixmap = glXCreatePixmap(gd->display, fbcfg->cfg, pixmap, attrs);
|
glxpixmap->glpixmap = glXCreatePixmap(base->c->dpy, fbconfig->cfg, pixmap, attrs);
|
||||||
glxpixmap->owned = owned;
|
glxpixmap->owned = owned;
|
||||||
free(fbcfg);
|
|
||||||
|
|
||||||
if (!glxpixmap->glpixmap) {
|
if (!glxpixmap->glpixmap) {
|
||||||
log_error("Failed to create glpixmap for pixmap %#010x", pixmap);
|
log_error("Failed to create glpixmap for pixmap %#010x", pixmap);
|
||||||
@@ -446,19 +471,19 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
|
|||||||
inner->has_alpha = fmt.alpha_size != 0;
|
inner->has_alpha = fmt.alpha_size != 0;
|
||||||
wd->inner->refcount = 1;
|
wd->inner->refcount = 1;
|
||||||
glBindTexture(GL_TEXTURE_2D, inner->texture);
|
glBindTexture(GL_TEXTURE_2D, inner->texture);
|
||||||
glXBindTexImageEXT(gd->display, glxpixmap->glpixmap, GLX_FRONT_LEFT_EXT, NULL);
|
glXBindTexImageEXT(base->c->dpy, glxpixmap->glpixmap, GLX_FRONT_LEFT_EXT, NULL);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
gl_check_err();
|
gl_check_err();
|
||||||
return wd;
|
return (image_handle)wd;
|
||||||
err:
|
err:
|
||||||
if (glxpixmap && glxpixmap->glpixmap) {
|
if (glxpixmap && glxpixmap->glpixmap) {
|
||||||
glXDestroyPixmap(gd->display, glxpixmap->glpixmap);
|
glXDestroyPixmap(base->c->dpy, glxpixmap->glpixmap);
|
||||||
}
|
}
|
||||||
free(glxpixmap);
|
free(glxpixmap);
|
||||||
|
|
||||||
if (owned) {
|
if (owned) {
|
||||||
xcb_free_pixmap(base->c, pixmap);
|
xcb_free_pixmap(base->c->c, pixmap);
|
||||||
}
|
}
|
||||||
free(wd);
|
free(wd);
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -467,10 +492,7 @@ err:
|
|||||||
static void glx_present(backend_t *base, const region_t *region attr_unused) {
|
static void glx_present(backend_t *base, const region_t *region attr_unused) {
|
||||||
struct _glx_data *gd = (void *)base;
|
struct _glx_data *gd = (void *)base;
|
||||||
gl_present(base, region);
|
gl_present(base, region);
|
||||||
glXSwapBuffers(gd->display, gd->target_win);
|
glXSwapBuffers(base->c->dpy, gd->target_win);
|
||||||
if (!gd->gl.is_nvidia) {
|
|
||||||
glFinish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int glx_buffer_age(backend_t *base) {
|
static int glx_buffer_age(backend_t *base) {
|
||||||
@@ -480,15 +502,14 @@ static int glx_buffer_age(backend_t *base) {
|
|||||||
|
|
||||||
struct _glx_data *gd = (void *)base;
|
struct _glx_data *gd = (void *)base;
|
||||||
unsigned int val;
|
unsigned int val;
|
||||||
glXQueryDrawable(gd->display, gd->target_win, GLX_BACK_BUFFER_AGE_EXT, &val);
|
glXQueryDrawable(base->c->dpy, gd->target_win, GLX_BACK_BUFFER_AGE_EXT, &val);
|
||||||
return (int)val ?: -1;
|
return (int)val ?: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void glx_diagnostics(backend_t *base) {
|
static void glx_diagnostics(backend_t *base) {
|
||||||
struct _glx_data *gd = (void *)base;
|
|
||||||
bool warn_software_rendering = false;
|
bool warn_software_rendering = false;
|
||||||
const char *software_renderer_names[] = {"llvmpipe", "SWR", "softpipe"};
|
const char *software_renderer_names[] = {"llvmpipe", "SWR", "softpipe"};
|
||||||
auto glx_vendor = glXGetClientString(gd->display, GLX_VENDOR);
|
auto glx_vendor = glXGetClientString(base->c->dpy, GLX_VENDOR);
|
||||||
printf("* Driver vendors:\n");
|
printf("* Driver vendors:\n");
|
||||||
printf(" * GLX: %s\n", glx_vendor);
|
printf(" * GLX: %s\n", glx_vendor);
|
||||||
printf(" * GL: %s\n", glGetString(GL_VENDOR));
|
printf(" * GL: %s\n", glGetString(GL_VENDOR));
|
||||||
@@ -528,6 +549,7 @@ struct backend_operations glx_ops = {
|
|||||||
.deinit = glx_deinit,
|
.deinit = glx_deinit,
|
||||||
.bind_pixmap = glx_bind_pixmap,
|
.bind_pixmap = glx_bind_pixmap,
|
||||||
.release_image = gl_release_image,
|
.release_image = gl_release_image,
|
||||||
|
.prepare = gl_prepare,
|
||||||
.compose = gl_compose,
|
.compose = gl_compose,
|
||||||
.image_op = gl_image_op,
|
.image_op = gl_image_op,
|
||||||
.set_image_property = gl_set_image_property,
|
.set_image_property = gl_set_image_property,
|
||||||
@@ -536,6 +558,7 @@ struct backend_operations glx_ops = {
|
|||||||
.is_image_transparent = default_is_image_transparent,
|
.is_image_transparent = default_is_image_transparent,
|
||||||
.present = glx_present,
|
.present = glx_present,
|
||||||
.buffer_age = glx_buffer_age,
|
.buffer_age = glx_buffer_age,
|
||||||
|
.last_render_time = gl_last_render_time,
|
||||||
.create_shadow_context = gl_create_shadow_context,
|
.create_shadow_context = gl_create_shadow_context,
|
||||||
.destroy_shadow_context = gl_destroy_shadow_context,
|
.destroy_shadow_context = gl_destroy_shadow_context,
|
||||||
.render_shadow = backend_render_shadow_from_mask,
|
.render_shadow = backend_render_shadow_from_mask,
|
||||||
@@ -553,62 +576,17 @@ struct backend_operations glx_ops = {
|
|||||||
.max_buffer_age = 5, // Why?
|
.max_buffer_age = 5, // Why?
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a GLX extension exists.
|
|
||||||
*/
|
|
||||||
static inline bool glx_has_extension(Display *dpy, int screen, const char *ext) {
|
|
||||||
const char *glx_exts = glXQueryExtensionsString(dpy, screen);
|
|
||||||
if (!glx_exts) {
|
|
||||||
log_error("Failed get GLX extension list.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto inlen = strlen(ext);
|
|
||||||
const char *curr = glx_exts;
|
|
||||||
bool match = false;
|
|
||||||
while (curr && !match) {
|
|
||||||
const char *end = strchr(curr, ' ');
|
|
||||||
if (!end) {
|
|
||||||
// Last extension string
|
|
||||||
match = strcmp(ext, curr) == 0;
|
|
||||||
} else if (curr + inlen == end) {
|
|
||||||
// Length match, do match string
|
|
||||||
match = strncmp(ext, curr, (unsigned long)(end - curr)) == 0;
|
|
||||||
}
|
|
||||||
curr = end ? end + 1 : NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!match) {
|
|
||||||
log_info("Missing GLX extension %s.", ext);
|
|
||||||
} else {
|
|
||||||
log_info("Found GLX extension %s.", ext);
|
|
||||||
}
|
|
||||||
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct glxext_info glxext = {0};
|
struct glxext_info glxext = {0};
|
||||||
PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI;
|
|
||||||
PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
|
|
||||||
PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML;
|
|
||||||
PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML;
|
|
||||||
PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
|
|
||||||
PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
|
|
||||||
PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA;
|
|
||||||
PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT;
|
|
||||||
PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT;
|
|
||||||
PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
|
|
||||||
|
|
||||||
#ifdef GLX_MESA_query_renderer
|
|
||||||
PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void glxext_init(Display *dpy, int screen) {
|
void glxext_init(Display *dpy, int screen) {
|
||||||
if (glxext.initialized) {
|
if (glxext.initialized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
glxext.initialized = true;
|
glxext.initialized = true;
|
||||||
#define check_ext(name) glxext.has_##name = glx_has_extension(dpy, screen, #name)
|
#define check_ext(name) \
|
||||||
|
glxext.has_##name = epoxy_has_glx_extension(dpy, screen, #name); \
|
||||||
|
log_info("Extension " #name " - %s", glxext.has_##name ? "present" : "absent")
|
||||||
|
|
||||||
check_ext(GLX_SGI_video_sync);
|
check_ext(GLX_SGI_video_sync);
|
||||||
check_ext(GLX_SGI_swap_control);
|
check_ext(GLX_SGI_swap_control);
|
||||||
check_ext(GLX_OML_sync_control);
|
check_ext(GLX_OML_sync_control);
|
||||||
@@ -622,36 +600,4 @@ void glxext_init(Display *dpy, int screen) {
|
|||||||
check_ext(GLX_MESA_query_renderer);
|
check_ext(GLX_MESA_query_renderer);
|
||||||
#endif
|
#endif
|
||||||
#undef check_ext
|
#undef check_ext
|
||||||
|
|
||||||
#define lookup(name) (name = (__typeof__(name))glXGetProcAddress((GLubyte *)#name))
|
|
||||||
// Checking if the returned function pointer is NULL is not really necessary,
|
|
||||||
// or maybe not even useful, since glXGetProcAddress might always return
|
|
||||||
// something. We are doing it just for completeness' sake.
|
|
||||||
if (!lookup(glXGetVideoSyncSGI) || !lookup(glXWaitVideoSyncSGI)) {
|
|
||||||
glxext.has_GLX_SGI_video_sync = false;
|
|
||||||
}
|
|
||||||
if (!lookup(glXSwapIntervalEXT)) {
|
|
||||||
glxext.has_GLX_EXT_swap_control = false;
|
|
||||||
}
|
|
||||||
if (!lookup(glXSwapIntervalMESA)) {
|
|
||||||
glxext.has_GLX_MESA_swap_control = false;
|
|
||||||
}
|
|
||||||
if (!lookup(glXSwapIntervalSGI)) {
|
|
||||||
glxext.has_GLX_SGI_swap_control = false;
|
|
||||||
}
|
|
||||||
if (!lookup(glXWaitForMscOML) || !lookup(glXGetSyncValuesOML)) {
|
|
||||||
glxext.has_GLX_OML_sync_control = false;
|
|
||||||
}
|
|
||||||
if (!lookup(glXBindTexImageEXT) || !lookup(glXReleaseTexImageEXT)) {
|
|
||||||
glxext.has_GLX_EXT_texture_from_pixmap = false;
|
|
||||||
}
|
|
||||||
if (!lookup(glXCreateContextAttribsARB)) {
|
|
||||||
glxext.has_GLX_ARB_create_context = false;
|
|
||||||
}
|
|
||||||
#ifdef GLX_MESA_query_renderer
|
|
||||||
if (!lookup(glXQueryCurrentRendererIntegerMESA)) {
|
|
||||||
glxext.has_GLX_MESA_query_renderer = false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#undef lookup
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,14 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <stdbool.h>
|
|
||||||
// Older version of glx.h defines function prototypes for these extensions...
|
|
||||||
// Rename them to avoid conflicts
|
|
||||||
#define glXSwapIntervalMESA glXSwapIntervalMESA_
|
|
||||||
#define glXBindTexImageEXT glXBindTexImageEXT_
|
|
||||||
#define glXReleaseTexImageEXT glXReleaseTexImageEXT
|
|
||||||
#include <GL/glx.h>
|
|
||||||
#undef glXSwapIntervalMESA
|
|
||||||
#undef glXBindTexImageEXT
|
|
||||||
#undef glXReleaseTexImageEXT
|
|
||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
#include <xcb/xcb.h>
|
#include <epoxy/glx.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <xcb/render.h>
|
#include <xcb/render.h>
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
#include "log.h"
|
|
||||||
#include "compiler.h"
|
#include "compiler.h"
|
||||||
|
#include "log.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "x.h"
|
#include "x.h"
|
||||||
|
|
||||||
@@ -27,22 +19,8 @@ struct glx_fbconfig_info {
|
|||||||
int y_inverted;
|
int y_inverted;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The search criteria for glx_find_fbconfig
|
bool glx_find_fbconfig(struct x_connection *c, struct xvisual_info m,
|
||||||
struct glx_fbconfig_criteria {
|
struct glx_fbconfig_info *info);
|
||||||
/// Bit width of the red component
|
|
||||||
int red_size;
|
|
||||||
/// Bit width of the green component
|
|
||||||
int green_size;
|
|
||||||
/// Bit width of the blue component
|
|
||||||
int blue_size;
|
|
||||||
/// Bit width of the alpha component
|
|
||||||
int alpha_size;
|
|
||||||
/// The depth of X visual
|
|
||||||
int visual_depth;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct glx_fbconfig_info *glx_find_fbconfig(Display *, int screen, struct xvisual_info);
|
|
||||||
|
|
||||||
|
|
||||||
struct glxext_info {
|
struct glxext_info {
|
||||||
bool initialized;
|
bool initialized;
|
||||||
@@ -60,19 +38,4 @@ struct glxext_info {
|
|||||||
|
|
||||||
extern struct glxext_info glxext;
|
extern struct glxext_info glxext;
|
||||||
|
|
||||||
extern PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI;
|
|
||||||
extern PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
|
|
||||||
extern PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML;
|
|
||||||
extern PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML;
|
|
||||||
extern PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
|
|
||||||
extern PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
|
|
||||||
extern PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA;
|
|
||||||
extern PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT;
|
|
||||||
extern PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT;
|
|
||||||
extern PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
|
|
||||||
|
|
||||||
#ifdef GLX_MESA_query_renderer
|
|
||||||
extern PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void glxext_init(Display *, int screen);
|
void glxext_init(Display *, int screen);
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ const char win_shader_glsl[] = GLSL(330,
|
|||||||
uniform bool invert_color;
|
uniform bool invert_color;
|
||||||
in vec2 texcoord;
|
in vec2 texcoord;
|
||||||
uniform sampler2D tex;
|
uniform sampler2D tex;
|
||||||
|
uniform vec2 effective_size;
|
||||||
uniform sampler2D brightness;
|
uniform sampler2D brightness;
|
||||||
uniform float max_brightness;
|
uniform float max_brightness;
|
||||||
// Signed distance field for rectangle center at (0, 0), with size of
|
// Signed distance field for rectangle center at (0, 0), with size of
|
||||||
@@ -130,7 +131,7 @@ const char win_shader_glsl[] = GLSL(330,
|
|||||||
// Using mix() to avoid a branch here.
|
// Using mix() to avoid a branch here.
|
||||||
vec4 rim_color = mix(c, border_color, clamp(border_width, 0.0f, 1.0f));
|
vec4 rim_color = mix(c, border_color, clamp(border_width, 0.0f, 1.0f));
|
||||||
|
|
||||||
vec2 outer_size = vec2(textureSize(tex, 0));
|
vec2 outer_size = effective_size;
|
||||||
vec2 inner_size = outer_size - vec2(corner_radius) * 2.0f;
|
vec2 inner_size = outer_size - vec2(corner_radius) * 2.0f;
|
||||||
float rect_distance = rectangle_sdf(texcoord - outer_size / 2.0f,
|
float rect_distance = rectangle_sdf(texcoord - outer_size / 2.0f,
|
||||||
inner_size / 2.0f) - corner_radius;
|
inner_size / 2.0f) - corner_radius;
|
||||||
@@ -157,7 +158,8 @@ const char win_shader_default[] = GLSL(330,
|
|||||||
uniform sampler2D tex;
|
uniform sampler2D tex;
|
||||||
vec4 default_post_processing(vec4 c);
|
vec4 default_post_processing(vec4 c);
|
||||||
vec4 window_shader() {
|
vec4 window_shader() {
|
||||||
vec4 c = texelFetch(tex, ivec2(texcoord), 0);
|
vec2 texsize = textureSize(tex, 0);
|
||||||
|
vec4 c = texture2D(tex, texcoord / texsize, 0);
|
||||||
return default_post_processing(c);
|
return default_post_processing(c);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -202,7 +204,8 @@ const char dither_glsl[] = GLSL(330,
|
|||||||
}
|
}
|
||||||
vec4 dither(vec4 c, vec2 coord) {
|
vec4 dither(vec4 c, vec2 coord) {
|
||||||
vec4 residual = mod(c, 1.0 / 255.0);
|
vec4 residual = mod(c, 1.0 / 255.0);
|
||||||
vec4 dithered = vec4(greaterThan(residual, vec4(1e-4)));
|
residual = min(residual, vec4(1.0 / 255.0) - residual);
|
||||||
|
vec4 dithered = vec4(greaterThan(residual, vec4(1.0 / 65535.0)));
|
||||||
return vec4(c + dithered * bayer(coord) / 255.0);
|
return vec4(c + dithered * bayer(coord) / 255.0);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "backend/backend.h"
|
#include "backend/backend.h"
|
||||||
#include "backend/backend_common.h"
|
#include "backend/backend_common.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "compiler.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "kernel.h"
|
#include "kernel.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
@@ -28,7 +29,6 @@ typedef struct _xrender_data {
|
|||||||
backend_t base;
|
backend_t base;
|
||||||
/// If vsync is enabled and supported by the current system
|
/// If vsync is enabled and supported by the current system
|
||||||
bool vsync;
|
bool vsync;
|
||||||
xcb_visualid_t default_visual;
|
|
||||||
/// Target window
|
/// Target window
|
||||||
xcb_window_t target_win;
|
xcb_window_t target_win;
|
||||||
/// Painting target, it is either the root or the overlay
|
/// Painting target, it is either the root or the overlay
|
||||||
@@ -57,6 +57,10 @@ typedef struct _xrender_data {
|
|||||||
int target_width, target_height;
|
int target_width, target_height;
|
||||||
|
|
||||||
xcb_special_event_t *present_event;
|
xcb_special_event_t *present_event;
|
||||||
|
|
||||||
|
/// Cache an X region to avoid creating and destroying it every frame. A
|
||||||
|
/// workaround for yshui/picom#1166.
|
||||||
|
xcb_xfixes_region_t present_region;
|
||||||
} xrender_data;
|
} xrender_data;
|
||||||
|
|
||||||
struct _xrender_blur_context {
|
struct _xrender_blur_context {
|
||||||
@@ -104,9 +108,9 @@ struct xrender_image {
|
|||||||
/// Make a picture of size width x height, which has a rounded rectangle of corner_radius
|
/// Make a picture of size width x height, which has a rounded rectangle of corner_radius
|
||||||
/// rendered in it.
|
/// rendered in it.
|
||||||
struct xrender_rounded_rectangle_cache *
|
struct xrender_rounded_rectangle_cache *
|
||||||
make_rounded_corner_cache(xcb_connection_t *c, xcb_render_picture_t src,
|
make_rounded_corner_cache(struct x_connection *c, xcb_render_picture_t src, int width,
|
||||||
xcb_drawable_t root, int width, int height, int corner_radius) {
|
int height, int corner_radius) {
|
||||||
auto picture = x_create_picture_with_standard(c, root, width, height,
|
auto picture = x_create_picture_with_standard(c, width, height,
|
||||||
XCB_PICT_STANDARD_ARGB_32, 0, NULL);
|
XCB_PICT_STANDARD_ARGB_32, 0, NULL);
|
||||||
if (picture == XCB_NONE) {
|
if (picture == XCB_NONE) {
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -160,7 +164,7 @@ make_rounded_corner_cache(xcb_connection_t *c, xcb_render_picture_t src,
|
|||||||
}
|
}
|
||||||
#undef ADD_POINT
|
#undef ADD_POINT
|
||||||
|
|
||||||
XCB_AWAIT_VOID(xcb_render_tri_strip, c, XCB_RENDER_PICT_OP_SRC, src, picture,
|
XCB_AWAIT_VOID(xcb_render_tri_strip, c->c, XCB_RENDER_PICT_OP_SRC, src, picture,
|
||||||
x_get_pictfmt_for_standard(c, XCB_PICT_STANDARD_A_8), 0, 0,
|
x_get_pictfmt_for_standard(c, XCB_PICT_STANDARD_A_8), 0, 0,
|
||||||
(uint32_t)point_count, points);
|
(uint32_t)point_count, points);
|
||||||
free(points);
|
free(points);
|
||||||
@@ -177,35 +181,34 @@ static xcb_render_picture_t process_mask(struct _xrender_data *xd, struct xrende
|
|||||||
*allocated = false;
|
*allocated = false;
|
||||||
return inner->pict;
|
return inner->pict;
|
||||||
}
|
}
|
||||||
const auto tmpw = to_u16_checked(inner->width);
|
auto const tmpw = to_u16_checked(inner->width);
|
||||||
const auto tmph = to_u16_checked(inner->height);
|
auto const tmph = to_u16_checked(inner->height);
|
||||||
*allocated = true;
|
*allocated = true;
|
||||||
x_clear_picture_clip_region(xd->base.c, inner->pict);
|
x_clear_picture_clip_region(xd->base.c, inner->pict);
|
||||||
auto ret = x_create_picture_with_visual(
|
auto ret = x_create_picture_with_visual(
|
||||||
xd->base.c, xd->base.root, inner->width, inner->height, inner->visual,
|
xd->base.c, inner->width, inner->height, inner->visual, XCB_RENDER_CP_REPEAT,
|
||||||
XCB_RENDER_CP_REPEAT,
|
|
||||||
(xcb_render_create_picture_value_list_t[]){XCB_RENDER_REPEAT_PAD});
|
(xcb_render_create_picture_value_list_t[]){XCB_RENDER_REPEAT_PAD});
|
||||||
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE,
|
xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE,
|
||||||
ret, 0, 0, 0, 0, 0, 0, tmpw, tmph);
|
ret, 0, 0, 0, 0, 0, 0, tmpw, tmph);
|
||||||
// Remember: the mask has a 1-pixel border
|
// Remember: the mask has a 1-pixel border
|
||||||
if (mask->base.corner_radius != 0) {
|
if (mask->base.corner_radius != 0) {
|
||||||
if (mask->rounded_rectangle == NULL) {
|
if (mask->rounded_rectangle == NULL) {
|
||||||
mask->rounded_rectangle = make_rounded_corner_cache(
|
mask->rounded_rectangle = make_rounded_corner_cache(
|
||||||
xd->base.c, xd->white_pixel, xd->base.root, inner->width - 2,
|
xd->base.c, xd->white_pixel, inner->width - 2,
|
||||||
inner->height - 2, (int)mask->base.corner_radius);
|
inner->height - 2, (int)mask->base.corner_radius);
|
||||||
}
|
}
|
||||||
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE,
|
xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_IN_REVERSE,
|
||||||
mask->rounded_rectangle->p, XCB_NONE, ret, 0, 0, 0,
|
mask->rounded_rectangle->p, XCB_NONE, ret, 0, 0, 0,
|
||||||
0, 1, 1, (uint16_t)(tmpw - 2), (uint16_t)(tmph - 2));
|
0, 1, 1, (uint16_t)(tmpw - 2), (uint16_t)(tmph - 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mask->base.color_inverted) {
|
if (mask->base.color_inverted) {
|
||||||
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_XOR, xd->white_pixel,
|
xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_XOR, xd->white_pixel,
|
||||||
XCB_NONE, ret, 0, 0, 0, 0, 0, 0, tmpw, tmph);
|
XCB_NONE, ret, 0, 0, 0, 0, 0, 0, tmpw, tmph);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alpha_pict != XCB_NONE) {
|
if (alpha_pict != XCB_NONE) {
|
||||||
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, ret, alpha_pict,
|
xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_SRC, ret, alpha_pict,
|
||||||
ret, 0, 0, 0, 0, 0, 0, to_u16_checked(inner->width),
|
ret, 0, 0, 0, 0, 0, 0, to_u16_checked(inner->width),
|
||||||
to_u16_checked(inner->height));
|
to_u16_checked(inner->height));
|
||||||
}
|
}
|
||||||
@@ -228,13 +231,13 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst,
|
|||||||
region_t reg;
|
region_t reg;
|
||||||
|
|
||||||
bool has_alpha = inner->has_alpha || img->opacity != 1;
|
bool has_alpha = inner->has_alpha || img->opacity != 1;
|
||||||
const auto tmpw = to_u16_checked(inner->width);
|
auto const tmpw = to_u16_checked(inner->width);
|
||||||
const auto tmph = to_u16_checked(inner->height);
|
auto const tmph = to_u16_checked(inner->height);
|
||||||
const auto tmpew = to_u16_checked(img->ewidth);
|
auto const tmpew = to_u16_checked(img->ewidth);
|
||||||
const auto tmpeh = to_u16_checked(img->eheight);
|
auto const tmpeh = to_u16_checked(img->eheight);
|
||||||
// Remember: the mask has a 1-pixel border
|
// Remember: the mask has a 1-pixel border
|
||||||
const auto mask_dst_x = to_i16_checked(dst.x - mask_dst.x + 1);
|
auto const mask_dst_x = to_i16_checked(dst.x - mask_dst.x + 1);
|
||||||
const auto mask_dst_y = to_i16_checked(dst.y - mask_dst.y + 1);
|
auto const mask_dst_y = to_i16_checked(dst.y - mask_dst.y + 1);
|
||||||
const xcb_render_color_t dim_color = {
|
const xcb_render_color_t dim_color = {
|
||||||
.red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * img->dim)};
|
.red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * img->dim)};
|
||||||
|
|
||||||
@@ -246,43 +249,49 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst,
|
|||||||
pixman_region32_intersect(®, (region_t *)reg_paint, (region_t *)reg_visible);
|
pixman_region32_intersect(®, (region_t *)reg_paint, (region_t *)reg_visible);
|
||||||
x_set_picture_clip_region(xd->base.c, result, 0, 0, ®);
|
x_set_picture_clip_region(xd->base.c, result, 0, 0, ®);
|
||||||
if (img->corner_radius != 0 && xrimg->rounded_rectangle == NULL) {
|
if (img->corner_radius != 0 && xrimg->rounded_rectangle == NULL) {
|
||||||
xrimg->rounded_rectangle = make_rounded_corner_cache(
|
xrimg->rounded_rectangle =
|
||||||
xd->base.c, xd->white_pixel, xd->base.root, inner->width,
|
make_rounded_corner_cache(xd->base.c, xd->white_pixel, inner->width,
|
||||||
inner->height, (int)img->corner_radius);
|
inner->height, (int)img->corner_radius);
|
||||||
}
|
}
|
||||||
if (((img->color_inverted || img->dim != 0) && has_alpha) || img->corner_radius != 0) {
|
if (((img->color_inverted || img->dim != 0) && has_alpha) || img->corner_radius != 0) {
|
||||||
// Apply image properties using a temporary image, because the source
|
// Apply image properties using a temporary image, because the source
|
||||||
// image is transparent. Otherwise the properties can be applied directly
|
// image is transparent or will get transparent corners. Otherwise the
|
||||||
// on the target image.
|
// properties can be applied directly on the target image.
|
||||||
auto tmp_pict =
|
// Also force a 32-bit ARGB visual for transparent corners, otherwise the
|
||||||
x_create_picture_with_visual(xd->base.c, xd->base.root, inner->width,
|
// corners become black.
|
||||||
inner->height, inner->visual, 0, NULL);
|
auto visual =
|
||||||
|
(img->corner_radius != 0 && inner->depth != 32)
|
||||||
|
? x_get_visual_for_standard(xd->base.c, XCB_PICT_STANDARD_ARGB_32)
|
||||||
|
: inner->visual;
|
||||||
|
auto tmp_pict = x_create_picture_with_visual(
|
||||||
|
xd->base.c, inner->width, inner->height, visual, 0, NULL);
|
||||||
|
|
||||||
// Set clip region translated to source coordinate
|
// Set clip region translated to source coordinate
|
||||||
x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst.x),
|
x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst.x),
|
||||||
to_i16_checked(-dst.y), ®);
|
to_i16_checked(-dst.y), ®);
|
||||||
// Copy source -> tmp
|
// Copy source -> tmp
|
||||||
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, inner->pict,
|
xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_SRC, inner->pict,
|
||||||
XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph);
|
XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph);
|
||||||
|
|
||||||
if (img->color_inverted) {
|
if (img->color_inverted) {
|
||||||
if (inner->has_alpha) {
|
if (inner->has_alpha) {
|
||||||
auto tmp_pict2 = x_create_picture_with_visual(
|
auto tmp_pict2 = x_create_picture_with_visual(
|
||||||
xd->base.c, xd->base.root, tmpw, tmph, inner->visual,
|
xd->base.c, tmpw, tmph, inner->visual, 0, NULL);
|
||||||
0, NULL);
|
xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_SRC,
|
||||||
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC,
|
|
||||||
tmp_pict, XCB_NONE, tmp_pict2, 0, 0,
|
tmp_pict, XCB_NONE, tmp_pict2, 0, 0,
|
||||||
0, 0, 0, 0, tmpw, tmph);
|
0, 0, 0, 0, tmpw, tmph);
|
||||||
|
|
||||||
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE,
|
xcb_render_composite(xd->base.c->c,
|
||||||
|
XCB_RENDER_PICT_OP_DIFFERENCE,
|
||||||
xd->white_pixel, XCB_NONE, tmp_pict,
|
xd->white_pixel, XCB_NONE, tmp_pict,
|
||||||
0, 0, 0, 0, 0, 0, tmpw, tmph);
|
0, 0, 0, 0, 0, 0, tmpw, tmph);
|
||||||
xcb_render_composite(
|
xcb_render_composite(
|
||||||
xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, tmp_pict2,
|
xd->base.c->c, XCB_RENDER_PICT_OP_IN_REVERSE, tmp_pict2,
|
||||||
XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph);
|
XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph);
|
||||||
xcb_render_free_picture(xd->base.c, tmp_pict2);
|
x_free_picture(xd->base.c, tmp_pict2);
|
||||||
} else {
|
} else {
|
||||||
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE,
|
xcb_render_composite(xd->base.c->c,
|
||||||
|
XCB_RENDER_PICT_OP_DIFFERENCE,
|
||||||
xd->white_pixel, XCB_NONE, tmp_pict,
|
xd->white_pixel, XCB_NONE, tmp_pict,
|
||||||
0, 0, 0, 0, 0, 0, tmpw, tmph);
|
0, 0, 0, 0, 0, 0, tmpw, tmph);
|
||||||
}
|
}
|
||||||
@@ -297,33 +306,34 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst,
|
|||||||
.height = tmph,
|
.height = tmph,
|
||||||
};
|
};
|
||||||
|
|
||||||
xcb_render_fill_rectangles(xd->base.c, XCB_RENDER_PICT_OP_OVER,
|
xcb_render_fill_rectangles(xd->base.c->c, XCB_RENDER_PICT_OP_OVER,
|
||||||
tmp_pict, dim_color, 1, &rect);
|
tmp_pict, dim_color, 1, &rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (img->corner_radius != 0 && xrimg->rounded_rectangle != NULL) {
|
if (img->corner_radius != 0 && xrimg->rounded_rectangle != NULL) {
|
||||||
// Clip tmp_pict with a rounded rectangle
|
// Clip tmp_pict with a rounded rectangle
|
||||||
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE,
|
xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_IN_REVERSE,
|
||||||
xrimg->rounded_rectangle->p, XCB_NONE,
|
xrimg->rounded_rectangle->p, XCB_NONE,
|
||||||
tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph);
|
tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph);
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, tmp_pict,
|
xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_OVER, tmp_pict,
|
||||||
mask_pict, result, 0, 0, mask_dst_x, mask_dst_y,
|
mask_pict, result, 0, 0, mask_dst_x, mask_dst_y,
|
||||||
to_i16_checked(dst.x), to_i16_checked(dst.y), tmpew,
|
to_i16_checked(dst.x), to_i16_checked(dst.y), tmpew,
|
||||||
tmpeh);
|
tmpeh);
|
||||||
xcb_render_free_picture(xd->base.c, tmp_pict);
|
xcb_render_free_picture(xd->base.c->c, tmp_pict);
|
||||||
} else {
|
} else {
|
||||||
uint8_t op = (has_alpha ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC);
|
uint8_t op = (has_alpha ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC);
|
||||||
|
|
||||||
xcb_render_composite(xd->base.c, op, inner->pict, mask_pict, result, 0, 0,
|
xcb_render_composite(xd->base.c->c, op, inner->pict, mask_pict, result, 0,
|
||||||
mask_dst_x, mask_dst_y, to_i16_checked(dst.x),
|
0, mask_dst_x, mask_dst_y, to_i16_checked(dst.x),
|
||||||
to_i16_checked(dst.y), tmpew, tmpeh);
|
to_i16_checked(dst.y), tmpew, tmpeh);
|
||||||
if (img->dim != 0 || img->color_inverted) {
|
if (img->dim != 0 || img->color_inverted) {
|
||||||
// Apply properties, if we reach here, then has_alpha == false
|
// Apply properties, if we reach here, then has_alpha == false
|
||||||
assert(!has_alpha);
|
assert(!has_alpha);
|
||||||
if (img->color_inverted) {
|
if (img->color_inverted) {
|
||||||
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE,
|
xcb_render_composite(xd->base.c->c,
|
||||||
|
XCB_RENDER_PICT_OP_DIFFERENCE,
|
||||||
xd->white_pixel, XCB_NONE, result, 0,
|
xd->white_pixel, XCB_NONE, result, 0,
|
||||||
0, 0, 0, to_i16_checked(dst.x),
|
0, 0, 0, to_i16_checked(dst.x),
|
||||||
to_i16_checked(dst.y), tmpew, tmpeh);
|
to_i16_checked(dst.y), tmpew, tmpeh);
|
||||||
@@ -338,21 +348,24 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst,
|
|||||||
.height = tmpeh,
|
.height = tmpeh,
|
||||||
};
|
};
|
||||||
|
|
||||||
xcb_render_fill_rectangles(xd->base.c, XCB_RENDER_PICT_OP_OVER,
|
xcb_render_fill_rectangles(xd->base.c->c,
|
||||||
|
XCB_RENDER_PICT_OP_OVER,
|
||||||
result, dim_color, 1, &rect);
|
result, dim_color, 1, &rect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mask_allocated) {
|
if (mask_allocated) {
|
||||||
xcb_render_free_picture(xd->base.c, mask_pict);
|
x_free_picture(xd->base.c, mask_pict);
|
||||||
}
|
}
|
||||||
pixman_region32_fini(®);
|
pixman_region32_fini(®);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void compose(backend_t *base, void *img_data, coord_t dst, void *mask, coord_t mask_dst,
|
static void compose(backend_t *base, image_handle image_, coord_t dst, image_handle mask_,
|
||||||
const region_t *reg_paint, const region_t *reg_visible, bool lerp attr_unused) {
|
coord_t mask_dst, const region_t *reg_paint, const region_t *reg_visible, attr_unused bool lerp) {
|
||||||
struct _xrender_data *xd = (void *)base;
|
struct _xrender_data *xd = (void *)base;
|
||||||
return compose_impl(xd, img_data, dst, mask, mask_dst, reg_paint, reg_visible,
|
auto image = (struct xrender_image *)image_;
|
||||||
|
auto mask = (struct xrender_image *)mask_;
|
||||||
|
return compose_impl(xd, image, dst, mask, mask_dst, reg_paint, reg_visible,
|
||||||
xd->back[2]);
|
xd->back[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +375,7 @@ static void fill(backend_t *base, struct color c, const region_t *clip) {
|
|||||||
x_set_picture_clip_region(base->c, xd->back[2], 0, 0, clip);
|
x_set_picture_clip_region(base->c, xd->back[2], 0, 0, clip);
|
||||||
// color is in X fixed point representation
|
// color is in X fixed point representation
|
||||||
xcb_render_fill_rectangles(
|
xcb_render_fill_rectangles(
|
||||||
base->c, XCB_RENDER_PICT_OP_OVER, xd->back[2],
|
base->c->c, XCB_RENDER_PICT_OP_OVER, xd->back[2],
|
||||||
(xcb_render_color_t){.red = (uint16_t)(c.red * 0xffff),
|
(xcb_render_color_t){.red = (uint16_t)(c.red * 0xffff),
|
||||||
.green = (uint16_t)(c.green * 0xffff),
|
.green = (uint16_t)(c.green * 0xffff),
|
||||||
.blue = (uint16_t)(c.blue * 0xffff),
|
.blue = (uint16_t)(c.blue * 0xffff),
|
||||||
@@ -374,15 +387,16 @@ static void fill(backend_t *base, struct color c, const region_t *clip) {
|
|||||||
.height = to_u16_checked(extent->y2 - extent->y1)}});
|
.height = to_u16_checked(extent->y2 - extent->y1)}});
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask,
|
static bool blur(backend_t *backend_data, double opacity, void *ctx_, image_handle mask_,
|
||||||
coord_t mask_dst, const region_t *reg_blur, const region_t *reg_visible) {
|
coord_t mask_dst, const region_t *reg_blur, const region_t *reg_visible) {
|
||||||
struct _xrender_blur_context *bctx = ctx_;
|
auto bctx = (struct _xrender_blur_context *)ctx_;
|
||||||
|
auto mask = (struct xrender_image *)mask_;
|
||||||
if (bctx->method == BLUR_METHOD_NONE) {
|
if (bctx->method == BLUR_METHOD_NONE) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct _xrender_data *xd = (void *)backend_data;
|
struct _xrender_data *xd = (void *)backend_data;
|
||||||
xcb_connection_t *c = xd->base.c;
|
auto c = xd->base.c;
|
||||||
region_t reg_op;
|
region_t reg_op;
|
||||||
pixman_region32_init(®_op);
|
pixman_region32_init(®_op);
|
||||||
pixman_region32_intersect(®_op, (region_t *)reg_blur, (region_t *)reg_visible);
|
pixman_region32_intersect(®_op, (region_t *)reg_blur, (region_t *)reg_visible);
|
||||||
@@ -395,8 +409,8 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask
|
|||||||
resize_region(®_op, bctx->resize_width, bctx->resize_height);
|
resize_region(®_op, bctx->resize_width, bctx->resize_height);
|
||||||
|
|
||||||
const pixman_box32_t *extent_resized = pixman_region32_extents(®_op_resized);
|
const pixman_box32_t *extent_resized = pixman_region32_extents(®_op_resized);
|
||||||
const auto height_resized = to_u16_checked(extent_resized->y2 - extent_resized->y1);
|
auto const height_resized = to_u16_checked(extent_resized->y2 - extent_resized->y1);
|
||||||
const auto width_resized = to_u16_checked(extent_resized->x2 - extent_resized->x1);
|
auto const width_resized = to_u16_checked(extent_resized->x2 - extent_resized->x1);
|
||||||
static const char *filter0 = "Nearest"; // The "null" filter
|
static const char *filter0 = "Nearest"; // The "null" filter
|
||||||
static const char *filter = "convolution";
|
static const char *filter = "convolution";
|
||||||
|
|
||||||
@@ -405,10 +419,12 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask
|
|||||||
const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT;
|
const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT;
|
||||||
const xcb_render_create_picture_value_list_t pic_attrs = {.repeat = XCB_RENDER_REPEAT_PAD};
|
const xcb_render_create_picture_value_list_t pic_attrs = {.repeat = XCB_RENDER_REPEAT_PAD};
|
||||||
xcb_render_picture_t tmp_picture[2] = {
|
xcb_render_picture_t tmp_picture[2] = {
|
||||||
x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, height_resized,
|
x_create_picture_with_visual(xd->base.c, width_resized, height_resized,
|
||||||
xd->default_visual, pic_attrs_mask, &pic_attrs),
|
xd->base.c->screen_info->root_visual,
|
||||||
x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, height_resized,
|
pic_attrs_mask, &pic_attrs),
|
||||||
xd->default_visual, pic_attrs_mask, &pic_attrs)};
|
x_create_picture_with_visual(xd->base.c, width_resized, height_resized,
|
||||||
|
xd->base.c->screen_info->root_visual,
|
||||||
|
pic_attrs_mask, &pic_attrs)};
|
||||||
|
|
||||||
if (!tmp_picture[0] || !tmp_picture[1]) {
|
if (!tmp_picture[0] || !tmp_picture[1]) {
|
||||||
log_error("Failed to build intermediate Picture.");
|
log_error("Failed to build intermediate Picture.");
|
||||||
@@ -445,8 +461,8 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask
|
|||||||
// Copy from source picture to destination. The filter must
|
// Copy from source picture to destination. The filter must
|
||||||
// be applied on source picture, to get the nearby pixels outside the
|
// be applied on source picture, to get the nearby pixels outside the
|
||||||
// window.
|
// window.
|
||||||
xcb_render_set_picture_filter(c, src_pict, to_u16_checked(strlen(filter)),
|
xcb_render_set_picture_filter(c->c, src_pict,
|
||||||
filter,
|
to_u16_checked(strlen(filter)), filter,
|
||||||
to_u32_checked(bctx->x_blur_kernel[i]->size),
|
to_u32_checked(bctx->x_blur_kernel[i]->size),
|
||||||
bctx->x_blur_kernel[i]->kernel);
|
bctx->x_blur_kernel[i]->kernel);
|
||||||
|
|
||||||
@@ -454,21 +470,21 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask
|
|||||||
// First pass, back buffer -> tmp picture
|
// First pass, back buffer -> tmp picture
|
||||||
// (we do this even if this is also the last pass, because we
|
// (we do this even if this is also the last pass, because we
|
||||||
// cannot do back buffer -> back buffer)
|
// cannot do back buffer -> back buffer)
|
||||||
xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE,
|
xcb_render_composite(c->c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE,
|
||||||
dst_pict, to_i16_checked(extent_resized->x1),
|
dst_pict, to_i16_checked(extent_resized->x1),
|
||||||
to_i16_checked(extent_resized->y1), 0, 0, 0,
|
to_i16_checked(extent_resized->y1), 0, 0, 0,
|
||||||
0, width_resized, height_resized);
|
0, width_resized, height_resized);
|
||||||
} else if (i < bctx->x_blur_kernel_count - 1) {
|
} else if (i < bctx->x_blur_kernel_count - 1) {
|
||||||
// This is not the last pass or the first pass,
|
// This is not the last pass or the first pass,
|
||||||
// tmp picture 1 -> tmp picture 2
|
// tmp picture 1 -> tmp picture 2
|
||||||
xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, src_pict,
|
xcb_render_composite(c->c, XCB_RENDER_PICT_OP_SRC, src_pict,
|
||||||
XCB_NONE, dst_pict, 0, 0, 0, 0, 0, 0,
|
XCB_NONE, dst_pict, 0, 0, 0, 0, 0, 0,
|
||||||
width_resized, height_resized);
|
width_resized, height_resized);
|
||||||
} else {
|
} else {
|
||||||
x_set_picture_clip_region(c, xd->back[2], 0, 0, ®_op);
|
x_set_picture_clip_region(c, xd->back[2], 0, 0, ®_op);
|
||||||
// This is the last pass, and we are doing more than 1 pass
|
// This is the last pass, and we are doing more than 1 pass
|
||||||
xcb_render_composite(
|
xcb_render_composite(
|
||||||
c, XCB_RENDER_PICT_OP_OVER, src_pict, mask_pict, xd->back[2],
|
c->c, XCB_RENDER_PICT_OP_OVER, src_pict, mask_pict, xd->back[2],
|
||||||
0, 0, to_i16_checked(extent_resized->x1 - mask_dst.x + 1),
|
0, 0, to_i16_checked(extent_resized->x1 - mask_dst.x + 1),
|
||||||
to_i16_checked(extent_resized->y1 - mask_dst.y + 1),
|
to_i16_checked(extent_resized->y1 - mask_dst.y + 1),
|
||||||
to_i16_checked(extent_resized->x1),
|
to_i16_checked(extent_resized->x1),
|
||||||
@@ -477,7 +493,7 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask
|
|||||||
|
|
||||||
// reset filter
|
// reset filter
|
||||||
xcb_render_set_picture_filter(
|
xcb_render_set_picture_filter(
|
||||||
c, src_pict, to_u16_checked(strlen(filter0)), filter0, 0, NULL);
|
c->c, src_pict, to_u16_checked(strlen(filter0)), filter0, 0, NULL);
|
||||||
|
|
||||||
src_pict = tmp_picture[current];
|
src_pict = tmp_picture[current];
|
||||||
dst_pict = tmp_picture[!current];
|
dst_pict = tmp_picture[!current];
|
||||||
@@ -488,24 +504,27 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask
|
|||||||
if (i == 1) {
|
if (i == 1) {
|
||||||
x_set_picture_clip_region(c, xd->back[2], 0, 0, ®_op);
|
x_set_picture_clip_region(c, xd->back[2], 0, 0, ®_op);
|
||||||
xcb_render_composite(
|
xcb_render_composite(
|
||||||
c, XCB_RENDER_PICT_OP_OVER, src_pict, mask_pict, xd->back[2], 0, 0,
|
c->c, XCB_RENDER_PICT_OP_OVER, src_pict, mask_pict, xd->back[2], 0, 0,
|
||||||
to_i16_checked(extent_resized->x1 - mask_dst.x + 1),
|
to_i16_checked(extent_resized->x1 - mask_dst.x + 1),
|
||||||
to_i16_checked(extent_resized->y1 - mask_dst.y + 1),
|
to_i16_checked(extent_resized->y1 - mask_dst.y + 1),
|
||||||
to_i16_checked(extent_resized->x1),
|
to_i16_checked(extent_resized->x1),
|
||||||
to_i16_checked(extent_resized->y1), width_resized, height_resized);
|
to_i16_checked(extent_resized->y1), width_resized, height_resized);
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_render_free_picture(c, tmp_picture[0]);
|
if (mask_allocated) {
|
||||||
xcb_render_free_picture(c, tmp_picture[1]);
|
x_free_picture(c, mask_pict);
|
||||||
|
}
|
||||||
|
x_free_picture(c, tmp_picture[0]);
|
||||||
|
x_free_picture(c, tmp_picture[1]);
|
||||||
pixman_region32_fini(®_op);
|
pixman_region32_fini(®_op);
|
||||||
pixman_region32_fini(®_op_resized);
|
pixman_region32_fini(®_op_resized);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *
|
static image_handle
|
||||||
bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
|
bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
|
||||||
xcb_generic_error_t *e;
|
xcb_generic_error_t *e;
|
||||||
auto r = xcb_get_geometry_reply(base->c, xcb_get_geometry(base->c, pixmap), &e);
|
auto r = xcb_get_geometry_reply(base->c->c, xcb_get_geometry(base->c->c, pixmap), &e);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
log_error("Invalid pixmap: %#010x", pixmap);
|
log_error("Invalid pixmap: %#010x", pixmap);
|
||||||
x_print_error(e->full_sequence, e->major_code, e->minor_code, e->error_code);
|
x_print_error(e->full_sequence, e->major_code, e->minor_code, e->error_code);
|
||||||
@@ -520,8 +539,9 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool
|
|||||||
inner->height = img->base.eheight = r->height;
|
inner->height = img->base.eheight = r->height;
|
||||||
inner->pixmap = pixmap;
|
inner->pixmap = pixmap;
|
||||||
inner->has_alpha = fmt.alpha_size != 0;
|
inner->has_alpha = fmt.alpha_size != 0;
|
||||||
inner->pict =
|
xcb_render_create_picture_value_list_t pic_attrs = {.repeat = XCB_RENDER_REPEAT_NORMAL};
|
||||||
x_create_picture_with_visual_and_pixmap(base->c, fmt.visual, pixmap, 0, NULL);
|
inner->pict = x_create_picture_with_visual_and_pixmap(
|
||||||
|
base->c, fmt.visual, pixmap, XCB_RENDER_CP_REPEAT, &pic_attrs);
|
||||||
inner->owned = owned;
|
inner->owned = owned;
|
||||||
inner->visual = fmt.visual;
|
inner->visual = fmt.visual;
|
||||||
inner->refcount = 1;
|
inner->refcount = 1;
|
||||||
@@ -536,12 +556,12 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool
|
|||||||
free(img);
|
free(img);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return img;
|
return (image_handle)img;
|
||||||
}
|
}
|
||||||
static void release_image_inner(backend_t *base, struct _xrender_image_data_inner *inner) {
|
static void release_image_inner(backend_t *base, struct _xrender_image_data_inner *inner) {
|
||||||
xcb_render_free_picture(base->c, inner->pict);
|
x_free_picture(base->c, inner->pict);
|
||||||
if (inner->owned) {
|
if (inner->owned) {
|
||||||
xcb_free_pixmap(base->c, inner->pixmap);
|
xcb_free_pixmap(base->c->c, inner->pixmap);
|
||||||
}
|
}
|
||||||
free(inner);
|
free(inner);
|
||||||
}
|
}
|
||||||
@@ -555,18 +575,18 @@ release_rounded_corner_cache(backend_t *base, struct xrender_rounded_rectangle_c
|
|||||||
assert(cache->refcount > 0);
|
assert(cache->refcount > 0);
|
||||||
cache->refcount--;
|
cache->refcount--;
|
||||||
if (cache->refcount == 0) {
|
if (cache->refcount == 0) {
|
||||||
xcb_render_free_picture(base->c, cache->p);
|
x_free_picture(base->c, cache->p);
|
||||||
free(cache);
|
free(cache);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void release_image(backend_t *base, void *image) {
|
static void release_image(backend_t *base, image_handle image) {
|
||||||
struct xrender_image *img = image;
|
auto img = (struct xrender_image *)image;
|
||||||
release_rounded_corner_cache(base, img->rounded_rectangle);
|
release_rounded_corner_cache(base, img->rounded_rectangle);
|
||||||
img->rounded_rectangle = NULL;
|
img->rounded_rectangle = NULL;
|
||||||
img->base.inner->refcount -= 1;
|
img->base.inner->refcount -= 1;
|
||||||
if (img->base.inner->refcount == 0) {
|
if (img->base.inner->refcount == 0) {
|
||||||
release_image_inner(base, (void *)img->base.inner);
|
release_image_inner(base, (struct _xrender_image_data_inner *)img->base.inner);
|
||||||
}
|
}
|
||||||
free(img);
|
free(img);
|
||||||
}
|
}
|
||||||
@@ -574,22 +594,23 @@ static void release_image(backend_t *base, void *image) {
|
|||||||
static void deinit(backend_t *backend_data) {
|
static void deinit(backend_t *backend_data) {
|
||||||
struct _xrender_data *xd = (void *)backend_data;
|
struct _xrender_data *xd = (void *)backend_data;
|
||||||
for (int i = 0; i < 256; i++) {
|
for (int i = 0; i < 256; i++) {
|
||||||
xcb_render_free_picture(xd->base.c, xd->alpha_pict[i]);
|
x_free_picture(xd->base.c, xd->alpha_pict[i]);
|
||||||
}
|
}
|
||||||
xcb_render_free_picture(xd->base.c, xd->target);
|
x_free_picture(xd->base.c, xd->target);
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
if (xd->back[i] != XCB_NONE) {
|
if (xd->back[i] != XCB_NONE) {
|
||||||
xcb_render_free_picture(xd->base.c, xd->back[i]);
|
x_free_picture(xd->base.c, xd->back[i]);
|
||||||
}
|
}
|
||||||
if (xd->back_pixmap[i] != XCB_NONE) {
|
if (xd->back_pixmap[i] != XCB_NONE) {
|
||||||
xcb_free_pixmap(xd->base.c, xd->back_pixmap[i]);
|
xcb_free_pixmap(xd->base.c->c, xd->back_pixmap[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
x_destroy_region(xd->base.c, xd->present_region);
|
||||||
if (xd->present_event) {
|
if (xd->present_event) {
|
||||||
xcb_unregister_for_special_event(xd->base.c, xd->present_event);
|
xcb_unregister_for_special_event(xd->base.c->c, xd->present_event);
|
||||||
}
|
}
|
||||||
xcb_render_free_picture(xd->base.c, xd->white_pixel);
|
x_free_picture(xd->base.c, xd->white_pixel);
|
||||||
xcb_render_free_picture(xd->base.c, xd->black_pixel);
|
x_free_picture(xd->base.c, xd->black_pixel);
|
||||||
free(xd);
|
free(xd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,20 +629,19 @@ static void present(backend_t *base, const region_t *region) {
|
|||||||
x_clear_picture_clip_region(base->c, xd->back[xd->curr_back]);
|
x_clear_picture_clip_region(base->c, xd->back[xd->curr_back]);
|
||||||
|
|
||||||
// Update the back buffer first, then present
|
// Update the back buffer first, then present
|
||||||
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, xd->back[2],
|
xcb_render_composite(base->c->c, XCB_RENDER_PICT_OP_SRC, xd->back[2],
|
||||||
XCB_NONE, xd->back[xd->curr_back], orig_x, orig_y, 0,
|
XCB_NONE, xd->back[xd->curr_back], orig_x, orig_y, 0,
|
||||||
0, orig_x, orig_y, region_width, region_height);
|
0, orig_x, orig_y, region_width, region_height);
|
||||||
|
|
||||||
auto xregion = x_create_region(base->c, region);
|
|
||||||
|
|
||||||
// Make sure we got reply from PresentPixmap before waiting for events,
|
// Make sure we got reply from PresentPixmap before waiting for events,
|
||||||
// to avoid deadlock
|
// to avoid deadlock
|
||||||
auto e = xcb_request_check(
|
auto e = xcb_request_check(
|
||||||
base->c, xcb_present_pixmap_checked(
|
base->c->c,
|
||||||
xd->base.c, xd->target_win,
|
xcb_present_pixmap_checked(
|
||||||
xd->back_pixmap[xd->curr_back], 0, XCB_NONE, xregion, 0,
|
base->c->c, xd->target_win, xd->back_pixmap[xd->curr_back], 0, XCB_NONE,
|
||||||
0, XCB_NONE, XCB_NONE, XCB_NONE, 0, 0, 0, 0, 0, NULL));
|
x_set_region(base->c, xd->present_region, region) ? xd->present_region
|
||||||
x_destroy_region(base->c, xregion);
|
: XCB_NONE,
|
||||||
|
0, 0, XCB_NONE, XCB_NONE, XCB_NONE, 0, 0, 0, 0, 0, NULL));
|
||||||
if (e) {
|
if (e) {
|
||||||
log_error("Failed to present pixmap");
|
log_error("Failed to present pixmap");
|
||||||
free(e);
|
free(e);
|
||||||
@@ -629,7 +649,7 @@ static void present(backend_t *base, const region_t *region) {
|
|||||||
}
|
}
|
||||||
// TODO(yshui) don't block wait for present completion
|
// TODO(yshui) don't block wait for present completion
|
||||||
xcb_present_generic_event_t *pev =
|
xcb_present_generic_event_t *pev =
|
||||||
(void *)xcb_wait_for_special_event(base->c, xd->present_event);
|
(void *)xcb_wait_for_special_event(base->c->c, xd->present_event);
|
||||||
if (!pev) {
|
if (!pev) {
|
||||||
// We don't know what happened, maybe X died
|
// We don't know what happened, maybe X died
|
||||||
// But reset buffer age, so in case we do recover, we will
|
// But reset buffer age, so in case we do recover, we will
|
||||||
@@ -653,7 +673,7 @@ static void present(backend_t *base, const region_t *region) {
|
|||||||
free(pev);
|
free(pev);
|
||||||
} else {
|
} else {
|
||||||
// No vsync needed, draw into the target picture directly
|
// No vsync needed, draw into the target picture directly
|
||||||
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, xd->back[2],
|
xcb_render_composite(base->c->c, XCB_RENDER_PICT_OP_SRC, xd->back[2],
|
||||||
XCB_NONE, xd->target, orig_x, orig_y, 0, 0, orig_x,
|
XCB_NONE, xd->target, orig_x, orig_y, 0, 0, orig_x,
|
||||||
orig_y, region_width, region_height);
|
orig_y, region_width, region_height);
|
||||||
}
|
}
|
||||||
@@ -672,7 +692,7 @@ static int buffer_age(backend_t *backend_data) {
|
|||||||
static struct _xrender_image_data_inner *
|
static struct _xrender_image_data_inner *
|
||||||
new_inner(backend_t *base, int w, int h, xcb_visualid_t visual, uint8_t depth) {
|
new_inner(backend_t *base, int w, int h, xcb_visualid_t visual, uint8_t depth) {
|
||||||
auto new_inner = ccalloc(1, struct _xrender_image_data_inner);
|
auto new_inner = ccalloc(1, struct _xrender_image_data_inner);
|
||||||
new_inner->pixmap = x_create_pixmap(base->c, depth, base->root, w, h);
|
new_inner->pixmap = x_create_pixmap(base->c, depth, w, h);
|
||||||
if (new_inner->pixmap == XCB_NONE) {
|
if (new_inner->pixmap == XCB_NONE) {
|
||||||
log_error("Failed to create pixmap for copy");
|
log_error("Failed to create pixmap for copy");
|
||||||
free(new_inner);
|
free(new_inner);
|
||||||
@@ -682,7 +702,7 @@ new_inner(backend_t *base, int w, int h, xcb_visualid_t visual, uint8_t depth) {
|
|||||||
base->c, visual, new_inner->pixmap, 0, NULL);
|
base->c, visual, new_inner->pixmap, 0, NULL);
|
||||||
if (new_inner->pict == XCB_NONE) {
|
if (new_inner->pict == XCB_NONE) {
|
||||||
log_error("Failed to create picture for copy");
|
log_error("Failed to create picture for copy");
|
||||||
xcb_free_pixmap(base->c, new_inner->pixmap);
|
xcb_free_pixmap(base->c->c, new_inner->pixmap);
|
||||||
free(new_inner);
|
free(new_inner);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -695,7 +715,7 @@ new_inner(backend_t *base, int w, int h, xcb_visualid_t visual, uint8_t depth) {
|
|||||||
return new_inner;
|
return new_inner;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *make_mask(backend_t *base, geometry_t size, const region_t *reg) {
|
static image_handle make_mask(backend_t *base, geometry_t size, const region_t *reg) {
|
||||||
struct _xrender_data *xd = (void *)base;
|
struct _xrender_data *xd = (void *)base;
|
||||||
// Give the mask a 1 pixel wide border to emulate the clamp to border behavior of
|
// Give the mask a 1 pixel wide border to emulate the clamp to border behavior of
|
||||||
// OpenGL textures.
|
// OpenGL textures.
|
||||||
@@ -704,12 +724,12 @@ static void *make_mask(backend_t *base, geometry_t size, const region_t *reg) {
|
|||||||
auto inner =
|
auto inner =
|
||||||
new_inner(base, size.width + 2, size.height + 2,
|
new_inner(base, size.width + 2, size.height + 2,
|
||||||
x_get_visual_for_standard(base->c, XCB_PICT_STANDARD_ARGB_32), 32);
|
x_get_visual_for_standard(base->c, XCB_PICT_STANDARD_ARGB_32), 32);
|
||||||
xcb_render_change_picture(base->c, inner->pict, XCB_RENDER_CP_REPEAT,
|
xcb_render_change_picture(base->c->c, inner->pict, XCB_RENDER_CP_REPEAT,
|
||||||
(uint32_t[]){XCB_RENDER_REPEAT_PAD});
|
(uint32_t[]){XCB_RENDER_REPEAT_PAD});
|
||||||
const rect_t *extent = pixman_region32_extents((region_t *)reg);
|
const rect_t *extent = pixman_region32_extents((region_t *)reg);
|
||||||
x_set_picture_clip_region(base->c, xd->back[2], 1, 1, reg);
|
x_set_picture_clip_region(base->c, xd->back[2], 1, 1, reg);
|
||||||
xcb_render_fill_rectangles(
|
xcb_render_fill_rectangles(
|
||||||
base->c, XCB_RENDER_PICT_OP_SRC, inner->pict,
|
base->c->c, XCB_RENDER_PICT_OP_SRC, inner->pict,
|
||||||
(xcb_render_color_t){.red = 0, .green = 0, .blue = 0, .alpha = 0xffff}, 1,
|
(xcb_render_color_t){.red = 0, .green = 0, .blue = 0, .alpha = 0xffff}, 1,
|
||||||
(xcb_rectangle_t[]){{.x = to_i16_checked(extent->x1 + 1),
|
(xcb_rectangle_t[]){{.x = to_i16_checked(extent->x1 + 1),
|
||||||
.y = to_i16_checked(extent->y1 + 1),
|
.y = to_i16_checked(extent->y1 + 1),
|
||||||
@@ -719,7 +739,7 @@ static void *make_mask(backend_t *base, geometry_t size, const region_t *reg) {
|
|||||||
|
|
||||||
// Paint the border transparent
|
// Paint the border transparent
|
||||||
xcb_render_fill_rectangles(
|
xcb_render_fill_rectangles(
|
||||||
base->c, XCB_RENDER_PICT_OP_SRC, inner->pict,
|
base->c->c, XCB_RENDER_PICT_OP_SRC, inner->pict,
|
||||||
(xcb_render_color_t){.red = 0, .green = 0, .blue = 0, .alpha = 0}, 4,
|
(xcb_render_color_t){.red = 0, .green = 0, .blue = 0, .alpha = 0}, 4,
|
||||||
(xcb_rectangle_t[]){{.x = 0, .y = 0, .width = w16, .height = 1},
|
(xcb_rectangle_t[]){{.x = 0, .y = 0, .width = w16, .height = 1},
|
||||||
{.x = 0, .y = 0, .width = 1, .height = h16},
|
{.x = 0, .y = 0, .width = 1, .height = h16},
|
||||||
@@ -738,7 +758,7 @@ static void *make_mask(backend_t *base, geometry_t size, const region_t *reg) {
|
|||||||
img->base.dim = 0;
|
img->base.dim = 0;
|
||||||
img->base.inner = (struct backend_image_inner_base *)inner;
|
img->base.inner = (struct backend_image_inner_base *)inner;
|
||||||
img->rounded_rectangle = NULL;
|
img->rounded_rectangle = NULL;
|
||||||
return img;
|
return (image_handle)img;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool decouple_image(backend_t *base, struct backend_image *img, const region_t *reg) {
|
static bool decouple_image(backend_t *base, struct backend_image *img, const region_t *reg) {
|
||||||
@@ -757,7 +777,7 @@ static bool decouple_image(backend_t *base, struct backend_image *img, const reg
|
|||||||
}
|
}
|
||||||
|
|
||||||
x_set_picture_clip_region(base->c, inner->pict, 0, 0, reg);
|
x_set_picture_clip_region(base->c, inner->pict, 0, 0, reg);
|
||||||
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE,
|
xcb_render_composite(base->c->c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE,
|
||||||
inner2->pict, 0, 0, 0, 0, 0, 0, to_u16_checked(inner->width),
|
inner2->pict, 0, 0, 0, 0, 0, 0, to_u16_checked(inner->width),
|
||||||
to_u16_checked(inner->height));
|
to_u16_checked(inner->height));
|
||||||
|
|
||||||
@@ -766,10 +786,10 @@ static bool decouple_image(backend_t *base, struct backend_image *img, const reg
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool image_op(backend_t *base, enum image_operations op, void *image,
|
static bool image_op(backend_t *base, enum image_operations op, image_handle image,
|
||||||
const region_t *reg_op, const region_t *reg_visible, void *arg) {
|
const region_t *reg_op, const region_t *reg_visible, void *arg) {
|
||||||
struct _xrender_data *xd = (void *)base;
|
struct _xrender_data *xd = (void *)base;
|
||||||
struct backend_image *img = image;
|
auto img = (struct backend_image *)image;
|
||||||
region_t reg;
|
region_t reg;
|
||||||
double *dargs = arg;
|
double *dargs = arg;
|
||||||
|
|
||||||
@@ -796,8 +816,8 @@ static bool image_op(backend_t *base, enum image_operations op, void *image,
|
|||||||
auto inner = (struct _xrender_image_data_inner *)img->inner;
|
auto inner = (struct _xrender_image_data_inner *)img->inner;
|
||||||
auto alpha_pict = xd->alpha_pict[(int)((1 - dargs[0]) * MAX_ALPHA)];
|
auto alpha_pict = xd->alpha_pict[(int)((1 - dargs[0]) * MAX_ALPHA)];
|
||||||
x_set_picture_clip_region(base->c, inner->pict, 0, 0, ®);
|
x_set_picture_clip_region(base->c, inner->pict, 0, 0, ®);
|
||||||
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_OUT_REVERSE, alpha_pict,
|
xcb_render_composite(base->c->c, XCB_RENDER_PICT_OP_OUT_REVERSE,
|
||||||
XCB_NONE, inner->pict, 0, 0, 0, 0, 0, 0,
|
alpha_pict, XCB_NONE, inner->pict, 0, 0, 0, 0, 0, 0,
|
||||||
to_u16_checked(inner->width),
|
to_u16_checked(inner->width),
|
||||||
to_u16_checked(inner->height));
|
to_u16_checked(inner->height));
|
||||||
inner->has_alpha = true;
|
inner->has_alpha = true;
|
||||||
@@ -866,7 +886,7 @@ static void get_blur_size(void *blur_context, int *width, int *height) {
|
|||||||
*height = ctx->resize_height;
|
*height = ctx->resize_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
static backend_t *backend_xrender_init(session_t *ps) {
|
static backend_t *backend_xrender_init(session_t *ps, xcb_window_t target) {
|
||||||
if (ps->o.dithered_present) {
|
if (ps->o.dithered_present) {
|
||||||
log_warn("\"dithered-present\" is not supported by the xrender backend.");
|
log_warn("\"dithered-present\" is not supported by the xrender backend.");
|
||||||
}
|
}
|
||||||
@@ -876,36 +896,30 @@ static backend_t *backend_xrender_init(session_t *ps) {
|
|||||||
|
|
||||||
for (int i = 0; i <= MAX_ALPHA; ++i) {
|
for (int i = 0; i <= MAX_ALPHA; ++i) {
|
||||||
double o = (double)i / (double)MAX_ALPHA;
|
double o = (double)i / (double)MAX_ALPHA;
|
||||||
xd->alpha_pict[i] = solid_picture(ps->c, ps->root, false, o, 0, 0, 0);
|
xd->alpha_pict[i] = solid_picture(&ps->c, false, o, 0, 0, 0);
|
||||||
assert(xd->alpha_pict[i] != XCB_NONE);
|
assert(xd->alpha_pict[i] != XCB_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
xd->target_width = ps->root_width;
|
xd->target_width = ps->root_width;
|
||||||
xd->target_height = ps->root_height;
|
xd->target_height = ps->root_height;
|
||||||
xd->default_visual = ps->vis;
|
xd->black_pixel = solid_picture(&ps->c, true, 1, 0, 0, 0);
|
||||||
xd->black_pixel = solid_picture(ps->c, ps->root, true, 1, 0, 0, 0);
|
xd->white_pixel = solid_picture(&ps->c, true, 1, 1, 1, 1);
|
||||||
xd->white_pixel = solid_picture(ps->c, ps->root, true, 1, 1, 1, 1);
|
|
||||||
|
|
||||||
xd->target_win = session_get_target_window(ps);
|
xd->target_win = target;
|
||||||
xcb_render_create_picture_value_list_t pa = {
|
xcb_render_create_picture_value_list_t pa = {
|
||||||
.subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS,
|
.subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS,
|
||||||
};
|
};
|
||||||
xd->target = x_create_picture_with_visual_and_pixmap(
|
xd->target = x_create_picture_with_visual_and_pixmap(
|
||||||
ps->c, ps->vis, xd->target_win, XCB_RENDER_CP_SUBWINDOW_MODE, &pa);
|
&ps->c, ps->c.screen_info->root_visual, xd->target_win,
|
||||||
|
XCB_RENDER_CP_SUBWINDOW_MODE, &pa);
|
||||||
auto pictfmt = x_get_pictform_for_visual(ps->c, ps->vis);
|
|
||||||
if (!pictfmt) {
|
|
||||||
log_fatal("Default visual is invalid");
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
xd->vsync = ps->o.vsync;
|
xd->vsync = ps->o.vsync;
|
||||||
if (ps->present_exists) {
|
if (ps->present_exists) {
|
||||||
auto eid = x_new_id(ps->c);
|
auto eid = x_new_id(&ps->c);
|
||||||
auto e =
|
auto e =
|
||||||
xcb_request_check(ps->c, xcb_present_select_input_checked(
|
xcb_request_check(ps->c.c, xcb_present_select_input_checked(
|
||||||
ps->c, eid, xd->target_win,
|
ps->c.c, eid, xd->target_win,
|
||||||
XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY));
|
XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY));
|
||||||
if (e) {
|
if (e) {
|
||||||
log_error("Cannot select present input, vsync will be disabled");
|
log_error("Cannot select present input, vsync will be disabled");
|
||||||
xd->vsync = false;
|
xd->vsync = false;
|
||||||
@@ -913,7 +927,7 @@ static backend_t *backend_xrender_init(session_t *ps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
xd->present_event =
|
xd->present_event =
|
||||||
xcb_register_for_special_xge(ps->c, &xcb_present_id, eid, NULL);
|
xcb_register_for_special_xge(ps->c.c, &xcb_present_id, eid, NULL);
|
||||||
if (!xd->present_event) {
|
if (!xd->present_event) {
|
||||||
log_error("Cannot register for special XGE, vsync will be "
|
log_error("Cannot register for special XGE, vsync will be "
|
||||||
"disabled");
|
"disabled");
|
||||||
@@ -923,18 +937,23 @@ static backend_t *backend_xrender_init(session_t *ps) {
|
|||||||
xd->vsync = false;
|
xd->vsync = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (xd->vsync) {
|
||||||
|
xd->present_region = x_create_region(&ps->c, &ps->screen_reg);
|
||||||
|
}
|
||||||
|
|
||||||
// We might need to do double buffering for vsync, and buffer 0 and 1 are for
|
// We might need to do double buffering for vsync, and buffer 0 and 1 are for
|
||||||
// double buffering.
|
// double buffering.
|
||||||
int first_buffer_index = xd->vsync ? 0 : 2;
|
int first_buffer_index = xd->vsync ? 0 : 2;
|
||||||
for (int i = first_buffer_index; i < 3; i++) {
|
for (int i = first_buffer_index; i < 3; i++) {
|
||||||
xd->back_pixmap[i] = x_create_pixmap(ps->c, pictfmt->depth, ps->root,
|
xd->back_pixmap[i] = x_create_pixmap(&ps->c, ps->c.screen_info->root_depth,
|
||||||
to_u16_checked(ps->root_width),
|
to_u16_checked(ps->root_width),
|
||||||
to_u16_checked(ps->root_height));
|
to_u16_checked(ps->root_height));
|
||||||
const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT;
|
const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT;
|
||||||
const xcb_render_create_picture_value_list_t pic_attrs = {
|
const xcb_render_create_picture_value_list_t pic_attrs = {
|
||||||
.repeat = XCB_RENDER_REPEAT_PAD};
|
.repeat = XCB_RENDER_REPEAT_PAD};
|
||||||
xd->back[i] = x_create_picture_with_pictfmt_and_pixmap(
|
xd->back[i] = x_create_picture_with_visual_and_pixmap(
|
||||||
ps->c, pictfmt, xd->back_pixmap[i], pic_attrs_mask, &pic_attrs);
|
&ps->c, ps->c.screen_info->root_visual, xd->back_pixmap[i],
|
||||||
|
pic_attrs_mask, &pic_attrs);
|
||||||
xd->buffer_age[i] = -1;
|
xd->buffer_age[i] = -1;
|
||||||
if (xd->back_pixmap[i] == XCB_NONE || xd->back[i] == XCB_NONE) {
|
if (xd->back_pixmap[i] == XCB_NONE || xd->back[i] == XCB_NONE) {
|
||||||
log_error("Cannot create pixmap for rendering");
|
log_error("Cannot create pixmap for rendering");
|
||||||
@@ -949,19 +968,19 @@ err:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *clone_image(backend_t *base attr_unused, const void *image_data,
|
image_handle clone_image(backend_t *base attr_unused, image_handle image,
|
||||||
const region_t *reg_visible attr_unused) {
|
const region_t *reg_visible attr_unused) {
|
||||||
auto new_img = ccalloc(1, struct xrender_image);
|
auto new_img = ccalloc(1, struct xrender_image);
|
||||||
*new_img = *(struct xrender_image *)image_data;
|
*new_img = *(struct xrender_image *)image;
|
||||||
new_img->base.inner->refcount++;
|
new_img->base.inner->refcount++;
|
||||||
if (new_img->rounded_rectangle) {
|
if (new_img->rounded_rectangle) {
|
||||||
new_img->rounded_rectangle->refcount++;
|
new_img->rounded_rectangle->refcount++;
|
||||||
}
|
}
|
||||||
return new_img;
|
return (image_handle)new_img;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool set_image_property(backend_t *base, enum image_properties op,
|
||||||
set_image_property(backend_t *base, enum image_properties op, void *image, void *args) {
|
image_handle image, void *args) {
|
||||||
auto xrimg = (struct xrender_image *)image;
|
auto xrimg = (struct xrender_image *)image;
|
||||||
if (op == IMAGE_PROPERTY_CORNER_RADIUS &&
|
if (op == IMAGE_PROPERTY_CORNER_RADIUS &&
|
||||||
((double *)args)[0] != xrimg->base.corner_radius) {
|
((double *)args)[0] != xrimg->base.corner_radius) {
|
||||||
@@ -983,10 +1002,8 @@ struct backend_operations xrender_ops = {
|
|||||||
.release_image = release_image,
|
.release_image = release_image,
|
||||||
.create_shadow_context = default_create_shadow_context,
|
.create_shadow_context = default_create_shadow_context,
|
||||||
.destroy_shadow_context = default_destroy_shadow_context,
|
.destroy_shadow_context = default_destroy_shadow_context,
|
||||||
.render_shadow = default_backend_render_shadow,
|
.render_shadow = default_render_shadow,
|
||||||
.make_mask = make_mask,
|
.make_mask = make_mask,
|
||||||
//.prepare_win = prepare_win,
|
|
||||||
//.release_win = release_win,
|
|
||||||
.is_image_transparent = default_is_image_transparent,
|
.is_image_transparent = default_is_image_transparent,
|
||||||
.buffer_age = buffer_age,
|
.buffer_age = buffer_age,
|
||||||
.max_buffer_age = 2,
|
.max_buffer_age = 2,
|
||||||
|
|||||||
146
src/c2.c
146
src/c2.c
@@ -233,14 +233,16 @@ static inline long winprop_get_int(winprop_t prop, size_t index) {
|
|||||||
*/
|
*/
|
||||||
static inline int strcmp_wd(const char *needle, const char *src) {
|
static inline int strcmp_wd(const char *needle, const char *src) {
|
||||||
int ret = mstrncmp(needle, src);
|
int ret = mstrncmp(needle, src);
|
||||||
if (ret)
|
if (ret) {
|
||||||
return ret;
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
char c = src[strlen(needle)];
|
char c = src[strlen(needle)];
|
||||||
if (isalnum((unsigned char)c) || '_' == c)
|
if (isalnum((unsigned char)c) || '_' == c) {
|
||||||
return 1;
|
return 1;
|
||||||
else
|
}
|
||||||
return 0;
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -254,8 +256,9 @@ static inline attr_unused bool c2_ptr_isempty(const c2_ptr_t p) {
|
|||||||
* Reset a c2_ptr_t.
|
* Reset a c2_ptr_t.
|
||||||
*/
|
*/
|
||||||
static inline void c2_ptr_reset(c2_ptr_t *pp) {
|
static inline void c2_ptr_reset(c2_ptr_t *pp) {
|
||||||
if (pp)
|
if (pp) {
|
||||||
memcpy(pp, &C2_PTR_NULL, sizeof(c2_ptr_t));
|
memcpy(pp, &C2_PTR_NULL, sizeof(c2_ptr_t));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -336,17 +339,19 @@ static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_p
|
|||||||
* Parse a condition string.
|
* Parse a condition string.
|
||||||
*/
|
*/
|
||||||
c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data) {
|
c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data) {
|
||||||
if (!pattern)
|
if (!pattern) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the pattern
|
// Parse the pattern
|
||||||
c2_ptr_t result = C2_PTR_INIT;
|
c2_ptr_t result = C2_PTR_INIT;
|
||||||
int offset = -1;
|
int offset = -1;
|
||||||
|
|
||||||
if (strlen(pattern) >= 2 && ':' == pattern[1])
|
if (strlen(pattern) >= 2 && ':' == pattern[1]) {
|
||||||
offset = c2_parse_legacy(pattern, 0, &result);
|
offset = c2_parse_legacy(pattern, 0, &result);
|
||||||
else
|
} else {
|
||||||
offset = c2_parse_grp(pattern, 0, &result, 0);
|
offset = c2_parse_grp(pattern, 0, &result, 0);
|
||||||
|
}
|
||||||
|
|
||||||
if (offset < 0) {
|
if (offset < 0) {
|
||||||
c2_freep(&result);
|
c2_freep(&result);
|
||||||
@@ -395,11 +400,13 @@ c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data) {
|
|||||||
*/
|
*/
|
||||||
static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int level) {
|
static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int level) {
|
||||||
// Check for recursion levels
|
// Check for recursion levels
|
||||||
if (level > C2_MAX_LEVELS)
|
if (level > C2_MAX_LEVELS) {
|
||||||
c2_error("Exceeded maximum recursion levels.");
|
c2_error("Exceeded maximum recursion levels.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!pattern)
|
if (!pattern) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
// Expected end character
|
// Expected end character
|
||||||
const char endchar = (offset ? ')' : '\0');
|
const char endchar = (offset ? ')' : '\0');
|
||||||
@@ -428,17 +435,20 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int
|
|||||||
assert(elei <= 2);
|
assert(elei <= 2);
|
||||||
|
|
||||||
// Jump over spaces
|
// Jump over spaces
|
||||||
if (isspace((unsigned char)pattern[offset]))
|
if (isspace((unsigned char)pattern[offset])) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle end of group
|
// Handle end of group
|
||||||
if (')' == pattern[offset])
|
if (')' == pattern[offset]) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle "!"
|
// Handle "!"
|
||||||
if ('!' == pattern[offset]) {
|
if ('!' == pattern[offset]) {
|
||||||
if (!next_expected)
|
if (!next_expected) {
|
||||||
c2_error("Unexpected \"!\".");
|
c2_error("Unexpected \"!\".");
|
||||||
|
}
|
||||||
|
|
||||||
neg = !neg;
|
neg = !neg;
|
||||||
continue;
|
continue;
|
||||||
@@ -446,8 +456,9 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int
|
|||||||
|
|
||||||
// Handle AND and OR
|
// Handle AND and OR
|
||||||
if ('&' == pattern[offset] || '|' == pattern[offset]) {
|
if ('&' == pattern[offset] || '|' == pattern[offset]) {
|
||||||
if (next_expected)
|
if (next_expected) {
|
||||||
c2_error("Unexpected logical operator.");
|
c2_error("Unexpected logical operator.");
|
||||||
|
}
|
||||||
|
|
||||||
next_expected = true;
|
next_expected = true;
|
||||||
if (!mstrncmp("&&", pattern + offset)) {
|
if (!mstrncmp("&&", pattern + offset)) {
|
||||||
@@ -456,15 +467,17 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int
|
|||||||
} else if (!mstrncmp("||", pattern + offset)) {
|
} else if (!mstrncmp("||", pattern + offset)) {
|
||||||
ops[elei] = C2_B_OOR;
|
ops[elei] = C2_B_OOR;
|
||||||
++offset;
|
++offset;
|
||||||
} else
|
} else {
|
||||||
c2_error("Illegal logical operator.");
|
c2_error("Illegal logical operator.");
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parsing an element
|
// Parsing an element
|
||||||
if (!next_expected)
|
if (!next_expected) {
|
||||||
c2_error("Unexpected expression.");
|
c2_error("Unexpected expression.");
|
||||||
|
}
|
||||||
|
|
||||||
assert(!elei || ops[elei]);
|
assert(!elei || ops[elei]);
|
||||||
|
|
||||||
@@ -491,21 +504,25 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int
|
|||||||
|
|
||||||
// It's a subgroup if it starts with '('
|
// It's a subgroup if it starts with '('
|
||||||
if ('(' == pattern[offset]) {
|
if ('(' == pattern[offset]) {
|
||||||
if ((offset = c2_parse_grp(pattern, offset + 1, pele, level + 1)) < 0)
|
if ((offset = c2_parse_grp(pattern, offset + 1, pele, level + 1)) < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Otherwise it's a leaf
|
// Otherwise it's a leaf
|
||||||
else {
|
else {
|
||||||
if ((offset = c2_parse_target(pattern, offset, pele)) < 0)
|
if ((offset = c2_parse_target(pattern, offset, pele)) < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
assert(!pele->isbranch && !c2_ptr_isempty(*pele));
|
assert(!pele->isbranch && !c2_ptr_isempty(*pele));
|
||||||
|
|
||||||
if ((offset = c2_parse_op(pattern, offset, pele)) < 0)
|
if ((offset = c2_parse_op(pattern, offset, pele)) < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
if ((offset = c2_parse_pattern(pattern, offset, pele)) < 0)
|
if ((offset = c2_parse_pattern(pattern, offset, pele)) < 0) {
|
||||||
goto fail;
|
goto fail;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Decrement offset -- we will increment it in loop update
|
// Decrement offset -- we will increment it in loop update
|
||||||
--offset;
|
--offset;
|
||||||
@@ -513,10 +530,11 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int
|
|||||||
// Apply negation
|
// Apply negation
|
||||||
if (neg) {
|
if (neg) {
|
||||||
neg = false;
|
neg = false;
|
||||||
if (pele->isbranch)
|
if (pele->isbranch) {
|
||||||
pele->b->neg = !pele->b->neg;
|
pele->b->neg = !pele->b->neg;
|
||||||
else
|
} else {
|
||||||
pele->l->neg = !pele->l->neg;
|
pele->l->neg = !pele->l->neg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
next_expected = false;
|
next_expected = false;
|
||||||
@@ -525,10 +543,12 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wrong end character?
|
// Wrong end character?
|
||||||
if (pattern[offset] && !endchar)
|
if (pattern[offset] && !endchar) {
|
||||||
c2_error("Expected end of string but found '%c'.", pattern[offset]);
|
c2_error("Expected end of string but found '%c'.", pattern[offset]);
|
||||||
if (!pattern[offset] && endchar)
|
}
|
||||||
|
if (!pattern[offset] && endchar) {
|
||||||
c2_error("Expected '%c' but found end of string.", endchar);
|
c2_error("Expected '%c' but found end of string.", endchar);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle end of group
|
// Handle end of group
|
||||||
if (!elei) {
|
if (!elei) {
|
||||||
@@ -544,8 +564,9 @@ static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int
|
|||||||
|
|
||||||
*presult = eles[0];
|
*presult = eles[0];
|
||||||
|
|
||||||
if (')' == pattern[offset])
|
if (')' == pattern[offset]) {
|
||||||
++offset;
|
++offset;
|
||||||
|
}
|
||||||
|
|
||||||
return offset;
|
return offset;
|
||||||
|
|
||||||
@@ -778,11 +799,11 @@ static int c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult) {
|
|||||||
|
|
||||||
// Parse operator
|
// Parse operator
|
||||||
while ('=' == pattern[offset] || '>' == pattern[offset] || '<' == pattern[offset]) {
|
while ('=' == pattern[offset] || '>' == pattern[offset] || '<' == pattern[offset]) {
|
||||||
if ('=' == pattern[offset] && C2_L_OGT == pleaf->op)
|
if ('=' == pattern[offset] && C2_L_OGT == pleaf->op) {
|
||||||
pleaf->op = C2_L_OGTEQ;
|
pleaf->op = C2_L_OGTEQ;
|
||||||
else if ('=' == pattern[offset] && C2_L_OLT == pleaf->op)
|
} else if ('=' == pattern[offset] && C2_L_OLT == pleaf->op) {
|
||||||
pleaf->op = C2_L_OLTEQ;
|
pleaf->op = C2_L_OLTEQ;
|
||||||
else if (pleaf->op) {
|
} else if (pleaf->op) {
|
||||||
c2_error("Duplicate operator.");
|
c2_error("Duplicate operator.");
|
||||||
} else {
|
} else {
|
||||||
switch (pattern[offset]) {
|
switch (pattern[offset]) {
|
||||||
@@ -797,9 +818,10 @@ static int c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for problems
|
// Check for problems
|
||||||
if (C2_L_OEQ != pleaf->op && (pleaf->match || pleaf->match_ignorecase))
|
if (C2_L_OEQ != pleaf->op && (pleaf->match || pleaf->match_ignorecase)) {
|
||||||
c2_error("Exists/greater-than/less-than operators cannot have a "
|
c2_error("Exists/greater-than/less-than operators cannot have a "
|
||||||
"qualifier.");
|
"qualifier.");
|
||||||
|
}
|
||||||
|
|
||||||
return offset;
|
return offset;
|
||||||
|
|
||||||
@@ -891,9 +913,10 @@ static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult)
|
|||||||
char *pstr = NULL;
|
char *pstr = NULL;
|
||||||
long val = strtol(
|
long val = strtol(
|
||||||
tstr, &pstr, ('o' == pattern[offset] ? 8 : 16));
|
tstr, &pstr, ('o' == pattern[offset] ? 8 : 16));
|
||||||
if (pstr != &tstr[2] || val <= 0)
|
if (pstr != &tstr[2] || val <= 0) {
|
||||||
c2_error("Invalid octal/hex escape "
|
c2_error("Invalid octal/hex escape "
|
||||||
"sequence.");
|
"sequence.");
|
||||||
|
}
|
||||||
*(ptptnstr++) = to_char_checked(val);
|
*(ptptnstr++) = to_char_checked(val);
|
||||||
offset += 2;
|
offset += 2;
|
||||||
break;
|
break;
|
||||||
@@ -904,8 +927,9 @@ static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult)
|
|||||||
*(ptptnstr++) = pattern[offset];
|
*(ptptnstr++) = pattern[offset];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!pattern[offset])
|
if (!pattern[offset]) {
|
||||||
c2_error("Premature end of pattern string.");
|
c2_error("Premature end of pattern string.");
|
||||||
|
}
|
||||||
++offset;
|
++offset;
|
||||||
*ptptnstr = '\0';
|
*ptptnstr = '\0';
|
||||||
pleaf->ptnstr = strdup(tptnstr);
|
pleaf->ptnstr = strdup(tptnstr);
|
||||||
@@ -914,27 +938,32 @@ static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult)
|
|||||||
|
|
||||||
C2H_SKIP_SPACES();
|
C2H_SKIP_SPACES();
|
||||||
|
|
||||||
if (!pleaf->ptntype)
|
if (!pleaf->ptntype) {
|
||||||
c2_error("Invalid pattern type.");
|
c2_error("Invalid pattern type.");
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the type is correct
|
// Check if the type is correct
|
||||||
if (!(((C2_L_TSTRING == pleaf->type || C2_L_TATOM == pleaf->type) &&
|
if (!(((C2_L_TSTRING == pleaf->type || C2_L_TATOM == pleaf->type) &&
|
||||||
C2_L_PTSTRING == pleaf->ptntype) ||
|
C2_L_PTSTRING == pleaf->ptntype) ||
|
||||||
((C2_L_TCARDINAL == pleaf->type || C2_L_TWINDOW == pleaf->type ||
|
((C2_L_TCARDINAL == pleaf->type || C2_L_TWINDOW == pleaf->type ||
|
||||||
C2_L_TDRAWABLE == pleaf->type) &&
|
C2_L_TDRAWABLE == pleaf->type) &&
|
||||||
C2_L_PTINT == pleaf->ptntype)))
|
C2_L_PTINT == pleaf->ptntype))) {
|
||||||
c2_error("Pattern type incompatible with target type.");
|
c2_error("Pattern type incompatible with target type.");
|
||||||
|
}
|
||||||
|
|
||||||
if (C2_L_PTINT == pleaf->ptntype && pleaf->match)
|
if (C2_L_PTINT == pleaf->ptntype && pleaf->match) {
|
||||||
c2_error("Integer/boolean pattern cannot have operator qualifiers.");
|
c2_error("Integer/boolean pattern cannot have operator qualifiers.");
|
||||||
|
}
|
||||||
|
|
||||||
if (C2_L_PTINT == pleaf->ptntype && pleaf->match_ignorecase)
|
if (C2_L_PTINT == pleaf->ptntype && pleaf->match_ignorecase) {
|
||||||
c2_error("Integer/boolean pattern cannot have flags.");
|
c2_error("Integer/boolean pattern cannot have flags.");
|
||||||
|
}
|
||||||
|
|
||||||
if (C2_L_PTSTRING == pleaf->ptntype &&
|
if (C2_L_PTSTRING == pleaf->ptntype &&
|
||||||
(C2_L_OGT == pleaf->op || C2_L_OGTEQ == pleaf->op || C2_L_OLT == pleaf->op ||
|
(C2_L_OGT == pleaf->op || C2_L_OGTEQ == pleaf->op || C2_L_OLT == pleaf->op ||
|
||||||
C2_L_OLTEQ == pleaf->op))
|
C2_L_OLTEQ == pleaf->op)) {
|
||||||
c2_error("String pattern cannot have an arithmetic operator.");
|
c2_error("String pattern cannot have an arithmetic operator.");
|
||||||
|
}
|
||||||
|
|
||||||
return offset;
|
return offset;
|
||||||
|
|
||||||
@@ -962,7 +991,7 @@ static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult) {
|
|||||||
|
|
||||||
// Determine the pattern target
|
// Determine the pattern target
|
||||||
#define TGTFILL(pdefid) \
|
#define TGTFILL(pdefid) \
|
||||||
(pleaf->predef = pdefid, pleaf->type = C2_PREDEFS[pdefid].type, \
|
(pleaf->predef = (pdefid), pleaf->type = C2_PREDEFS[pdefid].type, \
|
||||||
pleaf->format = C2_PREDEFS[pdefid].format)
|
pleaf->format = C2_PREDEFS[pdefid].format)
|
||||||
switch (pattern[offset]) {
|
switch (pattern[offset]) {
|
||||||
case 'n': TGTFILL(C2_L_PNAME); break;
|
case 'n': TGTFILL(C2_L_PNAME); break;
|
||||||
@@ -1018,7 +1047,7 @@ static bool c2_l_postprocess(session_t *ps, c2_l_t *pleaf) {
|
|||||||
|
|
||||||
// Get target atom if it's not a predefined one
|
// Get target atom if it's not a predefined one
|
||||||
if (pleaf->predef == C2_L_PUNDEFINED) {
|
if (pleaf->predef == C2_L_PUNDEFINED) {
|
||||||
pleaf->tgtatom = get_atom(ps->atoms, pleaf->tgt);
|
pleaf->tgtatom = get_atom(ps->atoms, pleaf->tgt, ps->c.c);
|
||||||
if (!pleaf->tgtatom) {
|
if (!pleaf->tgtatom) {
|
||||||
log_error("Failed to get atom for target \"%s\".", pleaf->tgt);
|
log_error("Failed to get atom for target \"%s\".", pleaf->tgt);
|
||||||
return false;
|
return false;
|
||||||
@@ -1173,9 +1202,8 @@ c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f) {
|
|||||||
static const char *c2h_dump_str_tgt(const c2_l_t *pleaf) {
|
static const char *c2h_dump_str_tgt(const c2_l_t *pleaf) {
|
||||||
if (pleaf->predef != C2_L_PUNDEFINED) {
|
if (pleaf->predef != C2_L_PUNDEFINED) {
|
||||||
return C2_PREDEFS[pleaf->predef].name;
|
return C2_PREDEFS[pleaf->predef].name;
|
||||||
} else {
|
|
||||||
return pleaf->tgt;
|
|
||||||
}
|
}
|
||||||
|
return pleaf->tgt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1308,7 +1336,7 @@ static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf) {
|
|||||||
case C2_L_TDRAWABLE: return XCB_ATOM_DRAWABLE;
|
case C2_L_TDRAWABLE: return XCB_ATOM_DRAWABLE;
|
||||||
default: assert(0); break;
|
default: assert(0); break;
|
||||||
}
|
}
|
||||||
unreachable;
|
unreachable();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1352,14 +1380,10 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w
|
|||||||
case C2_L_PWIDTHB: predef_target = w->widthb; break;
|
case C2_L_PWIDTHB: predef_target = w->widthb; break;
|
||||||
case C2_L_PHEIGHTB: predef_target = w->heightb; break;
|
case C2_L_PHEIGHTB: predef_target = w->heightb; break;
|
||||||
case C2_L_PBDW: predef_target = w->g.border_width; break;
|
case C2_L_PBDW: predef_target = w->g.border_width; break;
|
||||||
case C2_L_PFULLSCREEN:
|
case C2_L_PFULLSCREEN: predef_target = w->is_fullscreen; break;
|
||||||
predef_target = win_is_fullscreen(ps, w);
|
|
||||||
break;
|
|
||||||
case C2_L_POVREDIR: predef_target = w->a.override_redirect; break;
|
case C2_L_POVREDIR: predef_target = w->a.override_redirect; break;
|
||||||
case C2_L_PARGB: predef_target = win_has_alpha(w); break;
|
case C2_L_PARGB: predef_target = win_has_alpha(w); break;
|
||||||
case C2_L_PFOCUSED:
|
case C2_L_PFOCUSED: predef_target = win_is_focused_raw(w); break;
|
||||||
predef_target = win_is_focused_raw(ps, w);
|
|
||||||
break;
|
|
||||||
case C2_L_PWMWIN: predef_target = w->wmwin; break;
|
case C2_L_PWMWIN: predef_target = w->wmwin; break;
|
||||||
case C2_L_PBSHAPED: predef_target = w->bounding_shaped; break;
|
case C2_L_PBSHAPED: predef_target = w->bounding_shaped; break;
|
||||||
case C2_L_PROUNDED: predef_target = w->rounded_corners; break;
|
case C2_L_PROUNDED: predef_target = w->rounded_corners; break;
|
||||||
@@ -1378,11 +1402,11 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w
|
|||||||
int word_count = 1;
|
int word_count = 1;
|
||||||
if (pleaf->index < 0) {
|
if (pleaf->index < 0) {
|
||||||
// Get length of property in 32-bit multiples
|
// Get length of property in 32-bit multiples
|
||||||
auto prop_info = x_get_prop_info(ps->c, wid, pleaf->tgtatom);
|
auto prop_info = x_get_prop_info(&ps->c, wid, pleaf->tgtatom);
|
||||||
word_count = to_int_checked((prop_info.length + 4 - 1) / 4);
|
word_count = to_int_checked((prop_info.length + 4 - 1) / 4);
|
||||||
}
|
}
|
||||||
winprop_t prop = x_get_prop_with_offset(
|
winprop_t prop = x_get_prop_with_offset(
|
||||||
ps->c, wid, pleaf->tgtatom, idx, word_count,
|
&ps->c, wid, pleaf->tgtatom, idx, word_count,
|
||||||
c2_get_atom_type(pleaf), pleaf->format);
|
c2_get_atom_type(pleaf), pleaf->format);
|
||||||
|
|
||||||
ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1));
|
ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1));
|
||||||
@@ -1455,11 +1479,11 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w
|
|||||||
int word_count = 1;
|
int word_count = 1;
|
||||||
if (pleaf->index < 0) {
|
if (pleaf->index < 0) {
|
||||||
// Get length of property in 32-bit multiples
|
// Get length of property in 32-bit multiples
|
||||||
auto prop_info = x_get_prop_info(ps->c, wid, pleaf->tgtatom);
|
auto prop_info = x_get_prop_info(&ps->c, wid, pleaf->tgtatom);
|
||||||
word_count = to_int_checked((prop_info.length + 4 - 1) / 4);
|
word_count = to_int_checked((prop_info.length + 4 - 1) / 4);
|
||||||
}
|
}
|
||||||
winprop_t prop = x_get_prop_with_offset(
|
winprop_t prop = x_get_prop_with_offset(
|
||||||
ps->c, wid, pleaf->tgtatom, idx, word_count,
|
&ps->c, wid, pleaf->tgtatom, idx, word_count,
|
||||||
c2_get_atom_type(pleaf), pleaf->format);
|
c2_get_atom_type(pleaf), pleaf->format);
|
||||||
|
|
||||||
ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1));
|
ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1));
|
||||||
@@ -1470,7 +1494,7 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w
|
|||||||
xcb_atom_t atom = (xcb_atom_t)winprop_get_int(prop, i);
|
xcb_atom_t atom = (xcb_atom_t)winprop_get_int(prop, i);
|
||||||
if (atom) {
|
if (atom) {
|
||||||
xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply(
|
xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply(
|
||||||
ps->c, xcb_get_atom_name(ps->c, atom), NULL);
|
ps->c.c, xcb_get_atom_name(ps->c.c, atom), NULL);
|
||||||
if (reply) {
|
if (reply) {
|
||||||
targets[i] = targets_free_inner[i] = strndup(
|
targets[i] = targets_free_inner[i] = strndup(
|
||||||
xcb_get_atom_name_name(reply),
|
xcb_get_atom_name_name(reply),
|
||||||
@@ -1485,7 +1509,8 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w
|
|||||||
else {
|
else {
|
||||||
char **strlst = NULL;
|
char **strlst = NULL;
|
||||||
int nstr = 0;
|
int nstr = 0;
|
||||||
if (wid_get_text_prop(ps, wid, pleaf->tgtatom, &strlst, &nstr)) {
|
if (wid_get_text_prop(&ps->c, ps->atoms, wid, pleaf->tgtatom,
|
||||||
|
&strlst, &nstr)) {
|
||||||
if (pleaf->index < 0 && nstr > 0 && strlen(strlst[0]) > 0) {
|
if (pleaf->index < 0 && nstr > 0 && strlen(strlst[0]) > 0) {
|
||||||
ntargets = to_u32_checked(nstr);
|
ntargets = to_u32_checked(nstr);
|
||||||
targets = (const char **)strlst;
|
targets = (const char **)strlst;
|
||||||
@@ -1599,8 +1624,9 @@ static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_p
|
|||||||
if (cond.isbranch) {
|
if (cond.isbranch) {
|
||||||
const c2_b_t *pb = cond.b;
|
const c2_b_t *pb = cond.b;
|
||||||
|
|
||||||
if (!pb)
|
if (!pb) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
error = false;
|
error = false;
|
||||||
|
|
||||||
@@ -1630,8 +1656,9 @@ static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_p
|
|||||||
else {
|
else {
|
||||||
const c2_l_t *pleaf = cond.l;
|
const c2_l_t *pleaf = cond.l;
|
||||||
|
|
||||||
if (!pleaf)
|
if (!pleaf) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
c2_match_once_leaf(ps, w, pleaf, &result, &error);
|
c2_match_once_leaf(ps, w, pleaf, &result, &error);
|
||||||
|
|
||||||
@@ -1651,11 +1678,13 @@ static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_p
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Postprocess the result
|
// Postprocess the result
|
||||||
if (error)
|
if (error) {
|
||||||
result = false;
|
result = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (cond.isbranch ? cond.b->neg : cond.l->neg)
|
if (cond.isbranch ? cond.b->neg : cond.l->neg) {
|
||||||
result = !result;
|
result = !result;
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -1673,8 +1702,9 @@ bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condl
|
|||||||
// Then go through the whole linked list
|
// Then go through the whole linked list
|
||||||
for (; condlst; condlst = condlst->next) {
|
for (; condlst; condlst = condlst->next) {
|
||||||
if (c2_match_once(ps, w, condlst->ptr)) {
|
if (c2_match_once(ps, w, condlst->ptr)) {
|
||||||
if (pdata)
|
if (pdata) {
|
||||||
*pdata = condlst->data;
|
*pdata = condlst->data;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
96
src/cache.c
96
src/cache.c
@@ -1,95 +1,43 @@
|
|||||||
#include <uthash.h>
|
#include <uthash.h>
|
||||||
|
|
||||||
#include "compiler.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "cache.h"
|
#include "cache.h"
|
||||||
|
|
||||||
struct cache_entry {
|
struct cache_handle *cache_get(struct cache *c, const char *key) {
|
||||||
char *key;
|
struct cache_handle *e;
|
||||||
void *value;
|
|
||||||
UT_hash_handle hh;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct cache {
|
|
||||||
cache_getter_t getter;
|
|
||||||
cache_free_t free;
|
|
||||||
void *user_data;
|
|
||||||
struct cache_entry *entries;
|
|
||||||
};
|
|
||||||
|
|
||||||
void cache_set(struct cache *c, const char *key, void *data) {
|
|
||||||
struct cache_entry *e = NULL;
|
|
||||||
HASH_FIND_STR(c->entries, key, e);
|
HASH_FIND_STR(c->entries, key, e);
|
||||||
CHECK(!e);
|
return e;
|
||||||
|
|
||||||
e = ccalloc(1, struct cache_entry);
|
|
||||||
e->key = strdup(key);
|
|
||||||
e->value = data;
|
|
||||||
HASH_ADD_STR(c->entries, key, e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void *cache_get(struct cache *c, const char *key, int *err) {
|
int cache_get_or_fetch(struct cache *c, const char *key, struct cache_handle **value,
|
||||||
struct cache_entry *e;
|
void *user_data, cache_getter_t getter) {
|
||||||
HASH_FIND_STR(c->entries, key, e);
|
*value = cache_get(c, key);
|
||||||
if (e) {
|
if (*value) {
|
||||||
return e->value;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int tmperr;
|
int err = getter(c, key, value, user_data);
|
||||||
if (!err) {
|
assert(err <= 0);
|
||||||
err = &tmperr;
|
if (err < 0) {
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
(*value)->key = strdup(key);
|
||||||
|
|
||||||
*err = 0;
|
HASH_ADD_STR(c->entries, key, *value);
|
||||||
e = ccalloc(1, struct cache_entry);
|
return 1;
|
||||||
e->key = strdup(key);
|
|
||||||
e->value = c->getter(c->user_data, key, err);
|
|
||||||
if (*err) {
|
|
||||||
free(e->key);
|
|
||||||
free(e);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
HASH_ADD_STR(c->entries, key, e);
|
|
||||||
return e->value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void _cache_invalidate(struct cache *c, struct cache_entry *e) {
|
static inline void
|
||||||
if (c->free) {
|
cache_invalidate_impl(struct cache *c, struct cache_handle *e, cache_free_t free_fn) {
|
||||||
c->free(c->user_data, e->value);
|
|
||||||
}
|
|
||||||
free(e->key);
|
free(e->key);
|
||||||
HASH_DEL(c->entries, e);
|
HASH_DEL(c->entries, e);
|
||||||
free(e);
|
if (free_fn) {
|
||||||
}
|
free_fn(c, e);
|
||||||
|
|
||||||
void cache_invalidate(struct cache *c, const char *key) {
|
|
||||||
struct cache_entry *e;
|
|
||||||
HASH_FIND_STR(c->entries, key, e);
|
|
||||||
|
|
||||||
if (e) {
|
|
||||||
_cache_invalidate(c, e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cache_invalidate_all(struct cache *c) {
|
void cache_invalidate_all(struct cache *c, cache_free_t free_fn) {
|
||||||
struct cache_entry *e, *tmpe;
|
struct cache_handle *e, *tmpe;
|
||||||
HASH_ITER(hh, c->entries, e, tmpe) {
|
HASH_ITER(hh, c->entries, e, tmpe) {
|
||||||
_cache_invalidate(c, e);
|
cache_invalidate_impl(c, e, free_fn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void *cache_free(struct cache *c) {
|
|
||||||
void *ret = c->user_data;
|
|
||||||
cache_invalidate_all(c);
|
|
||||||
free(c);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct cache *new_cache(void *ud, cache_getter_t getter, cache_free_t f) {
|
|
||||||
auto c = ccalloc(1, struct cache);
|
|
||||||
c->user_data = ud;
|
|
||||||
c->getter = getter;
|
|
||||||
c->free = f;
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|||||||
57
src/cache.h
57
src/cache.h
@@ -1,32 +1,43 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <uthash.h>
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
#define cache_entry(ptr, type, member) container_of(ptr, type, member)
|
||||||
|
|
||||||
struct cache;
|
struct cache;
|
||||||
|
struct cache_handle;
|
||||||
|
|
||||||
typedef void *(*cache_getter_t)(void *user_data, const char *key, int *err);
|
/// User-provided function to fetch a value for the cache, when it's not present.
|
||||||
typedef void (*cache_free_t)(void *user_data, void *data);
|
/// Should return 0 if the value is fetched successfully, and a negative number if the
|
||||||
|
/// value cannot be fetched. Getter doesn't need to initialize fields of `struct
|
||||||
|
/// cache_handle`.
|
||||||
|
typedef int (*cache_getter_t)(struct cache *, const char *key,
|
||||||
|
struct cache_handle **value, void *user_data);
|
||||||
|
typedef void (*cache_free_t)(struct cache *, struct cache_handle *value);
|
||||||
|
|
||||||
/// Create a cache with `getter`, and a free function `f` which is used to free the cache
|
struct cache {
|
||||||
/// value when they are invalidated.
|
struct cache_handle *entries;
|
||||||
///
|
};
|
||||||
/// `user_data` will be passed to `getter` and `f` when they are called.
|
|
||||||
struct cache *new_cache(void *user_data, cache_getter_t getter, cache_free_t f);
|
|
||||||
|
|
||||||
/// Fetch a value from the cache. If the value doesn't present in the cache yet, the
|
static const struct cache CACHE_INIT = {NULL};
|
||||||
|
|
||||||
|
struct cache_handle {
|
||||||
|
char *key;
|
||||||
|
UT_hash_handle hh;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Get a value from the cache. If the value doesn't present in the cache yet, the
|
||||||
/// getter will be called, and the returned value will be stored into the cache.
|
/// getter will be called, and the returned value will be stored into the cache.
|
||||||
void *cache_get(struct cache *, const char *key, int *err);
|
/// Returns 0 if the value is already present in the cache, 1 if the value is fetched
|
||||||
|
/// successfully, and a negative number if the value cannot be fetched.
|
||||||
|
int cache_get_or_fetch(struct cache *, const char *key, struct cache_handle **value,
|
||||||
|
void *user_data, cache_getter_t getter);
|
||||||
|
|
||||||
/// Invalidate a value in the cache.
|
/// Get a value from the cache. If the value doesn't present in the cache, NULL will be
|
||||||
void cache_invalidate(struct cache *, const char *key);
|
/// returned.
|
||||||
|
struct cache_handle *cache_get(struct cache *, const char *key);
|
||||||
|
|
||||||
/// Invalidate all values in the cache.
|
/// Invalidate all values in the cache. After this call, `struct cache` holds no allocated
|
||||||
void cache_invalidate_all(struct cache *);
|
/// memory, and can be discarded.
|
||||||
|
void cache_invalidate_all(struct cache *, cache_free_t free_fn);
|
||||||
/// Invalidate all values in the cache and free it. Returns the user data passed to
|
|
||||||
/// `new_cache`
|
|
||||||
void *cache_free(struct cache *);
|
|
||||||
|
|
||||||
/// Insert a key-value pair into the cache. Only used for internal testing. Takes
|
|
||||||
/// ownership of `data`
|
|
||||||
///
|
|
||||||
/// If `key` already exists in the cache, this function will abort the program.
|
|
||||||
void cache_set(struct cache *c, const char *key, void *data);
|
|
||||||
|
|||||||
118
src/common.h
118
src/common.h
@@ -58,6 +58,7 @@
|
|||||||
#include "list.h"
|
#include "list.h"
|
||||||
#include "region.h"
|
#include "region.h"
|
||||||
#include "render.h"
|
#include "render.h"
|
||||||
|
#include "statistics.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "win_defs.h"
|
#include "win_defs.h"
|
||||||
@@ -83,18 +84,6 @@ struct glx_session;
|
|||||||
struct atom;
|
struct atom;
|
||||||
struct conv;
|
struct conv;
|
||||||
|
|
||||||
enum pending_reply_action {
|
|
||||||
PENDING_REPLY_ACTION_IGNORE,
|
|
||||||
PENDING_REPLY_ACTION_ABORT,
|
|
||||||
PENDING_REPLY_ACTION_DEBUG_ABORT,
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct pending_reply {
|
|
||||||
struct pending_reply *next;
|
|
||||||
unsigned long sequence;
|
|
||||||
enum pending_reply_action action;
|
|
||||||
} pending_reply_t;
|
|
||||||
|
|
||||||
#ifdef CONFIG_OPENGL
|
#ifdef CONFIG_OPENGL
|
||||||
#ifdef DEBUG_GLX_DEBUG_CONTEXT
|
#ifdef DEBUG_GLX_DEBUG_CONTEXT
|
||||||
typedef GLXContext (*f_glXCreateContextAttribsARB)(Display *dpy, GLXFBConfig config,
|
typedef GLXContext (*f_glXCreateContextAttribsARB)(Display *dpy, GLXFBConfig config,
|
||||||
@@ -150,8 +139,6 @@ typedef struct session {
|
|||||||
// === Event handlers ===
|
// === Event handlers ===
|
||||||
/// ev_io for X connection
|
/// ev_io for X connection
|
||||||
ev_io xiow;
|
ev_io xiow;
|
||||||
/// Timer for checking DPMS power level
|
|
||||||
ev_timer dpms_check_timer;
|
|
||||||
/// Timeout for delayed unredirection.
|
/// Timeout for delayed unredirection.
|
||||||
ev_timer unredir_timer;
|
ev_timer unredir_timer;
|
||||||
/// Timer for fading
|
/// Timer for fading
|
||||||
@@ -161,6 +148,8 @@ typedef struct session {
|
|||||||
/// Use an ev_idle callback for drawing
|
/// Use an ev_idle callback for drawing
|
||||||
/// So we only start drawing when events are processed
|
/// So we only start drawing when events are processed
|
||||||
ev_idle draw_idle;
|
ev_idle draw_idle;
|
||||||
|
/// Use an ev_timer callback for drawing
|
||||||
|
ev_timer draw_timer;
|
||||||
/// Called every time we have timeouts or new data on socket,
|
/// Called every time we have timeouts or new data on socket,
|
||||||
/// so we can be sure if xcb read from X socket at anytime during event
|
/// so we can be sure if xcb read from X socket at anytime during event
|
||||||
/// handling, we will not left any event unhandled in the queue
|
/// handling, we will not left any event unhandled in the queue
|
||||||
@@ -185,30 +174,13 @@ typedef struct session {
|
|||||||
struct shader_info *shaders;
|
struct shader_info *shaders;
|
||||||
|
|
||||||
// === Display related ===
|
// === Display related ===
|
||||||
|
/// X connection
|
||||||
|
struct x_connection c;
|
||||||
/// Whether the X server is grabbed by us
|
/// Whether the X server is grabbed by us
|
||||||
bool server_grabbed;
|
bool server_grabbed;
|
||||||
/// Display in use.
|
|
||||||
Display *dpy;
|
|
||||||
/// Previous handler of X errors
|
|
||||||
XErrorHandler previous_xerror_handler;
|
|
||||||
/// Default screen.
|
|
||||||
int scr;
|
|
||||||
/// XCB connection.
|
|
||||||
xcb_connection_t *c;
|
|
||||||
/// Default visual.
|
|
||||||
xcb_visualid_t vis;
|
|
||||||
/// Default depth.
|
|
||||||
int depth;
|
|
||||||
/// Root window.
|
|
||||||
xcb_window_t root;
|
|
||||||
/// Height of root window.
|
|
||||||
int root_height;
|
|
||||||
/// Width of root window.
|
/// Width of root window.
|
||||||
int root_width;
|
int root_width;
|
||||||
// Damage of root window.
|
int root_height;
|
||||||
// Damage root_damage;
|
|
||||||
int selmon_center_x;
|
|
||||||
int selmon_center_y;
|
|
||||||
/// X Composite overlay window.
|
/// X Composite overlay window.
|
||||||
xcb_window_t overlay;
|
xcb_window_t overlay;
|
||||||
/// The target window for debug mode
|
/// The target window for debug mode
|
||||||
@@ -218,7 +190,7 @@ typedef struct session {
|
|||||||
/// Picture of the root window background.
|
/// Picture of the root window background.
|
||||||
paint_t root_tile_paint;
|
paint_t root_tile_paint;
|
||||||
/// The backend data the root pixmap bound to
|
/// The backend data the root pixmap bound to
|
||||||
void *root_image;
|
image_handle root_image;
|
||||||
/// A region of the size of the screen.
|
/// A region of the size of the screen.
|
||||||
region_t screen_reg;
|
region_t screen_reg;
|
||||||
/// Picture of root window. Destination of painting in no-DBE painting
|
/// Picture of root window. Destination of painting in no-DBE painting
|
||||||
@@ -236,7 +208,7 @@ typedef struct session {
|
|||||||
/// Custom GLX program used for painting window.
|
/// Custom GLX program used for painting window.
|
||||||
// XXX should be in struct glx_session
|
// XXX should be in struct glx_session
|
||||||
glx_prog_main_t glx_prog_win;
|
glx_prog_main_t glx_prog_win;
|
||||||
struct glx_fbconfig_info *argb_fbconfig;
|
struct glx_fbconfig_info argb_fbconfig;
|
||||||
#endif
|
#endif
|
||||||
/// Sync fence to sync draw operations
|
/// Sync fence to sync draw operations
|
||||||
xcb_sync_fence_t sync_fence;
|
xcb_sync_fence_t sync_fence;
|
||||||
@@ -244,6 +216,22 @@ typedef struct session {
|
|||||||
bool first_frame;
|
bool first_frame;
|
||||||
/// Whether screen has been turned off
|
/// Whether screen has been turned off
|
||||||
bool screen_is_off;
|
bool screen_is_off;
|
||||||
|
/// When last MSC event happened, in useconds.
|
||||||
|
uint64_t last_msc_instant;
|
||||||
|
/// The last MSC number
|
||||||
|
uint64_t last_msc;
|
||||||
|
/// The delay between when the last frame was scheduled to be rendered, and when
|
||||||
|
/// the render actually started.
|
||||||
|
uint64_t last_schedule_delay;
|
||||||
|
/// When do we want our next frame to start rendering.
|
||||||
|
uint64_t next_render;
|
||||||
|
/// Whether we can perform frame pacing.
|
||||||
|
bool frame_pacing;
|
||||||
|
/// Vblank event scheduler
|
||||||
|
struct vblank_scheduler *vblank_scheduler;
|
||||||
|
|
||||||
|
/// Render statistics
|
||||||
|
struct render_statistics render_stats;
|
||||||
|
|
||||||
// === Operation related ===
|
// === Operation related ===
|
||||||
/// Flags related to the root window
|
/// Flags related to the root window
|
||||||
@@ -252,8 +240,18 @@ typedef struct session {
|
|||||||
options_t o;
|
options_t o;
|
||||||
/// Whether we have hit unredirection timeout.
|
/// Whether we have hit unredirection timeout.
|
||||||
bool tmout_unredir_hit;
|
bool tmout_unredir_hit;
|
||||||
/// Whether we need to redraw the screen
|
/// If the backend is busy. This means two things:
|
||||||
bool redraw_needed;
|
/// Either the backend is currently rendering a frame, or a frame has been
|
||||||
|
/// rendered but has yet to be presented. In either case, we should not start
|
||||||
|
/// another render right now. As if we start issuing rendering commands now, we
|
||||||
|
/// will have to wait for either the current render to finish, or the current
|
||||||
|
/// back buffer to become available again. In either case, we will be wasting
|
||||||
|
/// time.
|
||||||
|
bool backend_busy;
|
||||||
|
/// Whether a render is queued. This generally means there are pending updates
|
||||||
|
/// to the screen that's neither included in the current render, nor on the
|
||||||
|
/// screen.
|
||||||
|
bool render_queued;
|
||||||
|
|
||||||
/// Cache a xfixes region so we don't need to allocate it every time.
|
/// Cache a xfixes region so we don't need to allocate it every time.
|
||||||
/// A workaround for yshui/picom#301
|
/// A workaround for yshui/picom#301
|
||||||
@@ -281,7 +279,7 @@ typedef struct session {
|
|||||||
struct x_convolution_kernel **blur_kerns_cache;
|
struct x_convolution_kernel **blur_kerns_cache;
|
||||||
/// If we should quit
|
/// If we should quit
|
||||||
bool quit : 1;
|
bool quit : 1;
|
||||||
// TODO(yshui) use separate flags for dfferent kinds of updates so we don't
|
// TODO(yshui) use separate flags for different kinds of updates so we don't
|
||||||
// waste our time.
|
// waste our time.
|
||||||
/// Whether there are pending updates, like window creation, etc.
|
/// Whether there are pending updates, like window creation, etc.
|
||||||
bool pending_updates : 1;
|
bool pending_updates : 1;
|
||||||
@@ -374,12 +372,8 @@ typedef struct session {
|
|||||||
int glx_event;
|
int glx_event;
|
||||||
/// Error base number for X GLX extension.
|
/// Error base number for X GLX extension.
|
||||||
int glx_error;
|
int glx_error;
|
||||||
/// Whether X Xinerama extension exists.
|
/// Information about monitors.
|
||||||
bool xinerama_exists;
|
struct x_monitors monitors;
|
||||||
/// Xinerama screen regions.
|
|
||||||
region_t *xinerama_scr_regs;
|
|
||||||
/// Number of Xinerama screens.
|
|
||||||
int xinerama_nscrs;
|
|
||||||
/// Whether X Sync extension exists.
|
/// Whether X Sync extension exists.
|
||||||
bool xsync_exists;
|
bool xsync_exists;
|
||||||
/// Event base number for X Sync extension.
|
/// Event base number for X Sync extension.
|
||||||
@@ -477,7 +471,7 @@ static inline struct timespec get_time_timespec(void) {
|
|||||||
* Return the painting target window.
|
* Return the painting target window.
|
||||||
*/
|
*/
|
||||||
static inline xcb_window_t get_tgt_window(session_t *ps) {
|
static inline xcb_window_t get_tgt_window(session_t *ps) {
|
||||||
return ps->overlay != XCB_NONE ? ps->overlay : ps->root;
|
return ps->overlay != XCB_NONE ? ps->overlay : ps->c.screen_info->root;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -487,35 +481,6 @@ static inline bool bkend_use_glx(session_t *ps) {
|
|||||||
return BKEND_GLX == ps->o.backend || BKEND_XR_GLX_HYBRID == ps->o.backend;
|
return BKEND_GLX == ps->o.backend || BKEND_XR_GLX_HYBRID == ps->o.backend;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
set_reply_action(session_t *ps, uint32_t sequence, enum pending_reply_action action) {
|
|
||||||
auto i = cmalloc(pending_reply_t);
|
|
||||||
if (!i) {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
i->sequence = sequence;
|
|
||||||
i->next = 0;
|
|
||||||
i->action = action;
|
|
||||||
*ps->pending_reply_tail = i;
|
|
||||||
ps->pending_reply_tail = &i->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ignore X errors caused by given X request.
|
|
||||||
*/
|
|
||||||
static inline void set_ignore_cookie(session_t *ps, xcb_void_cookie_t cookie) {
|
|
||||||
if (ps->o.show_all_xerrors) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_reply_action(ps, cookie.sequence, PENDING_REPLY_ACTION_IGNORE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void set_cant_fail_cookie(session_t *ps, xcb_void_cookie_t cookie) {
|
|
||||||
set_reply_action(ps, cookie.sequence, PENDING_REPLY_ACTION_ABORT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a window has a specific property.
|
* Determine if a window has a specific property.
|
||||||
*
|
*
|
||||||
@@ -526,7 +491,8 @@ static inline void set_cant_fail_cookie(session_t *ps, xcb_void_cookie_t cookie)
|
|||||||
*/
|
*/
|
||||||
static inline bool wid_has_prop(const session_t *ps, xcb_window_t w, xcb_atom_t atom) {
|
static inline bool wid_has_prop(const session_t *ps, xcb_window_t w, xcb_atom_t atom) {
|
||||||
auto r = xcb_get_property_reply(
|
auto r = xcb_get_property_reply(
|
||||||
ps->c, xcb_get_property(ps->c, 0, w, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, 0), NULL);
|
ps->c.c,
|
||||||
|
xcb_get_property(ps->c.c, 0, w, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, 0), NULL);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
#if __STDC_VERSION__ <= 201710L
|
||||||
#define auto __auto_type
|
#define auto __auto_type
|
||||||
|
#endif
|
||||||
#define likely(x) __builtin_expect(!!(x), 1)
|
#define likely(x) __builtin_expect(!!(x), 1)
|
||||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||||
#define likely_if(x) if (likely(x))
|
#define likely_if(x) if (likely(x))
|
||||||
@@ -50,7 +52,7 @@
|
|||||||
#else
|
#else
|
||||||
# define attr_warn_unused_result
|
# define attr_warn_unused_result
|
||||||
#endif
|
#endif
|
||||||
// An alias for conveninence
|
// An alias for convenience
|
||||||
#define must_use attr_warn_unused_result
|
#define must_use attr_warn_unused_result
|
||||||
|
|
||||||
#if __has_attribute(nonnull)
|
#if __has_attribute(nonnull)
|
||||||
@@ -101,10 +103,12 @@
|
|||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
#ifndef unreachable
|
||||||
# define unreachable __builtin_unreachable()
|
# if defined(__GNUC__) || defined(__clang__)
|
||||||
#else
|
# define unreachable() assert(false); __builtin_unreachable()
|
||||||
# define unreachable do {} while(0)
|
# else
|
||||||
|
# define unreachable() assert(false); do {} while(0)
|
||||||
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef __has_include
|
#ifndef __has_include
|
||||||
|
|||||||
168
src/config.c
168
src/config.c
@@ -8,6 +8,8 @@
|
|||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -41,10 +43,7 @@ const char *xdg_config_home(void) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
xdgh = cvalloc(strlen(home) + strlen(default_dir) + 1);
|
xdgh = mstrjoin(home, default_dir);
|
||||||
|
|
||||||
strcpy(xdgh, home);
|
|
||||||
strcat(xdgh, default_dir);
|
|
||||||
} else {
|
} else {
|
||||||
xdgh = strdup(xdgh);
|
xdgh = strdup(xdgh);
|
||||||
}
|
}
|
||||||
@@ -133,8 +132,9 @@ bool parse_long(const char *s, long *dest) {
|
|||||||
log_error("Invalid number: %s", s);
|
log_error("Invalid number: %s", s);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
while (isspace((unsigned char)*endptr))
|
while (isspace((unsigned char)*endptr)) {
|
||||||
++endptr;
|
++endptr;
|
||||||
|
}
|
||||||
if (*endptr) {
|
if (*endptr) {
|
||||||
log_error("Trailing characters: %s", s);
|
log_error("Trailing characters: %s", s);
|
||||||
return false;
|
return false;
|
||||||
@@ -182,20 +182,19 @@ const char *parse_readnum(const char *src, double *dest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum blur_method parse_blur_method(const char *src) {
|
enum blur_method parse_blur_method(const char *src) {
|
||||||
|
if (strcmp(src, "box") == 0) {
|
||||||
|
return BLUR_METHOD_BOX;
|
||||||
|
}
|
||||||
|
if (strcmp(src, "dual_kawase") == 0) {
|
||||||
|
return BLUR_METHOD_DUAL_KAWASE;
|
||||||
|
}
|
||||||
|
if (strcmp(src, "gaussian") == 0) {
|
||||||
|
return BLUR_METHOD_GAUSSIAN;
|
||||||
|
}
|
||||||
if (strcmp(src, "kernel") == 0) {
|
if (strcmp(src, "kernel") == 0) {
|
||||||
return BLUR_METHOD_KERNEL;
|
return BLUR_METHOD_KERNEL;
|
||||||
} else if (strcmp(src, "box") == 0) {
|
}
|
||||||
return BLUR_METHOD_BOX;
|
if (strcmp(src, "none") == 0) {
|
||||||
} else if (strcmp(src, "gaussian") == 0) {
|
|
||||||
return BLUR_METHOD_GAUSSIAN;
|
|
||||||
} else if (strcmp(src, "dual_kawase") == 0) {
|
|
||||||
return BLUR_METHOD_DUAL_KAWASE;
|
|
||||||
} else if (strcmp(src, "kawase") == 0) {
|
|
||||||
log_warn("Blur method 'kawase' has been renamed to 'dual_kawase'. "
|
|
||||||
"Interpreted as 'dual_kawase', but this will stop working "
|
|
||||||
"soon.");
|
|
||||||
return BLUR_METHOD_DUAL_KAWASE;
|
|
||||||
} else if (strcmp(src, "none") == 0) {
|
|
||||||
return BLUR_METHOD_NONE;
|
return BLUR_METHOD_NONE;
|
||||||
}
|
}
|
||||||
return BLUR_METHOD_INVALID;
|
return BLUR_METHOD_INVALID;
|
||||||
@@ -216,12 +215,14 @@ conv *parse_blur_kern(const char *src, const char **endptr, bool *hasneg) {
|
|||||||
|
|
||||||
// Get matrix width and height
|
// Get matrix width and height
|
||||||
double val = 0.0;
|
double val = 0.0;
|
||||||
if (src == (pc = parse_readnum(src, &val)))
|
if (src == (pc = parse_readnum(src, &val))) {
|
||||||
goto err1;
|
goto err1;
|
||||||
|
}
|
||||||
src = pc;
|
src = pc;
|
||||||
width = (int)val;
|
width = (int)val;
|
||||||
if (src == (pc = parse_readnum(src, &val)))
|
if (src == (pc = parse_readnum(src, &val))) {
|
||||||
goto err1;
|
goto err1;
|
||||||
|
}
|
||||||
src = pc;
|
src = pc;
|
||||||
height = (int)val;
|
height = (int)val;
|
||||||
|
|
||||||
@@ -234,9 +235,10 @@ conv *parse_blur_kern(const char *src, const char **endptr, bool *hasneg) {
|
|||||||
log_error("Blur kernel width/height must be odd.");
|
log_error("Blur kernel width/height must be odd.");
|
||||||
goto err1;
|
goto err1;
|
||||||
}
|
}
|
||||||
if (width > 16 || height > 16)
|
if (width > 16 || height > 16) {
|
||||||
log_warn("Blur kernel width/height too large, may slow down"
|
log_warn("Blur kernel width/height too large, may slow down"
|
||||||
"rendering, and/or consume lots of memory");
|
"rendering, and/or consume lots of memory");
|
||||||
|
}
|
||||||
|
|
||||||
// Allocate memory
|
// Allocate memory
|
||||||
conv *matrix = cvalloc(sizeof(conv) + (size_t)(width * height) * sizeof(double));
|
conv *matrix = cvalloc(sizeof(conv) + (size_t)(width * height) * sizeof(double));
|
||||||
@@ -362,8 +364,9 @@ struct conv **parse_blur_kern_lst(const char *src, bool *hasneg, int *count) {
|
|||||||
*hasneg = false;
|
*hasneg = false;
|
||||||
for (unsigned int i = 0;
|
for (unsigned int i = 0;
|
||||||
i < sizeof(CONV_KERN_PREDEF) / sizeof(CONV_KERN_PREDEF[0]); ++i) {
|
i < sizeof(CONV_KERN_PREDEF) / sizeof(CONV_KERN_PREDEF[0]); ++i) {
|
||||||
if (!strcmp(CONV_KERN_PREDEF[i].name, src))
|
if (!strcmp(CONV_KERN_PREDEF[i].name, src)) {
|
||||||
return parse_blur_kern_lst(CONV_KERN_PREDEF[i].kern_str, hasneg, count);
|
return parse_blur_kern_lst(CONV_KERN_PREDEF[i].kern_str, hasneg, count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int nkernels = 1;
|
int nkernels = 1;
|
||||||
@@ -509,32 +512,37 @@ parse_geometry_end:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a list of opacity rules.
|
* Parse a list of window rules, prefixed with a number, separated by a ':'
|
||||||
*/
|
*/
|
||||||
bool parse_rule_opacity(c2_lptr_t **res, const char *src) {
|
bool parse_numeric_window_rule(c2_lptr_t **res, const char *src, long min, long max) {
|
||||||
// Find opacity value
|
if (!src) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find numeric value
|
||||||
char *endptr = NULL;
|
char *endptr = NULL;
|
||||||
long val = strtol(src, &endptr, 0);
|
long val = strtol(src, &endptr, 0);
|
||||||
if (!endptr || endptr == src) {
|
if (!endptr || endptr == src) {
|
||||||
log_error("No opacity specified: %s", src);
|
log_error("No number specified: %s", src);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (val > 100 || val < 0) {
|
|
||||||
log_error("Opacity %ld invalid: %s", val, src);
|
if (val < min || val > max) {
|
||||||
|
log_error("Number not in range (%ld <= n <= %ld): %s", min, max, src);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip over spaces
|
// Skip over spaces
|
||||||
while (*endptr && isspace((unsigned char)*endptr))
|
while (*endptr && isspace((unsigned char)*endptr)) {
|
||||||
++endptr;
|
++endptr;
|
||||||
|
}
|
||||||
if (':' != *endptr) {
|
if (':' != *endptr) {
|
||||||
log_error("Opacity terminator not found: %s", src);
|
log_error("Number separator (':') not found: %s", src);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
++endptr;
|
++endptr;
|
||||||
|
|
||||||
// Parse pattern
|
// Parse pattern
|
||||||
// I hope 1-100 is acceptable for (void *)
|
|
||||||
return c2_parse(res, endptr, (void *)val);
|
return c2_parse(res, endptr, (void *)val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,7 +560,7 @@ static char *locate_auxiliary_file_at(const char *base, const char *scope, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a path of an auxiliary file to read, could be a shader file, or any supplimenrary
|
* Get a path of an auxiliary file to read, could be a shader file, or any supplementary
|
||||||
* file.
|
* file.
|
||||||
*
|
*
|
||||||
* Follows the XDG specification to search for the shader file in configuration locations.
|
* Follows the XDG specification to search for the shader file in configuration locations.
|
||||||
@@ -587,15 +595,17 @@ char *locate_auxiliary_file(const char *scope, const char *path, const char *inc
|
|||||||
// Fall back to searching in user config directory
|
// Fall back to searching in user config directory
|
||||||
scoped_charp picom_scope = mstrjoin("/picom/", scope);
|
scoped_charp picom_scope = mstrjoin("/picom/", scope);
|
||||||
scoped_charp config_home = (char *)xdg_config_home();
|
scoped_charp config_home = (char *)xdg_config_home();
|
||||||
char *ret = locate_auxiliary_file_at(config_home, picom_scope, path);
|
if (config_home) {
|
||||||
if (ret) {
|
char *ret = locate_auxiliary_file_at(config_home, picom_scope, path);
|
||||||
return ret;
|
if (ret) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to searching in system config directory
|
// Fall back to searching in system config directory
|
||||||
auto config_dirs = xdg_config_dirs();
|
auto config_dirs = xdg_config_dirs();
|
||||||
for (int i = 0; config_dirs[i]; i++) {
|
for (int i = 0; config_dirs[i]; i++) {
|
||||||
ret = locate_auxiliary_file_at(config_dirs[i], picom_scope, path);
|
char *ret = locate_auxiliary_file_at(config_dirs[i], picom_scope, path);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
free(config_dirs);
|
free(config_dirs);
|
||||||
return ret;
|
return ret;
|
||||||
@@ -603,7 +613,83 @@ char *locate_auxiliary_file(const char *scope, const char *path, const char *inc
|
|||||||
}
|
}
|
||||||
free(config_dirs);
|
free(config_dirs);
|
||||||
|
|
||||||
return ret;
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct debug_options_entry {
|
||||||
|
const char *name;
|
||||||
|
const char **choices;
|
||||||
|
size_t offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
const char *vblank_scheduler_str[] = {
|
||||||
|
[VBLANK_SCHEDULER_PRESENT] = "present",
|
||||||
|
[VBLANK_SCHEDULER_SGI_VIDEO_SYNC] = "sgi_video_sync",
|
||||||
|
[LAST_VBLANK_SCHEDULER] = NULL
|
||||||
|
};
|
||||||
|
static const struct debug_options_entry debug_options_entries[] = {
|
||||||
|
{"smart_frame_pacing", NULL, offsetof(struct debug_options, smart_frame_pacing)},
|
||||||
|
{"force_vblank_sched", vblank_scheduler_str, offsetof(struct debug_options, force_vblank_scheduler)},
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
void parse_debug_option_single(char *setting, struct debug_options *debug_options) {
|
||||||
|
char *equal = strchr(setting, '=');
|
||||||
|
size_t name_len = equal ? (size_t)(equal - setting) : strlen(setting);
|
||||||
|
for (size_t i = 0; i < ARR_SIZE(debug_options_entries); i++) {
|
||||||
|
if (strncmp(setting, debug_options_entries[i].name, name_len) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (debug_options_entries[i].name[name_len] != '\0') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto value = (int *)((void *)debug_options + debug_options_entries[i].offset);
|
||||||
|
if (equal) {
|
||||||
|
const char *const arg = equal + 1;
|
||||||
|
if (debug_options_entries[i].choices != NULL) {
|
||||||
|
for (size_t j = 0; debug_options_entries[i].choices[j]; j++) {
|
||||||
|
if (strcmp(arg, debug_options_entries[i].choices[j]) ==
|
||||||
|
0) {
|
||||||
|
*value = (int)j;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!parse_int(arg, value)) {
|
||||||
|
log_error("Invalid value for debug option %s: %s, it "
|
||||||
|
"will be ignored.",
|
||||||
|
debug_options_entries[i].name, arg);
|
||||||
|
}
|
||||||
|
} else if (debug_options_entries[i].choices == NULL) {
|
||||||
|
*value = 1;
|
||||||
|
} else {
|
||||||
|
log_error(
|
||||||
|
"Missing value for debug option %s, it will be ignored.", setting);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log_error("Invalid debug option: %s", setting);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse debug options from environment variable `PICOM_DEBUG`.
|
||||||
|
void parse_debug_options(struct debug_options *debug_options) {
|
||||||
|
const char *debug = getenv("PICOM_DEBUG");
|
||||||
|
const struct debug_options default_debug_options = {
|
||||||
|
.force_vblank_scheduler = LAST_VBLANK_SCHEDULER,
|
||||||
|
};
|
||||||
|
|
||||||
|
*debug_options = default_debug_options;
|
||||||
|
if (!debug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scoped_charp debug_copy = strdup(debug);
|
||||||
|
char *tmp, *needle = strtok_r(debug_copy, ";", &tmp);
|
||||||
|
while (needle) {
|
||||||
|
parse_debug_option_single(needle, debug_options);
|
||||||
|
needle = strtok_r(NULL, ";", &tmp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -650,11 +736,13 @@ bool parse_rule_window_shader(c2_lptr_t **res, const char *src, const char *incl
|
|||||||
* Add a pattern to a condition linked list.
|
* Add a pattern to a condition linked list.
|
||||||
*/
|
*/
|
||||||
bool condlst_add(c2_lptr_t **pcondlst, const char *pattern) {
|
bool condlst_add(c2_lptr_t **pcondlst, const char *pattern) {
|
||||||
if (!pattern)
|
if (!pattern) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!c2_parse(pcondlst, pattern, NULL))
|
if (!c2_parse(pcondlst, pattern, NULL)) {
|
||||||
exit(1);
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -775,6 +863,7 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
|
|||||||
.logpath = NULL,
|
.logpath = NULL,
|
||||||
|
|
||||||
.use_damage = true,
|
.use_damage = true,
|
||||||
|
.no_frame_pacing = false,
|
||||||
|
|
||||||
.shadow_red = 0.0,
|
.shadow_red = 0.0,
|
||||||
.shadow_green = 0.0,
|
.shadow_green = 0.0,
|
||||||
@@ -785,7 +874,7 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
|
|||||||
.shadow_opacity = .75,
|
.shadow_opacity = .75,
|
||||||
.shadow_blacklist = NULL,
|
.shadow_blacklist = NULL,
|
||||||
.shadow_ignore_shaped = false,
|
.shadow_ignore_shaped = false,
|
||||||
.xinerama_shadow_crop = false,
|
.crop_shadow_to_monitor = false,
|
||||||
.shadow_clip_list = NULL,
|
.shadow_clip_list = NULL,
|
||||||
|
|
||||||
.corner_radius = 0,
|
.corner_radius = 0,
|
||||||
@@ -856,5 +945,6 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
|
|||||||
(void)hasneg;
|
(void)hasneg;
|
||||||
(void)winopt_mask;
|
(void)winopt_mask;
|
||||||
#endif
|
#endif
|
||||||
|
parse_debug_options(&opt->debug_options);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
35
src/config.h
35
src/config.h
@@ -93,6 +93,27 @@ enum blur_method {
|
|||||||
|
|
||||||
typedef struct _c2_lptr c2_lptr_t;
|
typedef struct _c2_lptr c2_lptr_t;
|
||||||
|
|
||||||
|
enum vblank_scheduler_type {
|
||||||
|
/// X Present extension based vblank events
|
||||||
|
VBLANK_SCHEDULER_PRESENT,
|
||||||
|
/// GLX_SGI_video_sync based vblank events
|
||||||
|
VBLANK_SCHEDULER_SGI_VIDEO_SYNC,
|
||||||
|
/// An invalid scheduler, served as a scheduler count, and
|
||||||
|
/// as a sentinel value.
|
||||||
|
LAST_VBLANK_SCHEDULER,
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const char *vblank_scheduler_str[];
|
||||||
|
|
||||||
|
/// Internal, private options for debugging and development use.
|
||||||
|
struct debug_options {
|
||||||
|
/// Try to reduce frame latency by using vblank interval and render time
|
||||||
|
/// estimates. Right now it's not working well across drivers.
|
||||||
|
int smart_frame_pacing;
|
||||||
|
/// Override the vblank scheduler chosen by the compositor.
|
||||||
|
int force_vblank_scheduler;
|
||||||
|
};
|
||||||
|
|
||||||
/// Structure representing all options.
|
/// Structure representing all options.
|
||||||
typedef struct options {
|
typedef struct options {
|
||||||
// === Debugging ===
|
// === Debugging ===
|
||||||
@@ -160,6 +181,8 @@ typedef struct options {
|
|||||||
bool vsync_use_glfinish;
|
bool vsync_use_glfinish;
|
||||||
/// Whether use damage information to help limit the area to paint
|
/// Whether use damage information to help limit the area to paint
|
||||||
bool use_damage;
|
bool use_damage;
|
||||||
|
/// Disable frame pacing
|
||||||
|
bool no_frame_pacing;
|
||||||
|
|
||||||
// === Shadow ===
|
// === Shadow ===
|
||||||
/// Red, green and blue tone of the shadow.
|
/// Red, green and blue tone of the shadow.
|
||||||
@@ -173,8 +196,8 @@ typedef struct options {
|
|||||||
c2_lptr_t *shadow_blacklist;
|
c2_lptr_t *shadow_blacklist;
|
||||||
/// Whether bounding-shaped window should be ignored.
|
/// Whether bounding-shaped window should be ignored.
|
||||||
bool shadow_ignore_shaped;
|
bool shadow_ignore_shaped;
|
||||||
/// Whether to crop shadow to the very Xinerama screen.
|
/// Whether to crop shadow to the very X RandR monitor.
|
||||||
bool xinerama_shadow_crop;
|
bool crop_shadow_to_monitor;
|
||||||
/// Don't draw shadow over these windows. A linked list of conditions.
|
/// Don't draw shadow over these windows. A linked list of conditions.
|
||||||
c2_lptr_t *shadow_clip_list;
|
c2_lptr_t *shadow_clip_list;
|
||||||
|
|
||||||
@@ -275,6 +298,8 @@ typedef struct options {
|
|||||||
int corner_radius;
|
int corner_radius;
|
||||||
/// Rounded corners blacklist. A linked list of conditions.
|
/// Rounded corners blacklist. A linked list of conditions.
|
||||||
c2_lptr_t *rounded_corners_blacklist;
|
c2_lptr_t *rounded_corners_blacklist;
|
||||||
|
/// Rounded corner rules. A linked list of conditions.
|
||||||
|
c2_lptr_t *corner_radius_rules;
|
||||||
|
|
||||||
// === Focus related ===
|
// === Focus related ===
|
||||||
/// Whether to try to detect WM windows and mark them as focused.
|
/// Whether to try to detect WM windows and mark them as focused.
|
||||||
@@ -312,6 +337,8 @@ typedef struct options {
|
|||||||
c2_lptr_t *transparent_clipping_blacklist;
|
c2_lptr_t *transparent_clipping_blacklist;
|
||||||
|
|
||||||
bool dithered_present;
|
bool dithered_present;
|
||||||
|
|
||||||
|
struct debug_options debug_options;
|
||||||
} options_t;
|
} options_t;
|
||||||
|
|
||||||
extern const char *const BACKEND_STRS[NUM_BKEND + 1];
|
extern const char *const BACKEND_STRS[NUM_BKEND + 1];
|
||||||
@@ -320,7 +347,7 @@ bool must_use parse_long(const char *, long *);
|
|||||||
bool must_use parse_int(const char *, int *);
|
bool must_use parse_int(const char *, int *);
|
||||||
struct conv **must_use parse_blur_kern_lst(const char *, bool *hasneg, int *count);
|
struct conv **must_use parse_blur_kern_lst(const char *, bool *hasneg, int *count);
|
||||||
bool must_use parse_geometry(session_t *, const char *, region_t *);
|
bool must_use parse_geometry(session_t *, const char *, region_t *);
|
||||||
bool must_use parse_rule_opacity(c2_lptr_t **, const char *);
|
bool must_use parse_numeric_window_rule(c2_lptr_t **, const char *, long, long);
|
||||||
bool must_use parse_rule_window_shader(c2_lptr_t **, const char *, const char *);
|
bool must_use parse_rule_window_shader(c2_lptr_t **, const char *, const char *);
|
||||||
char *must_use locate_auxiliary_file(const char *scope, const char *path,
|
char *must_use locate_auxiliary_file(const char *scope, const char *path,
|
||||||
const char *include_dir);
|
const char *include_dir);
|
||||||
@@ -339,7 +366,7 @@ char **xdg_config_dirs(void);
|
|||||||
/// Parse a configuration file
|
/// Parse a configuration file
|
||||||
/// Returns the actually config_file name used, allocated on heap
|
/// Returns the actually config_file name used, allocated on heap
|
||||||
/// Outputs:
|
/// Outputs:
|
||||||
/// shadow_enable = whether shaodw is enabled globally
|
/// shadow_enable = whether shadow is enabled globally
|
||||||
/// fading_enable = whether fading is enabled globally
|
/// fading_enable = whether fading is enabled globally
|
||||||
/// win_option_mask = whether option overrides for specific window type is set for given
|
/// win_option_mask = whether option overrides for specific window type is set for given
|
||||||
/// options
|
/// options
|
||||||
|
|||||||
@@ -73,24 +73,27 @@ FILE *open_config_file(const char *cpath, char **ppath) {
|
|||||||
|
|
||||||
if (cpath) {
|
if (cpath) {
|
||||||
FILE *ret = fopen(cpath, "r");
|
FILE *ret = fopen(cpath, "r");
|
||||||
if (ret && ppath)
|
if (ret && ppath) {
|
||||||
*ppath = strdup(cpath);
|
*ppath = strdup(cpath);
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// First search for config file in user config directory
|
// First search for config file in user config directory
|
||||||
auto config_home = xdg_config_home();
|
auto config_home = xdg_config_home();
|
||||||
auto ret = open_config_file_at(config_home, ppath);
|
if (config_home) {
|
||||||
free((void *)config_home);
|
auto ret = open_config_file_at(config_home, ppath);
|
||||||
if (ret) {
|
free((void *)config_home);
|
||||||
return ret;
|
if (ret) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to legacy config file in user home directory
|
// Fall back to legacy config file in user home directory
|
||||||
const char *home = getenv("HOME");
|
const char *home = getenv("HOME");
|
||||||
if (home && strlen(home)) {
|
if (home && strlen(home)) {
|
||||||
auto path = mstrjoin(home, config_filename_legacy);
|
auto path = mstrjoin(home, config_filename_legacy);
|
||||||
ret = fopen(path, "r");
|
auto ret = fopen(path, "r");
|
||||||
if (ret && ppath) {
|
if (ret && ppath) {
|
||||||
*ppath = path;
|
*ppath = path;
|
||||||
} else {
|
} else {
|
||||||
@@ -104,7 +107,7 @@ FILE *open_config_file(const char *cpath, char **ppath) {
|
|||||||
// Fall back to config file in system config directory
|
// Fall back to config file in system config directory
|
||||||
auto config_dirs = xdg_config_dirs();
|
auto config_dirs = xdg_config_dirs();
|
||||||
for (int i = 0; config_dirs[i]; i++) {
|
for (int i = 0; config_dirs[i]; i++) {
|
||||||
ret = open_config_file_at(config_dirs[i], ppath);
|
auto ret = open_config_file_at(config_dirs[i], ppath);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
free(config_dirs);
|
free(config_dirs);
|
||||||
return ret;
|
return ret;
|
||||||
@@ -124,9 +127,10 @@ void parse_cfg_condlst(const config_t *pcfg, c2_lptr_t **pcondlst, const char *n
|
|||||||
// Parse an array of options
|
// Parse an array of options
|
||||||
if (config_setting_is_array(setting)) {
|
if (config_setting_is_array(setting)) {
|
||||||
int i = config_setting_length(setting);
|
int i = config_setting_length(setting);
|
||||||
while (i--)
|
while (i--) {
|
||||||
condlst_add(pcondlst,
|
condlst_add(pcondlst,
|
||||||
config_setting_get_string_elem(setting, i));
|
config_setting_get_string_elem(setting, i));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Treat it as a single pattern if it's a string
|
// Treat it as a single pattern if it's a string
|
||||||
else if (CONFIG_TYPE_STRING == config_setting_type(setting)) {
|
else if (CONFIG_TYPE_STRING == config_setting_type(setting)) {
|
||||||
@@ -135,6 +139,36 @@ void parse_cfg_condlst(const config_t *pcfg, c2_lptr_t **pcondlst, const char *n
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a window corner radius rule list in configuration file.
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
parse_cfg_condlst_corner(options_t *opt, const config_t *pcfg, const char *name) {
|
||||||
|
config_setting_t *setting = config_lookup(pcfg, name);
|
||||||
|
if (setting) {
|
||||||
|
// Parse an array of options
|
||||||
|
if (config_setting_is_array(setting)) {
|
||||||
|
int i = config_setting_length(setting);
|
||||||
|
while (i--) {
|
||||||
|
if (!parse_numeric_window_rule(
|
||||||
|
&opt->corner_radius_rules,
|
||||||
|
config_setting_get_string_elem(setting, i), 0,
|
||||||
|
INT_MAX)) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Treat it as a single pattern if it's a string
|
||||||
|
else if (config_setting_type(setting) == CONFIG_TYPE_STRING) {
|
||||||
|
if (!parse_numeric_window_rule(&opt->corner_radius_rules,
|
||||||
|
config_setting_get_string(setting),
|
||||||
|
0, INT_MAX)) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse an opacity rule list in configuration file.
|
* Parse an opacity rule list in configuration file.
|
||||||
*/
|
*/
|
||||||
@@ -145,17 +179,21 @@ parse_cfg_condlst_opct(options_t *opt, const config_t *pcfg, const char *name) {
|
|||||||
// Parse an array of options
|
// Parse an array of options
|
||||||
if (config_setting_is_array(setting)) {
|
if (config_setting_is_array(setting)) {
|
||||||
int i = config_setting_length(setting);
|
int i = config_setting_length(setting);
|
||||||
while (i--)
|
while (i--) {
|
||||||
if (!parse_rule_opacity(
|
if (!parse_numeric_window_rule(
|
||||||
&opt->opacity_rules,
|
&opt->opacity_rules,
|
||||||
config_setting_get_string_elem(setting, i)))
|
config_setting_get_string_elem(setting, i), 0, 100)) {
|
||||||
exit(1);
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Treat it as a single pattern if it's a string
|
// Treat it as a single pattern if it's a string
|
||||||
else if (config_setting_type(setting) == CONFIG_TYPE_STRING) {
|
else if (config_setting_type(setting) == CONFIG_TYPE_STRING) {
|
||||||
if (!parse_rule_opacity(&opt->opacity_rules,
|
if (!parse_numeric_window_rule(&opt->opacity_rules,
|
||||||
config_setting_get_string(setting)))
|
config_setting_get_string(setting),
|
||||||
|
0, 100)) {
|
||||||
exit(1);
|
exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,6 +381,12 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
|
|||||||
config_lookup_int(&cfg, "corner-radius", &opt->corner_radius);
|
config_lookup_int(&cfg, "corner-radius", &opt->corner_radius);
|
||||||
// --rounded-corners-exclude
|
// --rounded-corners-exclude
|
||||||
parse_cfg_condlst(&cfg, &opt->rounded_corners_blacklist, "rounded-corners-exclude");
|
parse_cfg_condlst(&cfg, &opt->rounded_corners_blacklist, "rounded-corners-exclude");
|
||||||
|
// --corner-radius-rules
|
||||||
|
parse_cfg_condlst_corner(opt, &cfg, "corner-radius-rules");
|
||||||
|
|
||||||
|
// --no-frame-pacing
|
||||||
|
lcfg_lookup_bool(&cfg, "no-frame-pacing", &opt->no_frame_pacing);
|
||||||
|
|
||||||
// -e (frame_opacity)
|
// -e (frame_opacity)
|
||||||
config_lookup_float(&cfg, "frame-opacity", &opt->frame_opacity);
|
config_lookup_float(&cfg, "frame-opacity", &opt->frame_opacity);
|
||||||
// -c (shadow_enable)
|
// -c (shadow_enable)
|
||||||
@@ -358,8 +402,9 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
|
|||||||
winopt_mask[WINTYPE_POPUP_MENU].opacity = true;
|
winopt_mask[WINTYPE_POPUP_MENU].opacity = true;
|
||||||
}
|
}
|
||||||
// -f (fading_enable)
|
// -f (fading_enable)
|
||||||
if (config_lookup_bool(&cfg, "fading", &ival))
|
if (config_lookup_bool(&cfg, "fading", &ival)) {
|
||||||
*fading_enable = ival;
|
*fading_enable = ival;
|
||||||
|
}
|
||||||
// --no-fading-open-close
|
// --no-fading-open-close
|
||||||
lcfg_lookup_bool(&cfg, "no-fading-openclose", &opt->no_fading_openclose);
|
lcfg_lookup_bool(&cfg, "no-fading-openclose", &opt->no_fading_openclose);
|
||||||
// --no-fading-destroyed-argb
|
// --no-fading-destroyed-argb
|
||||||
@@ -379,8 +424,9 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
|
|||||||
opt->shadow_blue = rgb.blue;
|
opt->shadow_blue = rgb.blue;
|
||||||
}
|
}
|
||||||
// --shadow-exclude-reg
|
// --shadow-exclude-reg
|
||||||
if (config_lookup_string(&cfg, "shadow-exclude-reg", &sval))
|
if (config_lookup_string(&cfg, "shadow-exclude-reg", &sval)) {
|
||||||
opt->shadow_exclude_reg_str = strdup(sval);
|
opt->shadow_exclude_reg_str = strdup(sval);
|
||||||
|
}
|
||||||
// --inactive-opacity-override
|
// --inactive-opacity-override
|
||||||
lcfg_lookup_bool(&cfg, "inactive-opacity-override", &opt->inactive_opacity_override);
|
lcfg_lookup_bool(&cfg, "inactive-opacity-override", &opt->inactive_opacity_override);
|
||||||
// --inactive-dim
|
// --inactive-dim
|
||||||
@@ -393,8 +439,12 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
|
|||||||
lcfg_lookup_bool(&cfg, "shadow-ignore-shaped", &opt->shadow_ignore_shaped);
|
lcfg_lookup_bool(&cfg, "shadow-ignore-shaped", &opt->shadow_ignore_shaped);
|
||||||
// --detect-rounded-corners
|
// --detect-rounded-corners
|
||||||
lcfg_lookup_bool(&cfg, "detect-rounded-corners", &opt->detect_rounded_corners);
|
lcfg_lookup_bool(&cfg, "detect-rounded-corners", &opt->detect_rounded_corners);
|
||||||
// --xinerama-shadow-crop
|
// --crop-shadow-to-monitor
|
||||||
lcfg_lookup_bool(&cfg, "xinerama-shadow-crop", &opt->xinerama_shadow_crop);
|
if (lcfg_lookup_bool(&cfg, "xinerama-shadow-crop", &opt->crop_shadow_to_monitor)) {
|
||||||
|
log_warn("xinerama-shadow-crop is deprecated. Use crop-shadow-to-monitor "
|
||||||
|
"instead.");
|
||||||
|
}
|
||||||
|
lcfg_lookup_bool(&cfg, "crop-shadow-to-monitor", &opt->crop_shadow_to_monitor);
|
||||||
// --detect-client-opacity
|
// --detect-client-opacity
|
||||||
lcfg_lookup_bool(&cfg, "detect-client-opacity", &opt->detect_client_opacity);
|
lcfg_lookup_bool(&cfg, "detect-client-opacity", &opt->detect_client_opacity);
|
||||||
// --refresh-rate
|
// --refresh-rate
|
||||||
@@ -437,7 +487,8 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
|
|||||||
}
|
}
|
||||||
// --sw-opti
|
// --sw-opti
|
||||||
if (lcfg_lookup_bool(&cfg, "sw-opti", &bval)) {
|
if (lcfg_lookup_bool(&cfg, "sw-opti", &bval)) {
|
||||||
log_warn("The sw-opti %s", deprecation_message);
|
log_error("The sw-opti %s", deprecation_message);
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
// --use-ewmh-active-win
|
// --use-ewmh-active-win
|
||||||
lcfg_lookup_bool(&cfg, "use-ewmh-active-win", &opt->use_ewmh_active_win);
|
lcfg_lookup_bool(&cfg, "use-ewmh-active-win", &opt->use_ewmh_active_win);
|
||||||
@@ -638,9 +689,10 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
|
|||||||
// --xrender-sync-fence
|
// --xrender-sync-fence
|
||||||
lcfg_lookup_bool(&cfg, "xrender-sync-fence", &opt->xrender_sync_fence);
|
lcfg_lookup_bool(&cfg, "xrender-sync-fence", &opt->xrender_sync_fence);
|
||||||
|
|
||||||
if (lcfg_lookup_bool(&cfg, "clear-shadow", &bval))
|
if (lcfg_lookup_bool(&cfg, "clear-shadow", &bval)) {
|
||||||
log_warn("\"clear-shadow\" is removed as an option, and is always"
|
log_warn("\"clear-shadow\" is removed as an option, and is always"
|
||||||
" enabled now. Consider removing it from your config file");
|
" enabled now. Consider removing it from your config file");
|
||||||
|
}
|
||||||
|
|
||||||
config_setting_t *blur_cfg = config_lookup(&cfg, "blur");
|
config_setting_t *blur_cfg = config_lookup(&cfg, "blur");
|
||||||
if (blur_cfg) {
|
if (blur_cfg) {
|
||||||
|
|||||||
91
src/dbus.c
91
src/dbus.c
@@ -253,8 +253,9 @@ static dbus_bool_t cdbus_callback_add_timeout(DBusTimeout *timeout, void *data)
|
|||||||
t->t = timeout;
|
t->t = timeout;
|
||||||
dbus_timeout_set_data(timeout, t, NULL);
|
dbus_timeout_set_data(timeout, t, NULL);
|
||||||
|
|
||||||
if (dbus_timeout_get_enabled(timeout))
|
if (dbus_timeout_get_enabled(timeout)) {
|
||||||
ev_timer_start(ps->loop, &t->w);
|
ev_timer_start(ps->loop, &t->w);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -302,10 +303,12 @@ typedef struct ev_dbus_io {
|
|||||||
void cdbus_io_callback(EV_P attr_unused, ev_io *w, int revents) {
|
void cdbus_io_callback(EV_P attr_unused, ev_io *w, int revents) {
|
||||||
ev_dbus_io *dw = (void *)w;
|
ev_dbus_io *dw = (void *)w;
|
||||||
DBusWatchFlags flags = 0;
|
DBusWatchFlags flags = 0;
|
||||||
if (revents & EV_READ)
|
if (revents & EV_READ) {
|
||||||
flags |= DBUS_WATCH_READABLE;
|
flags |= DBUS_WATCH_READABLE;
|
||||||
if (revents & EV_WRITE)
|
}
|
||||||
|
if (revents & EV_WRITE) {
|
||||||
flags |= DBUS_WATCH_WRITABLE;
|
flags |= DBUS_WATCH_WRITABLE;
|
||||||
|
}
|
||||||
dbus_watch_handle(dw->dw, flags);
|
dbus_watch_handle(dw->dw, flags);
|
||||||
while (dbus_connection_dispatch(dw->cd->dbus_conn) != DBUS_DISPATCH_COMPLETE)
|
while (dbus_connection_dispatch(dw->cd->dbus_conn) != DBUS_DISPATCH_COMPLETE)
|
||||||
;
|
;
|
||||||
@@ -317,10 +320,12 @@ void cdbus_io_callback(EV_P attr_unused, ev_io *w, int revents) {
|
|||||||
static inline int cdbus_get_watch_cond(DBusWatch *watch) {
|
static inline int cdbus_get_watch_cond(DBusWatch *watch) {
|
||||||
const unsigned flags = dbus_watch_get_flags(watch);
|
const unsigned flags = dbus_watch_get_flags(watch);
|
||||||
int condition = 0;
|
int condition = 0;
|
||||||
if (flags & DBUS_WATCH_READABLE)
|
if (flags & DBUS_WATCH_READABLE) {
|
||||||
condition |= EV_READ;
|
condition |= EV_READ;
|
||||||
if (flags & DBUS_WATCH_WRITABLE)
|
}
|
||||||
|
if (flags & DBUS_WATCH_WRITABLE) {
|
||||||
condition |= EV_WRITE;
|
condition |= EV_WRITE;
|
||||||
|
}
|
||||||
|
|
||||||
return condition;
|
return condition;
|
||||||
}
|
}
|
||||||
@@ -338,8 +343,9 @@ static dbus_bool_t cdbus_callback_add_watch(DBusWatch *watch, void *data) {
|
|||||||
cdbus_get_watch_cond(watch));
|
cdbus_get_watch_cond(watch));
|
||||||
|
|
||||||
// Leave disabled watches alone
|
// Leave disabled watches alone
|
||||||
if (dbus_watch_get_enabled(watch))
|
if (dbus_watch_get_enabled(watch)) {
|
||||||
ev_io_start(ps->loop, &w->w);
|
ev_io_start(ps->loop, &w->w);
|
||||||
|
}
|
||||||
|
|
||||||
dbus_watch_set_data(watch, w, NULL);
|
dbus_watch_set_data(watch, w, NULL);
|
||||||
|
|
||||||
@@ -363,10 +369,11 @@ static void cdbus_callback_remove_watch(DBusWatch *watch, void *data) {
|
|||||||
static void cdbus_callback_watch_toggled(DBusWatch *watch, void *data) {
|
static void cdbus_callback_watch_toggled(DBusWatch *watch, void *data) {
|
||||||
session_t *ps = data;
|
session_t *ps = data;
|
||||||
ev_io *w = dbus_watch_get_data(watch);
|
ev_io *w = dbus_watch_get_data(watch);
|
||||||
if (dbus_watch_get_enabled(watch))
|
if (dbus_watch_get_enabled(watch)) {
|
||||||
ev_io_start(ps->loop, w);
|
ev_io_start(ps->loop, w);
|
||||||
else
|
} else {
|
||||||
ev_io_stop(ps->loop, w);
|
ev_io_stop(ps->loop, w);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///@}
|
///@}
|
||||||
@@ -513,8 +520,9 @@ static bool cdbus_apdarg_enum(session_t *ps attr_unused, DBusMessage *msg, const
|
|||||||
static bool
|
static bool
|
||||||
cdbus_apdarg_string(session_t *ps attr_unused, DBusMessage *msg, const void *data) {
|
cdbus_apdarg_string(session_t *ps attr_unused, DBusMessage *msg, const void *data) {
|
||||||
const char *str = data;
|
const char *str = data;
|
||||||
if (!str)
|
if (!str) {
|
||||||
str = "";
|
str = "";
|
||||||
|
}
|
||||||
|
|
||||||
if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &str, DBUS_TYPE_INVALID)) {
|
if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &str, DBUS_TYPE_INVALID)) {
|
||||||
log_error("Failed to append argument.");
|
log_error("Failed to append argument.");
|
||||||
@@ -898,7 +906,7 @@ cdbus_process_window_property_get(session_t *ps, DBusMessage *msg, cdbus_window_
|
|||||||
}
|
}
|
||||||
if (!strcmp("RawFocused", target)) {
|
if (!strcmp("RawFocused", target)) {
|
||||||
cdbus_reply(ps, msg, cdbus_append_bool_variant,
|
cdbus_reply(ps, msg, cdbus_append_bool_variant,
|
||||||
(bool[]){win_is_focused_raw(ps, w)});
|
(bool[]){win_is_focused_raw(w)});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -968,7 +976,7 @@ static bool cdbus_process_win_get(session_t *ps, DBusMessage *msg) {
|
|||||||
cdbus_m_win_get_do(wmwin, cdbus_reply_bool);
|
cdbus_m_win_get_do(wmwin, cdbus_reply_bool);
|
||||||
cdbus_m_win_get_do(leader, cdbus_reply_wid);
|
cdbus_m_win_get_do(leader, cdbus_reply_wid);
|
||||||
if (!strcmp("focused_raw", target)) {
|
if (!strcmp("focused_raw", target)) {
|
||||||
cdbus_reply_bool(ps, msg, win_is_focused_raw(ps, w));
|
cdbus_reply_bool(ps, msg, win_is_focused_raw(w));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
cdbus_m_win_get_do(fade_force, cdbus_reply_enum);
|
cdbus_m_win_get_do(fade_force, cdbus_reply_enum);
|
||||||
@@ -1050,32 +1058,36 @@ static bool cdbus_process_win_set(session_t *ps, DBusMessage *msg) {
|
|||||||
|
|
||||||
if (!strcmp("shadow_force", target)) {
|
if (!strcmp("shadow_force", target)) {
|
||||||
cdbus_enum_t val = UNSET;
|
cdbus_enum_t val = UNSET;
|
||||||
if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val))
|
if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
win_set_shadow_force(ps, w, val);
|
win_set_shadow_force(ps, w, val);
|
||||||
goto cdbus_process_win_set_success;
|
goto cdbus_process_win_set_success;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strcmp("fade_force", target)) {
|
if (!strcmp("fade_force", target)) {
|
||||||
cdbus_enum_t val = UNSET;
|
cdbus_enum_t val = UNSET;
|
||||||
if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val))
|
if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
win_set_fade_force(w, val);
|
win_set_fade_force(w, val);
|
||||||
goto cdbus_process_win_set_success;
|
goto cdbus_process_win_set_success;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strcmp("focused_force", target)) {
|
if (!strcmp("focused_force", target)) {
|
||||||
cdbus_enum_t val = UNSET;
|
cdbus_enum_t val = UNSET;
|
||||||
if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val))
|
if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
win_set_focused_force(ps, w, val);
|
win_set_focused_force(ps, w, val);
|
||||||
goto cdbus_process_win_set_success;
|
goto cdbus_process_win_set_success;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strcmp("invert_color_force", target)) {
|
if (!strcmp("invert_color_force", target)) {
|
||||||
cdbus_enum_t val = UNSET;
|
cdbus_enum_t val = UNSET;
|
||||||
if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val))
|
if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
win_set_invert_color_force(ps, w, val);
|
win_set_invert_color_force(ps, w, val);
|
||||||
goto cdbus_process_win_set_success;
|
goto cdbus_process_win_set_success;
|
||||||
}
|
}
|
||||||
@@ -1087,8 +1099,9 @@ static bool cdbus_process_win_set(session_t *ps, DBusMessage *msg) {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
cdbus_process_win_set_success:
|
cdbus_process_win_set_success:
|
||||||
if (!dbus_message_get_no_reply(msg))
|
if (!dbus_message_get_no_reply(msg)) {
|
||||||
cdbus_reply_bool(ps, msg, true);
|
cdbus_reply_bool(ps, msg, true);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1098,16 +1111,18 @@ cdbus_process_win_set_success:
|
|||||||
static bool cdbus_process_find_win(session_t *ps, DBusMessage *msg) {
|
static bool cdbus_process_find_win(session_t *ps, DBusMessage *msg) {
|
||||||
const char *target = NULL;
|
const char *target = NULL;
|
||||||
|
|
||||||
if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target))
|
if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
xcb_window_t wid = XCB_NONE;
|
xcb_window_t wid = XCB_NONE;
|
||||||
|
|
||||||
// Find window by client window
|
// Find window by client window
|
||||||
if (!strcmp("client", target)) {
|
if (!strcmp("client", target)) {
|
||||||
cdbus_window_t client = XCB_NONE;
|
cdbus_window_t client = XCB_NONE;
|
||||||
if (!cdbus_msg_get_arg(msg, 1, CDBUS_TYPE_WINDOW, &client))
|
if (!cdbus_msg_get_arg(msg, 1, CDBUS_TYPE_WINDOW, &client)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
auto w = find_toplevel(ps, client);
|
auto w = find_toplevel(ps, client);
|
||||||
if (w) {
|
if (w) {
|
||||||
wid = w->base.id;
|
wid = w->base.id;
|
||||||
@@ -1136,8 +1151,9 @@ static bool cdbus_process_find_win(session_t *ps, DBusMessage *msg) {
|
|||||||
static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) {
|
static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) {
|
||||||
const char *target = NULL;
|
const char *target = NULL;
|
||||||
|
|
||||||
if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target))
|
if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#define cdbus_m_opts_get_do(tgt, apdarg_func) \
|
#define cdbus_m_opts_get_do(tgt, apdarg_func) \
|
||||||
if (!strcmp(#tgt, target)) { \
|
if (!strcmp(#tgt, target)) { \
|
||||||
@@ -1165,7 +1181,7 @@ static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) {
|
|||||||
|
|
||||||
// display
|
// display
|
||||||
if (!strcmp("display", target)) {
|
if (!strcmp("display", target)) {
|
||||||
cdbus_reply_string(ps, msg, DisplayString(ps->dpy));
|
cdbus_reply_string(ps, msg, DisplayString(ps->c.dpy));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1202,7 +1218,7 @@ static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) {
|
|||||||
cdbus_m_opts_get_do(shadow_offset_x, cdbus_reply_int32);
|
cdbus_m_opts_get_do(shadow_offset_x, cdbus_reply_int32);
|
||||||
cdbus_m_opts_get_do(shadow_offset_y, cdbus_reply_int32);
|
cdbus_m_opts_get_do(shadow_offset_y, cdbus_reply_int32);
|
||||||
cdbus_m_opts_get_do(shadow_opacity, cdbus_reply_double);
|
cdbus_m_opts_get_do(shadow_opacity, cdbus_reply_double);
|
||||||
cdbus_m_opts_get_do(xinerama_shadow_crop, cdbus_reply_bool);
|
cdbus_m_opts_get_do(crop_shadow_to_monitor, cdbus_reply_bool);
|
||||||
|
|
||||||
cdbus_m_opts_get_do(fade_delta, cdbus_reply_int32);
|
cdbus_m_opts_get_do(fade_delta, cdbus_reply_int32);
|
||||||
cdbus_m_opts_get_do(fade_in_step, cdbus_reply_double);
|
cdbus_m_opts_get_do(fade_in_step, cdbus_reply_double);
|
||||||
@@ -1246,8 +1262,9 @@ void queue_redraw(session_t *ps);
|
|||||||
static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) {
|
static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) {
|
||||||
const char *target = NULL;
|
const char *target = NULL;
|
||||||
|
|
||||||
if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target))
|
if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#define cdbus_m_opts_set_do(tgt, type, real_type) \
|
#define cdbus_m_opts_set_do(tgt, type, real_type) \
|
||||||
if (!strcmp(#tgt, target)) { \
|
if (!strcmp(#tgt, target)) { \
|
||||||
@@ -1274,8 +1291,9 @@ static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) {
|
|||||||
// fade_in_step
|
// fade_in_step
|
||||||
if (!strcmp("fade_in_step", target)) {
|
if (!strcmp("fade_in_step", target)) {
|
||||||
double val = 0.0;
|
double val = 0.0;
|
||||||
if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_DOUBLE, &val))
|
if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_DOUBLE, &val)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
ps->o.fade_in_step = normalize_d(val);
|
ps->o.fade_in_step = normalize_d(val);
|
||||||
goto cdbus_process_opts_set_success;
|
goto cdbus_process_opts_set_success;
|
||||||
}
|
}
|
||||||
@@ -1283,8 +1301,9 @@ static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) {
|
|||||||
// fade_out_step
|
// fade_out_step
|
||||||
if (!strcmp("fade_out_step", target)) {
|
if (!strcmp("fade_out_step", target)) {
|
||||||
double val = 0.0;
|
double val = 0.0;
|
||||||
if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_DOUBLE, &val))
|
if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_DOUBLE, &val)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
ps->o.fade_out_step = normalize_d(val);
|
ps->o.fade_out_step = normalize_d(val);
|
||||||
goto cdbus_process_opts_set_success;
|
goto cdbus_process_opts_set_success;
|
||||||
}
|
}
|
||||||
@@ -1292,8 +1311,9 @@ static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) {
|
|||||||
// no_fading_openclose
|
// no_fading_openclose
|
||||||
if (!strcmp("no_fading_openclose", target)) {
|
if (!strcmp("no_fading_openclose", target)) {
|
||||||
dbus_bool_t val = FALSE;
|
dbus_bool_t val = FALSE;
|
||||||
if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val))
|
if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
opts_set_no_fading_openclose(ps, val);
|
opts_set_no_fading_openclose(ps, val);
|
||||||
goto cdbus_process_opts_set_success;
|
goto cdbus_process_opts_set_success;
|
||||||
}
|
}
|
||||||
@@ -1301,8 +1321,9 @@ static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) {
|
|||||||
// unredir_if_possible
|
// unredir_if_possible
|
||||||
if (!strcmp("unredir_if_possible", target)) {
|
if (!strcmp("unredir_if_possible", target)) {
|
||||||
dbus_bool_t val = FALSE;
|
dbus_bool_t val = FALSE;
|
||||||
if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val))
|
if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
if (ps->o.unredir_if_possible != val) {
|
if (ps->o.unredir_if_possible != val) {
|
||||||
ps->o.unredir_if_possible = val;
|
ps->o.unredir_if_possible = val;
|
||||||
queue_redraw(ps);
|
queue_redraw(ps);
|
||||||
@@ -1323,8 +1344,9 @@ static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) {
|
|||||||
// redirected_force
|
// redirected_force
|
||||||
if (!strcmp("redirected_force", target)) {
|
if (!strcmp("redirected_force", target)) {
|
||||||
cdbus_enum_t val = UNSET;
|
cdbus_enum_t val = UNSET;
|
||||||
if (!cdbus_msg_get_arg(msg, 1, CDBUS_TYPE_ENUM, &val))
|
if (!cdbus_msg_get_arg(msg, 1, CDBUS_TYPE_ENUM, &val)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
ps->o.redirected_force = val;
|
ps->o.redirected_force = val;
|
||||||
force_repaint(ps);
|
force_repaint(ps);
|
||||||
goto cdbus_process_opts_set_success;
|
goto cdbus_process_opts_set_success;
|
||||||
@@ -1341,8 +1363,9 @@ static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
cdbus_process_opts_set_success:
|
cdbus_process_opts_set_success:
|
||||||
if (!dbus_message_get_no_reply(msg))
|
if (!dbus_message_get_no_reply(msg)) {
|
||||||
cdbus_reply_bool(ps, msg, true);
|
cdbus_reply_bool(ps, msg, true);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1512,13 +1535,15 @@ cdbus_process(DBusConnection *c attr_unused, DBusMessage *msg, void *ud) {
|
|||||||
if (cdbus_m_ismethod("reset")) {
|
if (cdbus_m_ismethod("reset")) {
|
||||||
log_info("picom is resetting...");
|
log_info("picom is resetting...");
|
||||||
ev_break(ps->loop, EVBREAK_ALL);
|
ev_break(ps->loop, EVBREAK_ALL);
|
||||||
if (!dbus_message_get_no_reply(msg))
|
if (!dbus_message_get_no_reply(msg)) {
|
||||||
cdbus_reply_bool(ps, msg, true);
|
cdbus_reply_bool(ps, msg, true);
|
||||||
|
}
|
||||||
handled = true;
|
handled = true;
|
||||||
} else if (cdbus_m_ismethod("repaint")) {
|
} else if (cdbus_m_ismethod("repaint")) {
|
||||||
force_repaint(ps);
|
force_repaint(ps);
|
||||||
if (!dbus_message_get_no_reply(msg))
|
if (!dbus_message_get_no_reply(msg)) {
|
||||||
cdbus_reply_bool(ps, msg, true);
|
cdbus_reply_bool(ps, msg, true);
|
||||||
|
}
|
||||||
handled = true;
|
handled = true;
|
||||||
} else if (cdbus_m_ismethod("list_win")) {
|
} else if (cdbus_m_ismethod("list_win")) {
|
||||||
handled = cdbus_process_list_win(ps, msg);
|
handled = cdbus_process_list_win(ps, msg);
|
||||||
@@ -1566,8 +1591,9 @@ cdbus_process(DBusConnection *c attr_unused, DBusMessage *msg, void *ud) {
|
|||||||
dbus_message_get_member(msg));
|
dbus_message_get_member(msg));
|
||||||
}
|
}
|
||||||
if (DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) &&
|
if (DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) &&
|
||||||
!dbus_message_get_no_reply(msg))
|
!dbus_message_get_no_reply(msg)) {
|
||||||
cdbus_reply_err(ps, msg, CDBUS_ERROR_BADMSG, CDBUS_ERROR_BADMSG_S);
|
cdbus_reply_err(ps, msg, CDBUS_ERROR_BADMSG, CDBUS_ERROR_BADMSG_S);
|
||||||
|
}
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1593,8 +1619,9 @@ cdbus_process_windows(DBusConnection *c attr_unused, DBusMessage *msg, void *ud)
|
|||||||
const char *last_segment = strrchr(path, '/');
|
const char *last_segment = strrchr(path, '/');
|
||||||
if (last_segment == NULL) {
|
if (last_segment == NULL) {
|
||||||
if (DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) &&
|
if (DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) &&
|
||||||
!dbus_message_get_no_reply(msg))
|
!dbus_message_get_no_reply(msg)) {
|
||||||
cdbus_reply_err(ps, msg, CDBUS_ERROR_BADMSG, CDBUS_ERROR_BADMSG_S);
|
cdbus_reply_err(ps, msg, CDBUS_ERROR_BADMSG, CDBUS_ERROR_BADMSG_S);
|
||||||
|
}
|
||||||
return DBUS_HANDLER_RESULT_HANDLED;
|
return DBUS_HANDLER_RESULT_HANDLED;
|
||||||
}
|
}
|
||||||
bool is_root = strncmp(last_segment, "/windows", 8) == 0;
|
bool is_root = strncmp(last_segment, "/windows", 8) == 0;
|
||||||
|
|||||||
@@ -2,21 +2,21 @@
|
|||||||
// Copyright (c) 2018 Yuxuan Shui <yshuiv7@gmail.com>
|
// Copyright (c) 2018 Yuxuan Shui <yshuiv7@gmail.com>
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <xcb/xcb.h>
|
|
||||||
#include <xcb/composite.h>
|
#include <xcb/composite.h>
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
#include "backend/driver.h"
|
#include "backend/driver.h"
|
||||||
#include "diagnostic.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include "picom.h"
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "diagnostic.h"
|
||||||
|
#include "picom.h"
|
||||||
|
|
||||||
void print_diagnostics(session_t *ps, const char *config_file, bool compositor_running) {
|
void print_diagnostics(session_t *ps, const char *config_file, bool compositor_running) {
|
||||||
printf("**Version:** " PICOM_VERSION "\n");
|
printf("**Version:** " PICOM_VERSION "\n");
|
||||||
//printf("**CFLAGS:** %s\n", "??");
|
// printf("**CFLAGS:** %s\n", "??");
|
||||||
printf("\n### Extensions:\n\n");
|
printf("\n### Extensions:\n\n");
|
||||||
printf("* Shape: %s\n", ps->shape_exists ? "Yes" : "No");
|
printf("* Shape: %s\n", ps->shape_exists ? "Yes" : "No");
|
||||||
printf("* XRandR: %s\n", ps->randr_exists ? "Yes" : "No");
|
printf("* RandR: %s\n", ps->randr_exists ? "Yes" : "No");
|
||||||
printf("* Present: %s\n", ps->present_exists ? "Present" : "Not Present");
|
printf("* Present: %s\n", ps->present_exists ? "Present" : "Not Present");
|
||||||
printf("\n### Misc:\n\n");
|
printf("\n### Misc:\n\n");
|
||||||
printf("* Use Overlay: %s\n", ps->overlay != XCB_NONE ? "Yes" : "No");
|
printf("* Use Overlay: %s\n", ps->overlay != XCB_NONE ? "Yes" : "No");
|
||||||
@@ -39,7 +39,7 @@ void print_diagnostics(session_t *ps, const char *config_file, bool compositor_r
|
|||||||
for (int i = 0; i < NUM_BKEND; i++) {
|
for (int i = 0; i < NUM_BKEND; i++) {
|
||||||
if (backend_list[i] && backend_list[i]->diagnostics) {
|
if (backend_list[i] && backend_list[i]->diagnostics) {
|
||||||
printf("\n### Backend: %s\n\n", BACKEND_STRS[i]);
|
printf("\n### Backend: %s\n\n", BACKEND_STRS[i]);
|
||||||
auto data = backend_list[i]->init(ps);
|
auto data = backend_list[i]->init(ps, session_get_target_window(ps));
|
||||||
if (!data) {
|
if (!data) {
|
||||||
printf(" Cannot initialize this backend\n");
|
printf(" Cannot initialize this backend\n");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
133
src/event.c
133
src/event.c
@@ -1,12 +1,14 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
// Copyright (c) 2019, Yuxuan Shui <yshuiv7@gmail.com>
|
// Copyright (c) 2019, Yuxuan Shui <yshuiv7@gmail.com>
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <X11/Xlibint.h>
|
#include <X11/Xlibint.h>
|
||||||
#include <X11/extensions/sync.h>
|
#include <X11/extensions/sync.h>
|
||||||
#include <xcb/damage.h>
|
#include <xcb/damage.h>
|
||||||
#include <xcb/randr.h>
|
#include <xcb/randr.h>
|
||||||
|
#include <xcb/xcb_event.h>
|
||||||
|
|
||||||
#include "atom.h"
|
#include "atom.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
@@ -28,7 +30,7 @@
|
|||||||
/// made the query when those events were already in the queue. so the reply you got is
|
/// made the query when those events were already in the queue. so the reply you got is
|
||||||
/// more up-to-date than the events). Also, handling events when other client are making
|
/// more up-to-date than the events). Also, handling events when other client are making
|
||||||
/// concurrent requests is not good. Because the server states are changing without you
|
/// concurrent requests is not good. Because the server states are changing without you
|
||||||
/// knowning them. This is super racy, and can cause lots of potential problems.
|
/// knowing them. This is super racy, and can cause lots of potential problems.
|
||||||
///
|
///
|
||||||
/// All of above mandates we do these things:
|
/// All of above mandates we do these things:
|
||||||
/// 1. Grab server when handling events
|
/// 1. Grab server when handling events
|
||||||
@@ -46,8 +48,14 @@
|
|||||||
/// When top half finished, we enter the render stage, where no server state should be
|
/// When top half finished, we enter the render stage, where no server state should be
|
||||||
/// queried. All rendering should be done with our internal knowledge of the server state.
|
/// queried. All rendering should be done with our internal knowledge of the server state.
|
||||||
///
|
///
|
||||||
|
/// P.S. There is another reason to avoid sending any request to the server as much as
|
||||||
|
/// possible. To make sure requests are sent, flushes are needed. And `xcb_flush`/`XFlush`
|
||||||
|
/// functions may read more events from the server into their queues. This is
|
||||||
|
/// undesirable, see the comments on `handle_queued_x_events` in picom.c for more details.
|
||||||
|
|
||||||
// TODO(yshui) the things described above
|
// TODO(yshui) the things described above. This is mostly done, maybe some of
|
||||||
|
// the functions here is still making unnecessary queries, we need
|
||||||
|
// to do some auditing to be sure.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a window's name from window ID.
|
* Get a window's name from window ID.
|
||||||
@@ -56,7 +64,7 @@ static inline const char *ev_window_name(session_t *ps, xcb_window_t wid) {
|
|||||||
char *name = "";
|
char *name = "";
|
||||||
if (wid) {
|
if (wid) {
|
||||||
name = "(Failed to get title)";
|
name = "(Failed to get title)";
|
||||||
if (ps->root == wid) {
|
if (ps->c.screen_info->root == wid) {
|
||||||
name = "(Root window)";
|
name = "(Root window)";
|
||||||
} else if (ps->overlay == wid) {
|
} else if (ps->overlay == wid) {
|
||||||
name = "(Overlay)";
|
name = "(Overlay)";
|
||||||
@@ -106,7 +114,7 @@ static inline xcb_window_t attr_pure ev_window(session_t *ps, xcb_generic_event_
|
|||||||
|
|
||||||
static inline const char *ev_name(session_t *ps, xcb_generic_event_t *ev) {
|
static inline const char *ev_name(session_t *ps, xcb_generic_event_t *ev) {
|
||||||
static char buf[128];
|
static char buf[128];
|
||||||
switch (ev->response_type & 0x7f) {
|
switch (XCB_EVENT_RESPONSE_TYPE(ev)) {
|
||||||
CASESTRRET(FocusIn);
|
CASESTRRET(FocusIn);
|
||||||
CASESTRRET(FocusOut);
|
CASESTRRET(FocusOut);
|
||||||
CASESTRRET(CreateNotify);
|
CASESTRRET(CreateNotify);
|
||||||
@@ -183,7 +191,7 @@ static inline void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) {
|
static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) {
|
||||||
if (ev->parent == ps->root) {
|
if (ev->parent == ps->c.screen_info->root) {
|
||||||
add_win_top(ps, ev->window);
|
add_win_top(ps, ev->window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,8 +245,8 @@ static void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) {
|
|||||||
win_set_flags(mw, WIN_FLAGS_SIZE_STALE);
|
win_set_flags(mw, WIN_FLAGS_SIZE_STALE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recalculate which screen this window is on
|
// Recalculate which monitor this window is on
|
||||||
win_update_screen(ps->xinerama_nscrs, ps->xinerama_scr_regs, mw);
|
win_update_monitor(&ps->monitors, mw);
|
||||||
}
|
}
|
||||||
|
|
||||||
// override_redirect flag cannot be changed after window creation, as far
|
// override_redirect flag cannot be changed after window creation, as far
|
||||||
@@ -249,7 +257,7 @@ static void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) {
|
|||||||
static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event_t *ev) {
|
static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event_t *ev) {
|
||||||
log_debug("{ send_event: %d, id: %#010x, above: %#010x, override_redirect: %d }",
|
log_debug("{ send_event: %d, id: %#010x, above: %#010x, override_redirect: %d }",
|
||||||
ev->event, ev->window, ev->above_sibling, ev->override_redirect);
|
ev->event, ev->window, ev->above_sibling, ev->override_redirect);
|
||||||
if (ev->window == ps->root) {
|
if (ev->window == ps->c.screen_info->root) {
|
||||||
set_root_flags(ps, ROOT_FLAGS_CONFIGURED);
|
set_root_flags(ps, ROOT_FLAGS_CONFIGURED);
|
||||||
} else {
|
} else {
|
||||||
configure_win(ps, ev);
|
configure_win(ps, ev);
|
||||||
@@ -283,8 +291,8 @@ static inline void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) {
|
|||||||
// in redirected state.
|
// in redirected state.
|
||||||
if (ps->overlay && ev->window == ps->overlay && !ps->redirected) {
|
if (ps->overlay && ev->window == ps->overlay && !ps->redirected) {
|
||||||
log_debug("Overlay is mapped while we are not redirected");
|
log_debug("Overlay is mapped while we are not redirected");
|
||||||
auto e =
|
auto e = xcb_request_check(
|
||||||
xcb_request_check(ps->c, xcb_unmap_window_checked(ps->c, ps->overlay));
|
ps->c.c, xcb_unmap_window_checked(ps->c.c, ps->overlay));
|
||||||
if (e) {
|
if (e) {
|
||||||
log_error("Failed to unmap the overlay window");
|
log_error("Failed to unmap the overlay window");
|
||||||
free(e);
|
free(e);
|
||||||
@@ -322,8 +330,8 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t
|
|||||||
ps->pending_updates = true;
|
ps->pending_updates = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ev->parent == ps->root) {
|
if (ev->parent == ps->c.screen_info->root) {
|
||||||
// X will generate reparent notifiy even if the parent didn't actually
|
// X will generate reparent notify even if the parent didn't actually
|
||||||
// change (i.e. reparent again to current parent). So we check if that's
|
// change (i.e. reparent again to current parent). So we check if that's
|
||||||
// the case
|
// the case
|
||||||
auto w = find_win(ps, ev->window);
|
auto w = find_win(ps, ev->window);
|
||||||
@@ -349,19 +357,14 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reset event mask in case something wrong happens
|
// Reset event mask in case something wrong happens
|
||||||
xcb_change_window_attributes(
|
uint32_t evmask = determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN);
|
||||||
ps->c, ev->window, XCB_CW_EVENT_MASK,
|
|
||||||
(const uint32_t[]){determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN)});
|
|
||||||
|
|
||||||
if (!wid_has_prop(ps, ev->window, ps->atoms->aWM_STATE)) {
|
if (!wid_has_prop(ps, ev->window, ps->atoms->aWM_STATE)) {
|
||||||
log_debug("Window %#010x doesn't have WM_STATE property, it is "
|
log_debug("Window %#010x doesn't have WM_STATE property, it is "
|
||||||
"probably not a client window. But we will listen for "
|
"probably not a client window. But we will listen for "
|
||||||
"property change in case it gains one.",
|
"property change in case it gains one.",
|
||||||
ev->window);
|
ev->window);
|
||||||
xcb_change_window_attributes(
|
evmask |= XCB_EVENT_MASK_PROPERTY_CHANGE;
|
||||||
ps->c, ev->window, XCB_CW_EVENT_MASK,
|
|
||||||
(const uint32_t[]){determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN) |
|
|
||||||
XCB_EVENT_MASK_PROPERTY_CHANGE});
|
|
||||||
} else {
|
} else {
|
||||||
auto w_real_top = find_managed_window_or_parent(ps, ev->parent);
|
auto w_real_top = find_managed_window_or_parent(ps, ev->parent);
|
||||||
if (w_real_top && w_real_top->state != WSTATE_UNMAPPED &&
|
if (w_real_top && w_real_top->state != WSTATE_UNMAPPED &&
|
||||||
@@ -372,9 +375,9 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t
|
|||||||
win_set_flags(w_real_top, WIN_FLAGS_CLIENT_STALE);
|
win_set_flags(w_real_top, WIN_FLAGS_CLIENT_STALE);
|
||||||
ps->pending_updates = true;
|
ps->pending_updates = true;
|
||||||
} else {
|
} else {
|
||||||
if (!w_real_top)
|
if (!w_real_top) {
|
||||||
log_debug("parent %#010x not found", ev->parent);
|
log_debug("parent %#010x not found", ev->parent);
|
||||||
else {
|
} else {
|
||||||
// Window is not currently mapped, unmark its
|
// Window is not currently mapped, unmark its
|
||||||
// client to trigger a client recheck when it is
|
// client to trigger a client recheck when it is
|
||||||
// mapped later.
|
// mapped later.
|
||||||
@@ -385,14 +388,17 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
XCB_AWAIT_VOID(xcb_change_window_attributes, ps->c.c, ev->window,
|
||||||
|
XCB_CW_EVENT_MASK, (const uint32_t[]){evmask});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void ev_circulate_notify(session_t *ps, xcb_circulate_notify_event_t *ev) {
|
static inline void ev_circulate_notify(session_t *ps, xcb_circulate_notify_event_t *ev) {
|
||||||
auto w = find_win(ps, ev->window);
|
auto w = find_win(ps, ev->window);
|
||||||
|
|
||||||
if (!w)
|
if (!w) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (ev->place == PlaceOnTop) {
|
if (ev->place == PlaceOnTop) {
|
||||||
restack_top(ps, w);
|
restack_top(ps, w);
|
||||||
@@ -409,7 +415,8 @@ static inline void expose_root(session_t *ps, const rect_t *rects, int nrects) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline void ev_expose(session_t *ps, xcb_expose_event_t *ev) {
|
static inline void ev_expose(session_t *ps, xcb_expose_event_t *ev) {
|
||||||
if (ev->window == ps->root || (ps->overlay && ev->window == ps->overlay)) {
|
if (ev->window == ps->c.screen_info->root ||
|
||||||
|
(ps->overlay && ev->window == ps->overlay)) {
|
||||||
int more = ev->count + 1;
|
int more = ev->count + 1;
|
||||||
if (ps->n_expose == ps->size_expose) {
|
if (ps->n_expose == ps->size_expose) {
|
||||||
if (ps->expose_rects) {
|
if (ps->expose_rects) {
|
||||||
@@ -438,8 +445,8 @@ static inline void ev_expose(session_t *ps, xcb_expose_event_t *ev) {
|
|||||||
static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t *ev) {
|
static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t *ev) {
|
||||||
if (unlikely(log_get_level_tls() <= LOG_LEVEL_TRACE)) {
|
if (unlikely(log_get_level_tls() <= LOG_LEVEL_TRACE)) {
|
||||||
// Print out changed atom
|
// Print out changed atom
|
||||||
xcb_get_atom_name_reply_t *reply =
|
xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply(
|
||||||
xcb_get_atom_name_reply(ps->c, xcb_get_atom_name(ps->c, ev->atom), NULL);
|
ps->c.c, xcb_get_atom_name(ps->c.c, ev->atom), NULL);
|
||||||
const char *name = "?";
|
const char *name = "?";
|
||||||
int name_len = 1;
|
int name_len = 1;
|
||||||
if (reply) {
|
if (reply) {
|
||||||
@@ -451,16 +458,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
|
|||||||
free(reply);
|
free(reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps->root == ev->window) {
|
if (ps->c.screen_info->root == ev->window) {
|
||||||
if (ev->atom == ps->atoms->a_NET_CURRENT_MON_CENTER) {
|
|
||||||
winprop_t prop = x_get_prop(ps->c, ps->root, ps->atoms->a_NET_CURRENT_MON_CENTER, 2L, XCB_ATOM_CARDINAL, 32);
|
|
||||||
if (prop.nitems == 2) {
|
|
||||||
ps->selmon_center_x = prop.p32[0];
|
|
||||||
ps->selmon_center_y = prop.p32[1];
|
|
||||||
}
|
|
||||||
free_winprop(&prop);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps->o.use_ewmh_active_win && ps->atoms->a_NET_ACTIVE_WINDOW == ev->atom) {
|
if (ps->o.use_ewmh_active_win && ps->atoms->a_NET_ACTIVE_WINDOW == ev->atom) {
|
||||||
// to update focus
|
// to update focus
|
||||||
ps->pending_updates = true;
|
ps->pending_updates = true;
|
||||||
@@ -471,7 +469,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unconcerned about any other proprties on root window
|
// Unconcerned about any other properties on root window
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,9 +479,10 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
|
|||||||
// Check whether it could be a client window
|
// Check whether it could be a client window
|
||||||
if (!find_toplevel(ps, ev->window)) {
|
if (!find_toplevel(ps, ev->window)) {
|
||||||
// Reset event mask anyway
|
// Reset event mask anyway
|
||||||
xcb_change_window_attributes(ps->c, ev->window, XCB_CW_EVENT_MASK,
|
const uint32_t evmask =
|
||||||
(const uint32_t[]){determine_evmask(
|
determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN);
|
||||||
ps, ev->window, WIN_EVMODE_UNKNOWN)});
|
XCB_AWAIT_VOID(xcb_change_window_attributes, ps->c.c, ev->window,
|
||||||
|
XCB_CW_EVENT_MASK, (const uint32_t[]){evmask});
|
||||||
|
|
||||||
auto w_top = find_managed_window_or_parent(ps, ev->window);
|
auto w_top = find_managed_window_or_parent(ps, ev->window);
|
||||||
// ev->window might have not been managed yet, in that case w_top
|
// ev->window might have not been managed yet, in that case w_top
|
||||||
@@ -498,14 +497,14 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
|
|||||||
// If _NET_WM_WINDOW_TYPE changes... God knows why this would happen, but
|
// If _NET_WM_WINDOW_TYPE changes... God knows why this would happen, but
|
||||||
// there are always some stupid applications. (#144)
|
// there are always some stupid applications. (#144)
|
||||||
if (ev->atom == ps->atoms->a_NET_WM_WINDOW_TYPE) {
|
if (ev->atom == ps->atoms->a_NET_WM_WINDOW_TYPE) {
|
||||||
struct managed_win *w = NULL;
|
struct managed_win *w = find_toplevel(ps, ev->window);
|
||||||
if ((w = find_toplevel(ps, ev->window))) {
|
if (w) {
|
||||||
win_set_property_stale(w, ev->atom);
|
win_set_property_stale(w, ev->atom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ev->atom == ps->atoms->a_NET_WM_BYPASS_COMPOSITOR) {
|
if (ev->atom == ps->atoms->a_NET_WM_BYPASS_COMPOSITOR) {
|
||||||
// Unnecessay until we remove the queue_redraw in ev_handle
|
// Unnecessary until we remove the queue_redraw in ev_handle
|
||||||
queue_redraw(ps);
|
queue_redraw(ps);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,6 +565,13 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ps->o.no_ewmh_fullscreen && ev->atom == ps->atoms->a_NET_WM_STATE) {
|
||||||
|
auto w = find_toplevel(ps, ev->window);
|
||||||
|
if (w) {
|
||||||
|
win_set_property_stale(w, ev->atom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check for other atoms we are tracking
|
// Check for other atoms we are tracking
|
||||||
for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) {
|
for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) {
|
||||||
if (platom->atom == ev->atom) {
|
if (platom->atom == ev->atom) {
|
||||||
@@ -592,14 +598,30 @@ static inline void repair_win(session_t *ps, struct managed_win *w) {
|
|||||||
region_t parts;
|
region_t parts;
|
||||||
pixman_region32_init(&parts);
|
pixman_region32_init(&parts);
|
||||||
|
|
||||||
|
// If this is the first time this window is damaged, we would redraw the
|
||||||
|
// whole window, so we don't need to fetch the damage region. But we still need
|
||||||
|
// to make sure the X server receives the DamageSubtract request, hence the
|
||||||
|
// `xcb_request_check` here.
|
||||||
|
// Otherwise, we fetch the damage regions. That means we will receive a reply
|
||||||
|
// from the X server, which implies it has received our DamageSubtract request.
|
||||||
if (!w->ever_damaged) {
|
if (!w->ever_damaged) {
|
||||||
|
auto e = xcb_request_check(
|
||||||
|
ps->c.c, xcb_damage_subtract(ps->c.c, w->damage, XCB_NONE, XCB_NONE));
|
||||||
|
if (e) {
|
||||||
|
if (ps->o.show_all_xerrors) {
|
||||||
|
x_print_error(e->sequence, e->major_code, e->minor_code,
|
||||||
|
e->error_code);
|
||||||
|
}
|
||||||
|
free(e);
|
||||||
|
}
|
||||||
win_extents(w, &parts);
|
win_extents(w, &parts);
|
||||||
set_ignore_cookie(
|
|
||||||
ps, xcb_damage_subtract(ps->c, w->damage, XCB_NONE, XCB_NONE));
|
|
||||||
} else {
|
} else {
|
||||||
set_ignore_cookie(
|
auto cookie =
|
||||||
ps, xcb_damage_subtract(ps->c, w->damage, XCB_NONE, ps->damaged_region));
|
xcb_damage_subtract(ps->c.c, w->damage, XCB_NONE, ps->damaged_region);
|
||||||
x_fetch_region(ps->c, ps->damaged_region, &parts);
|
if (!ps->o.show_all_xerrors) {
|
||||||
|
set_ignore_cookie(&ps->c, cookie);
|
||||||
|
}
|
||||||
|
x_fetch_region(&ps->c, ps->damaged_region, &parts);
|
||||||
pixman_region32_translate(&parts, w->g.x + w->g.border_width,
|
pixman_region32_translate(&parts, w->g.x + w->g.border_width,
|
||||||
w->g.y + w->g.border_width);
|
w->g.y + w->g.border_width);
|
||||||
}
|
}
|
||||||
@@ -674,8 +696,8 @@ ev_selection_clear(session_t *ps, xcb_selection_clear_event_t attr_unused *ev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ev_handle(session_t *ps, xcb_generic_event_t *ev) {
|
void ev_handle(session_t *ps, xcb_generic_event_t *ev) {
|
||||||
if ((ev->response_type & 0x7f) != KeymapNotify) {
|
if (XCB_EVENT_RESPONSE_TYPE(ev) != KeymapNotify) {
|
||||||
discard_pending(ps, ev->full_sequence);
|
x_discard_pending(&ps->c, ev->full_sequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_window_t wid = ev_window(ps, ev);
|
xcb_window_t wid = ev_window(ps, ev);
|
||||||
@@ -696,9 +718,10 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) {
|
|||||||
// For even more details, see:
|
// For even more details, see:
|
||||||
// https://bugs.freedesktop.org/show_bug.cgi?id=35945
|
// https://bugs.freedesktop.org/show_bug.cgi?id=35945
|
||||||
// https://lists.freedesktop.org/archives/xcb/2011-November/007337.html
|
// https://lists.freedesktop.org/archives/xcb/2011-November/007337.html
|
||||||
auto proc = XESetWireToEvent(ps->dpy, ev->response_type, 0);
|
auto response_type = XCB_EVENT_RESPONSE_TYPE(ev);
|
||||||
|
auto proc = XESetWireToEvent(ps->c.dpy, response_type, 0);
|
||||||
if (proc) {
|
if (proc) {
|
||||||
XESetWireToEvent(ps->dpy, ev->response_type, proc);
|
XESetWireToEvent(ps->c.dpy, response_type, proc);
|
||||||
XEvent dummy;
|
XEvent dummy;
|
||||||
|
|
||||||
// Stop Xlib from complaining about lost sequence numbers.
|
// Stop Xlib from complaining about lost sequence numbers.
|
||||||
@@ -708,8 +731,8 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) {
|
|||||||
//
|
//
|
||||||
// We only need the low 16 bits
|
// We only need the low 16 bits
|
||||||
uint16_t seq = ev->sequence;
|
uint16_t seq = ev->sequence;
|
||||||
ev->sequence = (uint16_t)(LastKnownRequestProcessed(ps->dpy) & 0xffff);
|
ev->sequence = (uint16_t)(LastKnownRequestProcessed(ps->c.dpy) & 0xffff);
|
||||||
proc(ps->dpy, &dummy, (xEvent *)ev);
|
proc(ps->c.dpy, &dummy, (xEvent *)ev);
|
||||||
// Restore the sequence number
|
// Restore the sequence number
|
||||||
ev->sequence = seq;
|
ev->sequence = seq;
|
||||||
}
|
}
|
||||||
@@ -717,6 +740,8 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) {
|
|||||||
// XXX redraw needs to be more fine grained
|
// XXX redraw needs to be more fine grained
|
||||||
queue_redraw(ps);
|
queue_redraw(ps);
|
||||||
|
|
||||||
|
// We intentionally ignore events sent via SendEvent. Those events has the 8th bit
|
||||||
|
// of response_type set, meaning they will match none of the cases below.
|
||||||
switch (ev->response_type) {
|
switch (ev->response_type) {
|
||||||
case FocusIn: ev_focus_in(ps, (xcb_focus_in_event_t *)ev); break;
|
case FocusIn: ev_focus_in(ps, (xcb_focus_in_event_t *)ev); break;
|
||||||
case FocusOut: ev_focus_out(ps, (xcb_focus_out_event_t *)ev); break;
|
case FocusOut: ev_focus_out(ps, (xcb_focus_out_event_t *)ev); break;
|
||||||
@@ -742,7 +767,7 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) {
|
|||||||
case SelectionClear:
|
case SelectionClear:
|
||||||
ev_selection_clear(ps, (xcb_selection_clear_event_t *)ev);
|
ev_selection_clear(ps, (xcb_selection_clear_event_t *)ev);
|
||||||
break;
|
break;
|
||||||
case 0: ev_xcb_error(ps, (xcb_generic_error_t *)ev); break;
|
case 0: x_handle_error(&ps->c, (xcb_generic_error_t *)ev); break;
|
||||||
default:
|
default:
|
||||||
if (ps->shape_exists && ev->response_type == ps->shape_event) {
|
if (ps->shape_exists && ev->response_type == ps->shape_event) {
|
||||||
ev_shape_notify(ps, (xcb_shape_notify_event_t *)ev);
|
ev_shape_notify(ps, (xcb_shape_notify_event_t *)ev);
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ bool file_watch_add(void *_fwr, const char *filename, file_watch_cb_t cb, void *
|
|||||||
fflags |= NOTE_CLOSE_WRITE;
|
fflags |= NOTE_CLOSE_WRITE;
|
||||||
#else
|
#else
|
||||||
// NOTE_WRITE will receive notification more frequent than necessary, so is less
|
// NOTE_WRITE will receive notification more frequent than necessary, so is less
|
||||||
// preferrable
|
// preferable
|
||||||
fflags |= NOTE_WRITE;
|
fflags |= NOTE_WRITE;
|
||||||
#endif
|
#endif
|
||||||
struct kevent ev = {
|
struct kevent ev = {
|
||||||
|
|||||||
@@ -54,8 +54,9 @@ static inline double attr_const gaussian(double r, double x, double y) {
|
|||||||
// Formula can be found here:
|
// Formula can be found here:
|
||||||
// https://en.wikipedia.org/wiki/Gaussian_blur#Mathematics
|
// https://en.wikipedia.org/wiki/Gaussian_blur#Mathematics
|
||||||
// Except a special case for r == 0 to produce sharp shadows
|
// Except a special case for r == 0 to produce sharp shadows
|
||||||
if (r == 0)
|
if (r == 0) {
|
||||||
return 1;
|
return 1;
|
||||||
|
}
|
||||||
return exp(-0.5 * (x * x + y * y) / (r * r)) / (2 * M_PI * r * r);
|
return exp(-0.5 * (x * x + y * y) / (r * r)) / (2 * M_PI * r * r);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +96,7 @@ static inline double estimate_first_row_sum(double size, double r) {
|
|||||||
// `a` is gaussian at (size, 0)
|
// `a` is gaussian at (size, 0)
|
||||||
double a = exp(-0.5 * size * size / (r * r)) / sqrt(2 * M_PI) / r;
|
double a = exp(-0.5 * size * size / (r * r)) / sqrt(2 * M_PI) / r;
|
||||||
// The sum of the whole kernel is normalized to 1, i.e. each element is divided by
|
// The sum of the whole kernel is normalized to 1, i.e. each element is divided by
|
||||||
// factor sqaured. So the sum of the first row is a * factor / factor^2 = a /
|
// factor squared. So the sum of the first row is a * factor / factor^2 = a /
|
||||||
// factor
|
// factor
|
||||||
return a / factor;
|
return a / factor;
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/list.h
13
src/list.h
@@ -2,18 +2,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
/**
|
#include "utils.h"
|
||||||
* container_of - cast a member of a structure out to the containing structure
|
|
||||||
* @ptr: the pointer to the member.
|
|
||||||
* @type: the type of the container struct this is embedded in.
|
|
||||||
* @member: the name of the member within the struct.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#define container_of(ptr, type, member) \
|
|
||||||
({ \
|
|
||||||
const __typeof__(((type *)0)->member) *__mptr = (ptr); \
|
|
||||||
(type *)((char *)__mptr - offsetof(type, member)); \
|
|
||||||
})
|
|
||||||
|
|
||||||
struct list_node {
|
struct list_node {
|
||||||
struct list_node *next, *prev;
|
struct list_node *next, *prev;
|
||||||
|
|||||||
57
src/log.c
57
src/log.c
@@ -9,7 +9,7 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#ifdef CONFIG_OPENGL
|
#ifdef CONFIG_OPENGL
|
||||||
#include <GL/gl.h>
|
#include <epoxy/gl.h>
|
||||||
#include "backend/gl/gl_common.h"
|
#include "backend/gl/gl_common.h"
|
||||||
#include "backend/gl/glx.h"
|
#include "backend/gl/glx.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -68,6 +68,7 @@ log_default_writev(struct log_target *tgt, const struct iovec *vec, int vcnt) {
|
|||||||
static attr_const const char *log_level_to_string(enum log_level level) {
|
static attr_const const char *log_level_to_string(enum log_level level) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case LOG_LEVEL_TRACE: return "TRACE";
|
case LOG_LEVEL_TRACE: return "TRACE";
|
||||||
|
case LOG_LEVEL_VERBOSE: return "VERBOSE";
|
||||||
case LOG_LEVEL_DEBUG: return "DEBUG";
|
case LOG_LEVEL_DEBUG: return "DEBUG";
|
||||||
case LOG_LEVEL_INFO: return "INFO";
|
case LOG_LEVEL_INFO: return "INFO";
|
||||||
case LOG_LEVEL_WARN: return "WARN";
|
case LOG_LEVEL_WARN: return "WARN";
|
||||||
@@ -78,16 +79,24 @@ static attr_const const char *log_level_to_string(enum log_level level) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum log_level string_to_log_level(const char *str) {
|
enum log_level string_to_log_level(const char *str) {
|
||||||
if (strcasecmp(str, "TRACE") == 0)
|
if (strcasecmp(str, "TRACE") == 0) {
|
||||||
return LOG_LEVEL_TRACE;
|
return LOG_LEVEL_TRACE;
|
||||||
else if (strcasecmp(str, "DEBUG") == 0)
|
}
|
||||||
|
if (strcasecmp(str, "VERBOSE") == 0) {
|
||||||
|
return LOG_LEVEL_VERBOSE;
|
||||||
|
}
|
||||||
|
if (strcasecmp(str, "DEBUG") == 0) {
|
||||||
return LOG_LEVEL_DEBUG;
|
return LOG_LEVEL_DEBUG;
|
||||||
else if (strcasecmp(str, "INFO") == 0)
|
}
|
||||||
|
if (strcasecmp(str, "INFO") == 0) {
|
||||||
return LOG_LEVEL_INFO;
|
return LOG_LEVEL_INFO;
|
||||||
else if (strcasecmp(str, "WARN") == 0)
|
}
|
||||||
|
if (strcasecmp(str, "WARN") == 0) {
|
||||||
return LOG_LEVEL_WARN;
|
return LOG_LEVEL_WARN;
|
||||||
else if (strcasecmp(str, "ERROR") == 0)
|
}
|
||||||
|
if (strcasecmp(str, "ERROR") == 0) {
|
||||||
return LOG_LEVEL_ERROR;
|
return LOG_LEVEL_ERROR;
|
||||||
|
}
|
||||||
return LOG_LEVEL_INVALID;
|
return LOG_LEVEL_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,8 +152,9 @@ enum log_level log_get_level(const struct log *l) {
|
|||||||
attr_printf(4, 5) void log_printf(struct log *l, int level, const char *func,
|
attr_printf(4, 5) void log_printf(struct log *l, int level, const char *func,
|
||||||
const char *fmt, ...) {
|
const char *fmt, ...) {
|
||||||
assert(level <= LOG_LEVEL_FATAL && level >= 0);
|
assert(level <= LOG_LEVEL_FATAL && level >= 0);
|
||||||
if (level < l->log_level)
|
if (level < l->log_level) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
char *buf = NULL;
|
char *buf = NULL;
|
||||||
va_list args;
|
va_list args;
|
||||||
@@ -228,12 +238,10 @@ struct log_target *null_logger_new(void) {
|
|||||||
|
|
||||||
static void null_logger_write(struct log_target *tgt attr_unused,
|
static void null_logger_write(struct log_target *tgt attr_unused,
|
||||||
const char *str attr_unused, size_t len attr_unused) {
|
const char *str attr_unused, size_t len attr_unused) {
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void null_logger_writev(struct log_target *tgt attr_unused,
|
static void null_logger_writev(struct log_target *tgt attr_unused,
|
||||||
const struct iovec *vec attr_unused, int vcnt attr_unused) {
|
const struct iovec *vec attr_unused, int vcnt attr_unused) {
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct log_ops null_logger_ops = {
|
static const struct log_ops null_logger_ops = {
|
||||||
@@ -269,6 +277,7 @@ static void file_logger_destroy(struct log_target *tgt) {
|
|||||||
static const char *terminal_colorize_begin(enum log_level level) {
|
static const char *terminal_colorize_begin(enum log_level level) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case LOG_LEVEL_TRACE: return ANSI("30;2");
|
case LOG_LEVEL_TRACE: return ANSI("30;2");
|
||||||
|
case LOG_LEVEL_VERBOSE:
|
||||||
case LOG_LEVEL_DEBUG: return ANSI("37;2");
|
case LOG_LEVEL_DEBUG: return ANSI("37;2");
|
||||||
case LOG_LEVEL_INFO: return ANSI("92");
|
case LOG_LEVEL_INFO: return ANSI("92");
|
||||||
case LOG_LEVEL_WARN: return ANSI("33");
|
case LOG_LEVEL_WARN: return ANSI("33");
|
||||||
@@ -329,21 +338,14 @@ struct log_target *stderr_logger_new(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_OPENGL
|
#ifdef CONFIG_OPENGL
|
||||||
/// An opengl logger that can be used for logging into opengl debugging tools,
|
|
||||||
/// such as apitrace
|
|
||||||
struct gl_string_marker_logger {
|
|
||||||
struct log_target tgt;
|
|
||||||
PFNGLSTRINGMARKERGREMEDYPROC gl_string_marker;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
static void gl_string_marker_logger_write(struct log_target *tgt attr_unused,
|
||||||
gl_string_marker_logger_write(struct log_target *tgt, const char *str, size_t len) {
|
const char *str, size_t len) {
|
||||||
auto g = (struct gl_string_marker_logger *)tgt;
|
|
||||||
// strip newlines at the end of the string
|
// strip newlines at the end of the string
|
||||||
while (len > 0 && str[len-1] == '\n') {
|
while (len > 0 && str[len - 1] == '\n') {
|
||||||
len--;
|
len--;
|
||||||
}
|
}
|
||||||
g->gl_string_marker((GLsizei)len, str);
|
glStringMarkerGREMEDY((GLsizei)len, str);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct log_ops gl_string_marker_logger_ops = {
|
static const struct log_ops gl_string_marker_logger_ops = {
|
||||||
@@ -352,19 +354,16 @@ static const struct log_ops gl_string_marker_logger_ops = {
|
|||||||
.destroy = logger_trivial_destroy,
|
.destroy = logger_trivial_destroy,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Create an opengl logger that can be used for logging into opengl debugging tools,
|
||||||
|
/// such as apitrace
|
||||||
struct log_target *gl_string_marker_logger_new(void) {
|
struct log_target *gl_string_marker_logger_new(void) {
|
||||||
if (!gl_has_extension("GL_GREMEDY_string_marker")) {
|
if (!epoxy_has_gl_extension("GL_GREMEDY_string_marker")) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *fnptr = glXGetProcAddress((GLubyte *)"glStringMarkerGREMEDY");
|
auto ret = cmalloc(struct log_target);
|
||||||
if (!fnptr)
|
ret->ops = &gl_string_marker_logger_ops;
|
||||||
return NULL;
|
return ret;
|
||||||
|
|
||||||
auto ret = cmalloc(struct gl_string_marker_logger);
|
|
||||||
ret->tgt.ops = &gl_string_marker_logger_ops;
|
|
||||||
ret->gl_string_marker = fnptr;
|
|
||||||
return &ret->tgt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -9,11 +9,19 @@
|
|||||||
|
|
||||||
enum log_level {
|
enum log_level {
|
||||||
LOG_LEVEL_INVALID = -1,
|
LOG_LEVEL_INVALID = -1,
|
||||||
|
/// Very noisy debug messages, many lines per frame.
|
||||||
LOG_LEVEL_TRACE = 0,
|
LOG_LEVEL_TRACE = 0,
|
||||||
|
/// Frequent debug messages, a few lines per frame.
|
||||||
|
LOG_LEVEL_VERBOSE,
|
||||||
|
/// Less frequent debug messages.
|
||||||
LOG_LEVEL_DEBUG,
|
LOG_LEVEL_DEBUG,
|
||||||
|
/// Informational messages.
|
||||||
LOG_LEVEL_INFO,
|
LOG_LEVEL_INFO,
|
||||||
|
/// Warnings.
|
||||||
LOG_LEVEL_WARN,
|
LOG_LEVEL_WARN,
|
||||||
|
/// Errors.
|
||||||
LOG_LEVEL_ERROR,
|
LOG_LEVEL_ERROR,
|
||||||
|
/// Fatal errors.
|
||||||
LOG_LEVEL_FATAL,
|
LOG_LEVEL_FATAL,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,6 +39,7 @@ enum log_level {
|
|||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
#define log_trace(x, ...) LOG_UNLIKELY(TRACE, x, ##__VA_ARGS__)
|
#define log_trace(x, ...) LOG_UNLIKELY(TRACE, x, ##__VA_ARGS__)
|
||||||
|
#define log_verbose(x, ...) LOG_UNLIKELY(VERBOSE, x, ##__VA_ARGS__)
|
||||||
#define log_debug(x, ...) LOG_UNLIKELY(DEBUG, x, ##__VA_ARGS__)
|
#define log_debug(x, ...) LOG_UNLIKELY(DEBUG, x, ##__VA_ARGS__)
|
||||||
#define log_info(x, ...) LOG(INFO, x, ##__VA_ARGS__)
|
#define log_info(x, ...) LOG(INFO, x, ##__VA_ARGS__)
|
||||||
#define log_warn(x, ...) LOG(WARN, x, ##__VA_ARGS__)
|
#define log_warn(x, ...) LOG(WARN, x, ##__VA_ARGS__)
|
||||||
|
|||||||
@@ -9,19 +9,21 @@ base_deps = [
|
|||||||
|
|
||||||
srcs = [ files('picom.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', 'utils.c',
|
srcs = [ files('picom.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', 'utils.c',
|
||||||
'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c', 'log.c',
|
'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c', 'log.c',
|
||||||
'options.c', 'event.c', 'cache.c', 'atom.c', 'file_watch.c') ]
|
'options.c', 'event.c', 'cache.c', 'atom.c', 'file_watch.c', 'statistics.c',
|
||||||
|
'vblank.c') ]
|
||||||
picom_inc = include_directories('.')
|
picom_inc = include_directories('.')
|
||||||
|
|
||||||
cflags = []
|
cflags = []
|
||||||
|
|
||||||
required_xcb_packages = [
|
required_xcb_packages = [
|
||||||
'xcb-render', 'xcb-damage', 'xcb-randr', 'xcb-sync', 'xcb-composite',
|
'xcb', 'xcb-composite', 'xcb-damage', 'xcb-dpms', 'xcb-glx', 'xcb-present',
|
||||||
'xcb-shape', 'xcb-xinerama', 'xcb-xfixes', 'xcb-present', 'xcb-glx',
|
'xcb-randr', 'xcb-render', 'xcb-shape', 'xcb-sync', 'xcb-xfixes'
|
||||||
'xcb-dpms', 'xcb'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Some XCB packages are here because their versioning differs (see check below).
|
||||||
required_packages = [
|
required_packages = [
|
||||||
'x11', 'x11-xcb', 'xcb-renderutil', 'xcb-image', 'xext', 'pixman-1'
|
'pixman-1', 'x11', 'x11-xcb', 'xcb-image', 'xcb-renderutil', 'xcb-util',
|
||||||
|
'xext', 'threads',
|
||||||
]
|
]
|
||||||
|
|
||||||
foreach i : required_packages
|
foreach i : required_packages
|
||||||
@@ -56,8 +58,8 @@ if get_option('vsync_drm')
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
if get_option('opengl')
|
if get_option('opengl')
|
||||||
cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES']
|
cflags += ['-DCONFIG_OPENGL']
|
||||||
deps += [dependency('gl', required: true), dependency('egl', required: true)]
|
deps += [dependency('epoxy', required: true)]
|
||||||
srcs += [ 'opengl.c' ]
|
srcs += [ 'opengl.c' ]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|||||||
150
src/opengl.c
150
src/opengl.c
@@ -39,7 +39,7 @@ static inline XVisualInfo *get_visualinfo_from_visual(session_t *ps, xcb_visuali
|
|||||||
XVisualInfo vreq = {.visualid = visual};
|
XVisualInfo vreq = {.visualid = visual};
|
||||||
int nitems = 0;
|
int nitems = 0;
|
||||||
|
|
||||||
return XGetVisualInfo(ps->dpy, VisualIDMask, &vreq, &nitems);
|
return XGetVisualInfo(ps->c.dpy, VisualIDMask, &vreq, &nitems);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,7 +56,7 @@ bool glx_init(session_t *ps, bool need_render) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get XVisualInfo
|
// Get XVisualInfo
|
||||||
pvis = get_visualinfo_from_visual(ps, ps->vis);
|
pvis = get_visualinfo_from_visual(ps, ps->c.screen_info->root_visual);
|
||||||
if (!pvis) {
|
if (!pvis) {
|
||||||
log_error("Failed to acquire XVisualInfo for current visual.");
|
log_error("Failed to acquire XVisualInfo for current visual.");
|
||||||
goto glx_init_end;
|
goto glx_init_end;
|
||||||
@@ -65,20 +65,22 @@ bool glx_init(session_t *ps, bool need_render) {
|
|||||||
// Ensure the visual is double-buffered
|
// Ensure the visual is double-buffered
|
||||||
if (need_render) {
|
if (need_render) {
|
||||||
int value = 0;
|
int value = 0;
|
||||||
if (Success != glXGetConfig(ps->dpy, pvis, GLX_USE_GL, &value) || !value) {
|
if (Success != glXGetConfig(ps->c.dpy, pvis, GLX_USE_GL, &value) || !value) {
|
||||||
log_error("Root visual is not a GL visual.");
|
log_error("Root visual is not a GL visual.");
|
||||||
goto glx_init_end;
|
goto glx_init_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Success != glXGetConfig(ps->dpy, pvis, GLX_DOUBLEBUFFER, &value) || !value) {
|
if (Success != glXGetConfig(ps->c.dpy, pvis, GLX_DOUBLEBUFFER, &value) ||
|
||||||
|
!value) {
|
||||||
log_error("Root visual is not a double buffered GL visual.");
|
log_error("Root visual is not a double buffered GL visual.");
|
||||||
goto glx_init_end;
|
goto glx_init_end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure GLX_EXT_texture_from_pixmap exists
|
// Ensure GLX_EXT_texture_from_pixmap exists
|
||||||
if (need_render && !glxext.has_GLX_EXT_texture_from_pixmap)
|
if (need_render && !glxext.has_GLX_EXT_texture_from_pixmap) {
|
||||||
goto glx_init_end;
|
goto glx_init_end;
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize GLX data structure
|
// Initialize GLX data structure
|
||||||
if (!ps->psglx) {
|
if (!ps->psglx) {
|
||||||
@@ -112,7 +114,7 @@ bool glx_init(session_t *ps, bool need_render) {
|
|||||||
if (!psglx->context) {
|
if (!psglx->context) {
|
||||||
// Get GLX context
|
// Get GLX context
|
||||||
#ifndef DEBUG_GLX_DEBUG_CONTEXT
|
#ifndef DEBUG_GLX_DEBUG_CONTEXT
|
||||||
psglx->context = glXCreateContext(ps->dpy, pvis, None, GL_TRUE);
|
psglx->context = glXCreateContext(ps->c.dpy, pvis, None, GL_TRUE);
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
GLXFBConfig fbconfig = get_fbconfig_from_visualinfo(ps, pvis);
|
GLXFBConfig fbconfig = get_fbconfig_from_visualinfo(ps, pvis);
|
||||||
@@ -134,7 +136,7 @@ bool glx_init(session_t *ps, bool need_render) {
|
|||||||
static const int attrib_list[] = {
|
static const int attrib_list[] = {
|
||||||
GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB, None};
|
GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB, None};
|
||||||
psglx->context = p_glXCreateContextAttribsARB(
|
psglx->context = p_glXCreateContextAttribsARB(
|
||||||
ps->dpy, fbconfig, NULL, GL_TRUE, attrib_list);
|
ps->c.dpy, fbconfig, NULL, GL_TRUE, attrib_list);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -144,7 +146,7 @@ bool glx_init(session_t *ps, bool need_render) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attach GLX context
|
// Attach GLX context
|
||||||
if (!glXMakeCurrent(ps->dpy, get_tgt_window(ps), psglx->context)) {
|
if (!glXMakeCurrent(ps->c.dpy, get_tgt_window(ps), psglx->context)) {
|
||||||
log_error("Failed to attach GLX context.");
|
log_error("Failed to attach GLX context.");
|
||||||
goto glx_init_end;
|
goto glx_init_end;
|
||||||
}
|
}
|
||||||
@@ -177,9 +179,10 @@ bool glx_init(session_t *ps, bool need_render) {
|
|||||||
|
|
||||||
// Check GL_ARB_texture_non_power_of_two, requires a GLX context and
|
// Check GL_ARB_texture_non_power_of_two, requires a GLX context and
|
||||||
// must precede FBConfig fetching
|
// must precede FBConfig fetching
|
||||||
if (need_render)
|
if (need_render) {
|
||||||
psglx->has_texture_non_power_of_two =
|
psglx->has_texture_non_power_of_two =
|
||||||
gl_has_extension("GL_ARB_texture_non_power_of_two");
|
epoxy_has_gl_extension("GL_ARB_texture_non_power_of_two");
|
||||||
|
}
|
||||||
|
|
||||||
// Render preparations
|
// Render preparations
|
||||||
if (need_render) {
|
if (need_render) {
|
||||||
@@ -199,9 +202,9 @@ bool glx_init(session_t *ps, bool need_render) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear screen
|
// Clear screen
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
glClearColor(0.0F, 0.0F, 0.0F, 1.0F);
|
||||||
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
// glXSwapBuffers(ps->dpy, get_tgt_window(ps));
|
// glXSwapBuffers(ps->c.dpy, get_tgt_window(ps));
|
||||||
}
|
}
|
||||||
|
|
||||||
success = true;
|
success = true;
|
||||||
@@ -209,15 +212,17 @@ bool glx_init(session_t *ps, bool need_render) {
|
|||||||
glx_init_end:
|
glx_init_end:
|
||||||
XFree(pvis);
|
XFree(pvis);
|
||||||
|
|
||||||
if (!success)
|
if (!success) {
|
||||||
glx_destroy(ps);
|
glx_destroy(ps);
|
||||||
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void glx_free_prog_main(glx_prog_main_t *pprogram) {
|
static void glx_free_prog_main(glx_prog_main_t *pprogram) {
|
||||||
if (!pprogram)
|
if (!pprogram) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
if (pprogram->prog) {
|
if (pprogram->prog) {
|
||||||
glDeleteProgram(pprogram->prog);
|
glDeleteProgram(pprogram->prog);
|
||||||
pprogram->prog = 0;
|
pprogram->prog = 0;
|
||||||
@@ -231,8 +236,9 @@ static void glx_free_prog_main(glx_prog_main_t *pprogram) {
|
|||||||
* Destroy GLX related resources.
|
* Destroy GLX related resources.
|
||||||
*/
|
*/
|
||||||
void glx_destroy(session_t *ps) {
|
void glx_destroy(session_t *ps) {
|
||||||
if (!ps->psglx)
|
if (!ps->psglx) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Free all GLX resources of windows
|
// Free all GLX resources of windows
|
||||||
win_stack_foreach_managed(w, &ps->window_stack) {
|
win_stack_foreach_managed(w, &ps->window_stack) {
|
||||||
@@ -266,14 +272,14 @@ void glx_destroy(session_t *ps) {
|
|||||||
|
|
||||||
// Destroy GLX context
|
// Destroy GLX context
|
||||||
if (ps->psglx->context) {
|
if (ps->psglx->context) {
|
||||||
glXMakeCurrent(ps->dpy, None, NULL);
|
glXMakeCurrent(ps->c.dpy, None, NULL);
|
||||||
glXDestroyContext(ps->dpy, ps->psglx->context);
|
glXDestroyContext(ps->c.dpy, ps->psglx->context);
|
||||||
ps->psglx->context = NULL;
|
ps->psglx->context = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(ps->psglx);
|
free(ps->psglx);
|
||||||
ps->psglx = NULL;
|
ps->psglx = NULL;
|
||||||
ps->argb_fbconfig = NULL;
|
ps->argb_fbconfig = (struct glx_fbconfig_info){0};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -372,8 +378,9 @@ bool glx_init_blur(session_t *ps) {
|
|||||||
double sum = 0.0;
|
double sum = 0.0;
|
||||||
for (int j = 0; j < height; ++j) {
|
for (int j = 0; j < height; ++j) {
|
||||||
for (int k = 0; k < width; ++k) {
|
for (int k = 0; k < width; ++k) {
|
||||||
if (height / 2 == j && width / 2 == k)
|
if (height / 2 == j && width / 2 == k) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
double val = kern->data[j * width + k];
|
double val = kern->data[j * width + k];
|
||||||
if (val == 0) {
|
if (val == 0) {
|
||||||
continue;
|
continue;
|
||||||
@@ -690,8 +697,9 @@ bool glx_bind_texture(session_t *ps attr_unused, glx_texture_t **pptex, int x, i
|
|||||||
*/
|
*/
|
||||||
bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, int width,
|
bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, int width,
|
||||||
int height, bool repeat, const struct glx_fbconfig_info *fbcfg) {
|
int height, bool repeat, const struct glx_fbconfig_info *fbcfg) {
|
||||||
if (ps->o.backend != BKEND_GLX && ps->o.backend != BKEND_XR_GLX_HYBRID)
|
if (ps->o.backend != BKEND_GLX && ps->o.backend != BKEND_XR_GLX_HYBRID) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!pixmap) {
|
if (!pixmap) {
|
||||||
log_error("Binding to an empty pixmap %#010x. This can't work.", pixmap);
|
log_error("Binding to an empty pixmap %#010x. This can't work.", pixmap);
|
||||||
@@ -732,7 +740,7 @@ bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap,
|
|||||||
// Retrieve pixmap parameters, if they aren't provided
|
// Retrieve pixmap parameters, if they aren't provided
|
||||||
if (!width || !height) {
|
if (!width || !height) {
|
||||||
auto r = xcb_get_geometry_reply(
|
auto r = xcb_get_geometry_reply(
|
||||||
ps->c, xcb_get_geometry(ps->c, pixmap), NULL);
|
ps->c.c, xcb_get_geometry(ps->c.c, pixmap), NULL);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
log_error("Failed to query info of pixmap %#010x.", pixmap);
|
log_error("Failed to query info of pixmap %#010x.", pixmap);
|
||||||
return false;
|
return false;
|
||||||
@@ -753,14 +761,15 @@ bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap,
|
|||||||
// pixmap-specific parameters, and this may change in the future
|
// pixmap-specific parameters, and this may change in the future
|
||||||
GLenum tex_tgt = 0;
|
GLenum tex_tgt = 0;
|
||||||
if (GLX_TEXTURE_2D_BIT_EXT & fbcfg->texture_tgts &&
|
if (GLX_TEXTURE_2D_BIT_EXT & fbcfg->texture_tgts &&
|
||||||
ps->psglx->has_texture_non_power_of_two)
|
ps->psglx->has_texture_non_power_of_two) {
|
||||||
tex_tgt = GLX_TEXTURE_2D_EXT;
|
tex_tgt = GLX_TEXTURE_2D_EXT;
|
||||||
else if (GLX_TEXTURE_RECTANGLE_BIT_EXT & fbcfg->texture_tgts)
|
} else if (GLX_TEXTURE_RECTANGLE_BIT_EXT & fbcfg->texture_tgts) {
|
||||||
tex_tgt = GLX_TEXTURE_RECTANGLE_EXT;
|
tex_tgt = GLX_TEXTURE_RECTANGLE_EXT;
|
||||||
else if (!(GLX_TEXTURE_2D_BIT_EXT & fbcfg->texture_tgts))
|
} else if (!(GLX_TEXTURE_2D_BIT_EXT & fbcfg->texture_tgts)) {
|
||||||
tex_tgt = GLX_TEXTURE_RECTANGLE_EXT;
|
tex_tgt = GLX_TEXTURE_RECTANGLE_EXT;
|
||||||
else
|
} else {
|
||||||
tex_tgt = GLX_TEXTURE_2D_EXT;
|
tex_tgt = GLX_TEXTURE_2D_EXT;
|
||||||
|
}
|
||||||
|
|
||||||
log_debug("depth %d, tgt %#x, rgba %d", depth, tex_tgt,
|
log_debug("depth %d, tgt %#x, rgba %d", depth, tex_tgt,
|
||||||
(GLX_TEXTURE_FORMAT_RGBA_EXT == fbcfg->texture_fmt));
|
(GLX_TEXTURE_FORMAT_RGBA_EXT == fbcfg->texture_fmt));
|
||||||
@@ -773,7 +782,7 @@ bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap,
|
|||||||
0,
|
0,
|
||||||
};
|
};
|
||||||
|
|
||||||
ptex->glpixmap = glXCreatePixmap(ps->dpy, fbcfg->cfg, pixmap, attrs);
|
ptex->glpixmap = glXCreatePixmap(ps->c.dpy, fbcfg->cfg, pixmap, attrs);
|
||||||
ptex->pixmap = pixmap;
|
ptex->pixmap = pixmap;
|
||||||
ptex->target =
|
ptex->target =
|
||||||
(GLX_TEXTURE_2D_EXT == tex_tgt ? GL_TEXTURE_2D : GL_TEXTURE_RECTANGLE);
|
(GLX_TEXTURE_2D_EXT == tex_tgt ? GL_TEXTURE_2D : GL_TEXTURE_RECTANGLE);
|
||||||
@@ -819,10 +828,11 @@ bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap,
|
|||||||
|
|
||||||
// The specification requires rebinding whenever the content changes...
|
// The specification requires rebinding whenever the content changes...
|
||||||
// We can't follow this, too slow.
|
// We can't follow this, too slow.
|
||||||
if (need_release)
|
if (need_release) {
|
||||||
glXReleaseTexImageEXT(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT);
|
glXReleaseTexImageEXT(ps->c.dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT);
|
||||||
|
}
|
||||||
|
|
||||||
glXBindTexImageEXT(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT, NULL);
|
glXBindTexImageEXT(ps->c.dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT, NULL);
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
glBindTexture(ptex->target, 0);
|
glBindTexture(ptex->target, 0);
|
||||||
@@ -840,13 +850,13 @@ void glx_release_pixmap(session_t *ps, glx_texture_t *ptex) {
|
|||||||
// Release binding
|
// Release binding
|
||||||
if (ptex->glpixmap && ptex->texture) {
|
if (ptex->glpixmap && ptex->texture) {
|
||||||
glBindTexture(ptex->target, ptex->texture);
|
glBindTexture(ptex->target, ptex->texture);
|
||||||
glXReleaseTexImageEXT(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT);
|
glXReleaseTexImageEXT(ps->c.dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT);
|
||||||
glBindTexture(ptex->target, 0);
|
glBindTexture(ptex->target, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free GLX Pixmap
|
// Free GLX Pixmap
|
||||||
if (ptex->glpixmap) {
|
if (ptex->glpixmap) {
|
||||||
glXDestroyPixmap(ps->dpy, ptex->glpixmap);
|
glXDestroyPixmap(ps->c.dpy, ptex->glpixmap);
|
||||||
ptex->glpixmap = 0;
|
ptex->glpixmap = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -858,14 +868,16 @@ void glx_release_pixmap(session_t *ps, glx_texture_t *ptex) {
|
|||||||
*/
|
*/
|
||||||
void glx_set_clip(session_t *ps, const region_t *reg) {
|
void glx_set_clip(session_t *ps, const region_t *reg) {
|
||||||
// Quit if we aren't using stencils
|
// Quit if we aren't using stencils
|
||||||
if (ps->o.glx_no_stencil)
|
if (ps->o.glx_no_stencil) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
glDisable(GL_STENCIL_TEST);
|
glDisable(GL_STENCIL_TEST);
|
||||||
glDisable(GL_SCISSOR_TEST);
|
glDisable(GL_SCISSOR_TEST);
|
||||||
|
|
||||||
if (!reg)
|
if (!reg) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int nrects;
|
int nrects;
|
||||||
const rect_t *rects = pixman_region32_rectangles((region_t *)reg, &nrects);
|
const rect_t *rects = pixman_region32_rectangles((region_t *)reg, &nrects);
|
||||||
@@ -913,8 +925,9 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z,
|
|||||||
|
|
||||||
// Calculate copy region size
|
// Calculate copy region size
|
||||||
glx_blur_cache_t ibc = {.width = 0, .height = 0};
|
glx_blur_cache_t ibc = {.width = 0, .height = 0};
|
||||||
if (!pbc)
|
if (!pbc) {
|
||||||
pbc = &ibc;
|
pbc = &ibc;
|
||||||
|
}
|
||||||
|
|
||||||
int mdx = dx, mdy = dy, mwidth = width, mheight = height;
|
int mdx = dx, mdy = dy, mwidth = width, mheight = height;
|
||||||
// log_trace("%d, %d, %d, %d", mdx, mdy, mwidth, mheight);
|
// log_trace("%d, %d, %d, %d", mdx, mdy, mwidth, mheight);
|
||||||
@@ -941,24 +954,29 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z,
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
GLenum tex_tgt = GL_TEXTURE_RECTANGLE;
|
GLenum tex_tgt = GL_TEXTURE_RECTANGLE;
|
||||||
if (ps->psglx->has_texture_non_power_of_two)
|
if (ps->psglx->has_texture_non_power_of_two) {
|
||||||
tex_tgt = GL_TEXTURE_2D;
|
tex_tgt = GL_TEXTURE_2D;
|
||||||
|
}
|
||||||
|
|
||||||
// Free textures if size inconsistency discovered
|
// Free textures if size inconsistency discovered
|
||||||
if (mwidth != pbc->width || mheight != pbc->height)
|
if (mwidth != pbc->width || mheight != pbc->height) {
|
||||||
free_glx_bc_resize(ps, pbc);
|
free_glx_bc_resize(ps, pbc);
|
||||||
|
}
|
||||||
|
|
||||||
// Generate FBO and textures if needed
|
// Generate FBO and textures if needed
|
||||||
if (!pbc->textures[0])
|
if (!pbc->textures[0]) {
|
||||||
pbc->textures[0] = glx_gen_texture(tex_tgt, mwidth, mheight);
|
pbc->textures[0] = glx_gen_texture(tex_tgt, mwidth, mheight);
|
||||||
|
}
|
||||||
GLuint tex_scr = pbc->textures[0];
|
GLuint tex_scr = pbc->textures[0];
|
||||||
if (more_passes && !pbc->textures[1])
|
if (more_passes && !pbc->textures[1]) {
|
||||||
pbc->textures[1] = glx_gen_texture(tex_tgt, mwidth, mheight);
|
pbc->textures[1] = glx_gen_texture(tex_tgt, mwidth, mheight);
|
||||||
|
}
|
||||||
pbc->width = mwidth;
|
pbc->width = mwidth;
|
||||||
pbc->height = mheight;
|
pbc->height = mheight;
|
||||||
GLuint tex_scr2 = pbc->textures[1];
|
GLuint tex_scr2 = pbc->textures[1];
|
||||||
if (more_passes && !pbc->fbo)
|
if (more_passes && !pbc->fbo) {
|
||||||
glGenFramebuffers(1, &pbc->fbo);
|
glGenFramebuffers(1, &pbc->fbo);
|
||||||
|
}
|
||||||
const GLuint fbo = pbc->fbo;
|
const GLuint fbo = pbc->fbo;
|
||||||
|
|
||||||
if (!tex_scr || (more_passes && !tex_scr2)) {
|
if (!tex_scr || (more_passes && !tex_scr2)) {
|
||||||
@@ -986,7 +1004,7 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z,
|
|||||||
} */
|
} */
|
||||||
|
|
||||||
// Texture scaling factor
|
// Texture scaling factor
|
||||||
GLfloat texfac_x = 1.0f, texfac_y = 1.0f;
|
GLfloat texfac_x = 1.0F, texfac_y = 1.0F;
|
||||||
if (tex_tgt == GL_TEXTURE_2D) {
|
if (tex_tgt == GL_TEXTURE_2D) {
|
||||||
texfac_x /= (GLfloat)mwidth;
|
texfac_x /= (GLfloat)mwidth;
|
||||||
texfac_y /= (GLfloat)mheight;
|
texfac_y /= (GLfloat)mheight;
|
||||||
@@ -1019,10 +1037,12 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z,
|
|||||||
} else {
|
} else {
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
glDrawBuffer(GL_BACK);
|
glDrawBuffer(GL_BACK);
|
||||||
if (have_scissors)
|
if (have_scissors) {
|
||||||
glEnable(GL_SCISSOR_TEST);
|
glEnable(GL_SCISSOR_TEST);
|
||||||
if (have_stencil)
|
}
|
||||||
|
if (have_stencil) {
|
||||||
glEnable(GL_STENCIL_TEST);
|
glEnable(GL_STENCIL_TEST);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Color negation for testing...
|
// Color negation for testing...
|
||||||
@@ -1032,12 +1052,15 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z,
|
|||||||
|
|
||||||
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
||||||
glUseProgram(ppass->prog);
|
glUseProgram(ppass->prog);
|
||||||
if (ppass->unifm_offset_x >= 0)
|
if (ppass->unifm_offset_x >= 0) {
|
||||||
glUniform1f(ppass->unifm_offset_x, texfac_x);
|
glUniform1f(ppass->unifm_offset_x, texfac_x);
|
||||||
if (ppass->unifm_offset_y >= 0)
|
}
|
||||||
|
if (ppass->unifm_offset_y >= 0) {
|
||||||
glUniform1f(ppass->unifm_offset_y, texfac_y);
|
glUniform1f(ppass->unifm_offset_y, texfac_y);
|
||||||
if (ppass->unifm_factor_center >= 0)
|
}
|
||||||
|
if (ppass->unifm_factor_center >= 0) {
|
||||||
glUniform1f(ppass->unifm_factor_center, factor_center);
|
glUniform1f(ppass->unifm_factor_center, factor_center);
|
||||||
|
}
|
||||||
|
|
||||||
P_PAINTREG_START(crect) {
|
P_PAINTREG_START(crect) {
|
||||||
auto rx = (GLfloat)(crect.x1 - mdx) * texfac_x;
|
auto rx = (GLfloat)(crect.x1 - mdx) * texfac_x;
|
||||||
@@ -1087,10 +1110,12 @@ glx_blur_dst_end:
|
|||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
glBindTexture(tex_tgt, 0);
|
glBindTexture(tex_tgt, 0);
|
||||||
glDisable(tex_tgt);
|
glDisable(tex_tgt);
|
||||||
if (have_scissors)
|
if (have_scissors) {
|
||||||
glEnable(GL_SCISSOR_TEST);
|
glEnable(GL_SCISSOR_TEST);
|
||||||
if (have_stencil)
|
}
|
||||||
|
if (have_stencil) {
|
||||||
glEnable(GL_STENCIL_TEST);
|
glEnable(GL_STENCIL_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
if (&ibc == pbc) {
|
if (&ibc == pbc) {
|
||||||
free_glx_bc(ps, pbc);
|
free_glx_bc(ps, pbc);
|
||||||
@@ -1104,7 +1129,7 @@ glx_blur_dst_end:
|
|||||||
// TODO(bhagwan) this is a mess and needs a more consistent way of getting the border
|
// TODO(bhagwan) this is a mess and needs a more consistent way of getting the border
|
||||||
// pixel I tried looking for a notify event for XCB_CW_BORDER_PIXEL (in
|
// pixel I tried looking for a notify event for XCB_CW_BORDER_PIXEL (in
|
||||||
// xcb_create_window()) or a way to get the pixels from xcb_render_picture_t but the
|
// xcb_create_window()) or a way to get the pixels from xcb_render_picture_t but the
|
||||||
// documentation for the xcb_xrender extension is literaly non existent...
|
// documentation for the xcb_xrender extension is literally non existent...
|
||||||
//
|
//
|
||||||
// NOTE(yshui) There is no consistent way to get the "border" color of a X window. From
|
// NOTE(yshui) There is no consistent way to get the "border" color of a X window. From
|
||||||
// the WM's perspective there are multiple ways to implement window borders. Using
|
// the WM's perspective there are multiple ways to implement window borders. Using
|
||||||
@@ -1275,7 +1300,7 @@ bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z,
|
|||||||
// considering all those mess in color negation and modulation
|
// considering all those mess in color negation and modulation
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
glColor4f(0.0f, 0.0f, 0.0f, factor);
|
glColor4f(0.0F, 0.0F, 0.0F, factor);
|
||||||
|
|
||||||
P_PAINTREG_START(crect) {
|
P_PAINTREG_START(crect) {
|
||||||
// XXX what does all of these variables mean?
|
// XXX what does all of these variables mean?
|
||||||
@@ -1291,7 +1316,7 @@ bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z,
|
|||||||
}
|
}
|
||||||
P_PAINTREG_END();
|
P_PAINTREG_END();
|
||||||
|
|
||||||
glColor4f(0.0f, 0.0f, 0.0f, 0.0f);
|
glColor4f(0.0F, 0.0F, 0.0F, 0.0F);
|
||||||
glDisable(GL_BLEND);
|
glDisable(GL_BLEND);
|
||||||
|
|
||||||
gl_check_err();
|
gl_check_err();
|
||||||
@@ -1411,15 +1436,19 @@ bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx,
|
|||||||
glUseProgram(pprogram->prog);
|
glUseProgram(pprogram->prog);
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
if (pprogram->unifm_opacity >= 0)
|
if (pprogram->unifm_opacity >= 0) {
|
||||||
glUniform1f(pprogram->unifm_opacity, (float)opacity);
|
glUniform1f(pprogram->unifm_opacity, (float)opacity);
|
||||||
if (pprogram->unifm_invert_color >= 0)
|
}
|
||||||
|
if (pprogram->unifm_invert_color >= 0) {
|
||||||
glUniform1i(pprogram->unifm_invert_color, neg);
|
glUniform1i(pprogram->unifm_invert_color, neg);
|
||||||
if (pprogram->unifm_tex >= 0)
|
}
|
||||||
|
if (pprogram->unifm_tex >= 0) {
|
||||||
glUniform1i(pprogram->unifm_tex, 0);
|
glUniform1i(pprogram->unifm_tex, 0);
|
||||||
if (pprogram->unifm_time >= 0)
|
}
|
||||||
glUniform1f(pprogram->unifm_time, (float)ts.tv_sec * 1000.0f +
|
if (pprogram->unifm_time >= 0) {
|
||||||
(float)ts.tv_nsec / 1.0e6f);
|
glUniform1f(pprogram->unifm_time, (float)ts.tv_sec * 1000.0F +
|
||||||
|
(float)ts.tv_nsec / 1.0e6F);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d", x, y, width, height,
|
// log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d", x, y, width, height,
|
||||||
@@ -1459,8 +1488,8 @@ bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx,
|
|||||||
// Invert Y if needed, this may not work as expected, though. I
|
// Invert Y if needed, this may not work as expected, though. I
|
||||||
// don't have such a FBConfig to test with.
|
// don't have such a FBConfig to test with.
|
||||||
if (!ptex->y_inverted) {
|
if (!ptex->y_inverted) {
|
||||||
ry = 1.0f - ry;
|
ry = 1.0F - ry;
|
||||||
rye = 1.0f - rye;
|
rye = 1.0F - rye;
|
||||||
}
|
}
|
||||||
|
|
||||||
// log_trace("Rect %d: %f, %f, %f, %f -> %d, %d, %d, %d", ri, rx,
|
// log_trace("Rect %d: %f, %f, %f, %f -> %d, %d, %d, %d", ri, rx,
|
||||||
@@ -1492,7 +1521,7 @@ bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx,
|
|||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
glBindTexture(ptex->target, 0);
|
glBindTexture(ptex->target, 0);
|
||||||
glColor4f(0.0f, 0.0f, 0.0f, 0.0f);
|
glColor4f(0.0F, 0.0F, 0.0F, 0.0F);
|
||||||
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
||||||
glDisable(GL_BLEND);
|
glDisable(GL_BLEND);
|
||||||
glDisable(GL_COLOR_LOGIC_OP);
|
glDisable(GL_COLOR_LOGIC_OP);
|
||||||
@@ -1505,8 +1534,9 @@ bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx,
|
|||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (has_prog)
|
if (has_prog) {
|
||||||
glUseProgram(0);
|
glUseProgram(0);
|
||||||
|
}
|
||||||
|
|
||||||
gl_check_err();
|
gl_check_err();
|
||||||
|
|
||||||
|
|||||||
18
src/opengl.h
18
src/opengl.h
@@ -18,9 +18,9 @@
|
|||||||
#include "render.h"
|
#include "render.h"
|
||||||
#include "win.h"
|
#include "win.h"
|
||||||
|
|
||||||
#include <GL/gl.h>
|
|
||||||
#include <GL/glx.h>
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <epoxy/gl.h>
|
||||||
|
#include <epoxy/glx.h>
|
||||||
#include <locale.h>
|
#include <locale.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -75,7 +75,7 @@ typedef struct glx_session {
|
|||||||
glx_round_pass_t *round_passes;
|
glx_round_pass_t *round_passes;
|
||||||
} glx_session_t;
|
} glx_session_t;
|
||||||
|
|
||||||
/// @brief Wrapper of a binded GLX texture.
|
/// @brief Wrapper of a bound GLX texture.
|
||||||
typedef struct _glx_texture {
|
typedef struct _glx_texture {
|
||||||
GLuint texture;
|
GLuint texture;
|
||||||
GLXPixmap glpixmap;
|
GLXPixmap glpixmap;
|
||||||
@@ -121,9 +121,9 @@ bool glx_bind_texture(session_t *ps, glx_texture_t **pptex, int x, int y, int wi
|
|||||||
void glx_paint_pre(session_t *ps, region_t *preg) attr_nonnull(1, 2);
|
void glx_paint_pre(session_t *ps, region_t *preg) attr_nonnull(1, 2);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a texture is binded, or is binded to the given pixmap.
|
* Check if a texture is bound, or is bound to the given pixmap.
|
||||||
*/
|
*/
|
||||||
static inline bool glx_tex_binded(const glx_texture_t *ptex, xcb_pixmap_t pixmap) {
|
static inline bool glx_tex_bound(const glx_texture_t *ptex, xcb_pixmap_t pixmap) {
|
||||||
return ptex && ptex->glpixmap && ptex->texture && (!pixmap || pixmap == ptex->pixmap);
|
return ptex && ptex->glpixmap && ptex->texture && (!pixmap || pixmap == ptex->pixmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,8 +156,9 @@ static inline bool glx_has_context(session_t *ps) {
|
|||||||
*/
|
*/
|
||||||
static inline bool ensure_glx_context(session_t *ps) {
|
static inline bool ensure_glx_context(session_t *ps) {
|
||||||
// Create GLX context
|
// Create GLX context
|
||||||
if (!glx_has_context(ps))
|
if (!glx_has_context(ps)) {
|
||||||
glx_init(ps, false);
|
glx_init(ps, false);
|
||||||
|
}
|
||||||
|
|
||||||
return glx_has_context(ps);
|
return glx_has_context(ps);
|
||||||
}
|
}
|
||||||
@@ -227,10 +228,7 @@ static inline void free_texture(session_t *ps, glx_texture_t **pptex) {
|
|||||||
*/
|
*/
|
||||||
static inline void free_paint_glx(session_t *ps, paint_t *ppaint) {
|
static inline void free_paint_glx(session_t *ps, paint_t *ppaint) {
|
||||||
free_texture(ps, &ppaint->ptex);
|
free_texture(ps, &ppaint->ptex);
|
||||||
#ifdef CONFIG_OPENGL
|
ppaint->fbcfg = (struct glx_fbconfig_info){0};
|
||||||
free(ppaint->fbcfg);
|
|
||||||
#endif
|
|
||||||
ppaint->fbcfg = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ static const struct picom_option picom_options[] = {
|
|||||||
"managers not passing _NET_WM_WINDOW_OPACITY of client windows to frame"},
|
"managers not passing _NET_WM_WINDOW_OPACITY of client windows to frame"},
|
||||||
{"refresh-rate" , required_argument, 269, NULL , NULL},
|
{"refresh-rate" , required_argument, 269, NULL , NULL},
|
||||||
{"vsync" , optional_argument, 270, NULL , "Enable VSync"},
|
{"vsync" , optional_argument, 270, NULL , "Enable VSync"},
|
||||||
|
{"crop-shadow-to-monitor" , no_argument , 271, NULL , "Crop shadow of a window fully on a particular monitor to that monitor. "
|
||||||
|
"This is currently implemented using the X RandR extension"},
|
||||||
|
{"xinerama-shadow-crop" , no_argument , 272, NULL , NULL},
|
||||||
{"sw-opti" , no_argument , 274, NULL , NULL},
|
{"sw-opti" , no_argument , 274, NULL , NULL},
|
||||||
{"vsync-aggressive" , no_argument , 275, NULL , NULL},
|
{"vsync-aggressive" , no_argument , 275, NULL , NULL},
|
||||||
{"use-ewmh-active-win" , no_argument , 276, NULL , "Use _NET_WM_ACTIVE_WINDOW on the root window to determine which window is "
|
{"use-ewmh-active-win" , no_argument , 276, NULL , "Use _NET_WM_ACTIVE_WINDOW on the root window to determine which window is "
|
||||||
@@ -119,7 +122,6 @@ static const struct picom_option picom_options[] = {
|
|||||||
{"opacity-rule" , required_argument, 304, "OPACITY:COND", "Specify a list of opacity rules, see man page for more details"},
|
{"opacity-rule" , required_argument, 304, "OPACITY:COND", "Specify a list of opacity rules, see man page for more details"},
|
||||||
{"shadow-exclude-reg" , required_argument, 305, NULL , NULL},
|
{"shadow-exclude-reg" , required_argument, 305, NULL , NULL},
|
||||||
{"paint-exclude" , required_argument, 306, NULL , NULL},
|
{"paint-exclude" , required_argument, 306, NULL , NULL},
|
||||||
{"xinerama-shadow-crop" , no_argument , 307, NULL , "Crop shadow of a window fully on a particular Xinerama screen to the screen."},
|
|
||||||
{"unredir-if-possible-exclude" , required_argument, 308, "COND" , "Conditions of windows that shouldn't be considered full-screen for "
|
{"unredir-if-possible-exclude" , required_argument, 308, "COND" , "Conditions of windows that shouldn't be considered full-screen for "
|
||||||
"unredirecting screen."},
|
"unredirecting screen."},
|
||||||
{"unredir-if-possible-delay" , required_argument, 309, NULL, "Delay before unredirecting the window, in milliseconds. Defaults to 0."},
|
{"unredir-if-possible-delay" , required_argument, 309, NULL, "Delay before unredirecting the window, in milliseconds. Defaults to 0."},
|
||||||
@@ -159,6 +161,7 @@ static const struct picom_option picom_options[] = {
|
|||||||
{"corner-radius" , required_argument, 333, NULL , "Sets the radius of rounded window corners. When > 0, the compositor will "
|
{"corner-radius" , required_argument, 333, NULL , "Sets the radius of rounded window corners. When > 0, the compositor will "
|
||||||
"round the corners of windows. (defaults to 0)."},
|
"round the corners of windows. (defaults to 0)."},
|
||||||
{"rounded-corners-exclude" , required_argument, 334, "COND" , "Exclude conditions for rounded corners."},
|
{"rounded-corners-exclude" , required_argument, 334, "COND" , "Exclude conditions for rounded corners."},
|
||||||
|
{"corner-radius-rules" , required_argument, 340, "RADIUS:COND" , "Window rules for specific rounded corner radii."},
|
||||||
{"clip-shadow-above" , required_argument, 335, NULL , "Specify a list of conditions of windows to not paint a shadow over, such "
|
{"clip-shadow-above" , required_argument, 335, NULL , "Specify a list of conditions of windows to not paint a shadow over, such "
|
||||||
"as a dock window."},
|
"as a dock window."},
|
||||||
{"window-shader-fg" , required_argument, 336, "PATH" , "Specify GLSL fragment shader path for rendering window contents. Does not"
|
{"window-shader-fg" , required_argument, 336, "PATH" , "Specify GLSL fragment shader path for rendering window contents. Does not"
|
||||||
@@ -172,6 +175,8 @@ static const struct picom_option picom_options[] = {
|
|||||||
{"dithered-present" , no_argument , 339, NULL , "Use higher precision during rendering, and apply dither when presenting the "
|
{"dithered-present" , no_argument , 339, NULL , "Use higher precision during rendering, and apply dither when presenting the "
|
||||||
"rendered screen. Reduces banding artifacts, but might cause performance "
|
"rendered screen. Reduces banding artifacts, but might cause performance "
|
||||||
"degradation. Only works with OpenGL."},
|
"degradation. Only works with OpenGL."},
|
||||||
|
// 340 is corner-radius-rules
|
||||||
|
{"no-frame-pacing" , no_argument , 341, NULL , "Disable frame pacing. This might increase the latency."},
|
||||||
{"legacy-backends" , no_argument , 733, NULL , "Use deprecated version of the backends."},
|
{"legacy-backends" , no_argument , 733, NULL , "Use deprecated version of the backends."},
|
||||||
{"monitor-repaint" , no_argument , 800, NULL , "Highlight the updated area of the screen. For debugging."},
|
{"monitor-repaint" , no_argument , 800, NULL , "Highlight the updated area of the screen. For debugging."},
|
||||||
{"diagnostics" , no_argument , 801, NULL , "Print diagnostic information"},
|
{"diagnostics" , no_argument , 801, NULL , "Print diagnostic information"},
|
||||||
@@ -310,7 +315,7 @@ static void usage(const char *argv0, int ret) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *shortopts = "D:I:O:r:o:m:l:t:i:e:hscnfFCazGb";
|
static const char *shortopts = "D:I:O:r:o:m:l:t:i:e:hscnfCazGb";
|
||||||
|
|
||||||
/// Get config options that are needed to parse the rest of the options
|
/// Get config options that are needed to parse the rest of the options
|
||||||
/// Return true if we should quit
|
/// Return true if we should quit
|
||||||
@@ -320,9 +325,9 @@ bool get_early_config(int argc, char *const *argv, char **config_file, bool *all
|
|||||||
|
|
||||||
int o = 0, longopt_idx = -1;
|
int o = 0, longopt_idx = -1;
|
||||||
|
|
||||||
// Pre-parse the commandline arguments to check for --config and invalid
|
// Pre-parse the command line arguments to check for --config and invalid
|
||||||
// switches
|
// switches
|
||||||
// Must reset optind to 0 here in case we reread the commandline
|
// Must reset optind to 0 here in case we reread the command line
|
||||||
// arguments
|
// arguments
|
||||||
optind = 1;
|
optind = 1;
|
||||||
*config_file = NULL;
|
*config_file = NULL;
|
||||||
@@ -374,7 +379,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
|
|||||||
// instead of commas in atof().
|
// instead of commas in atof().
|
||||||
setlocale(LC_NUMERIC, "C");
|
setlocale(LC_NUMERIC, "C");
|
||||||
|
|
||||||
// Parse commandline arguments. Range checking will be done later.
|
// Parse command line arguments. Range checking will be done later.
|
||||||
|
|
||||||
bool failed = false;
|
bool failed = false;
|
||||||
const char *deprecation_message attr_unused =
|
const char *deprecation_message attr_unused =
|
||||||
@@ -430,7 +435,6 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
|
|||||||
opt->wintype_option[WINTYPE_DROPDOWN_MENU].opacity = tmp;
|
opt->wintype_option[WINTYPE_DROPDOWN_MENU].opacity = tmp;
|
||||||
break;
|
break;
|
||||||
case 'f':
|
case 'f':
|
||||||
case 'F':
|
|
||||||
fading_enable = true;
|
fading_enable = true;
|
||||||
break;
|
break;
|
||||||
P_CASEINT('r', shadow_radius);
|
P_CASEINT('r', shadow_radius);
|
||||||
@@ -494,8 +498,8 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
|
|||||||
P_CASEBOOL(267, detect_rounded_corners);
|
P_CASEBOOL(267, detect_rounded_corners);
|
||||||
P_CASEBOOL(268, detect_client_opacity);
|
P_CASEBOOL(268, detect_client_opacity);
|
||||||
case 269:
|
case 269:
|
||||||
log_warn("--refresh-rate has been deprecated, please remove it from"
|
log_warn("--refresh-rate has been deprecated, please remove it "
|
||||||
"your command line options");
|
"from your command line options");
|
||||||
break;
|
break;
|
||||||
case 270:
|
case 270:
|
||||||
if (optarg) {
|
if (optarg) {
|
||||||
@@ -508,22 +512,30 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
|
|||||||
opt->vsync = true;
|
opt->vsync = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
P_CASEBOOL(271, crop_shadow_to_monitor);
|
||||||
|
case 272:
|
||||||
|
opt->crop_shadow_to_monitor = true;
|
||||||
|
log_warn("--xinerama-shadow-crop is deprecated. Use "
|
||||||
|
"--crop-shadow-to-monitor instead.");
|
||||||
|
break;
|
||||||
case 274:
|
case 274:
|
||||||
log_warn("--sw-opti has been deprecated, please remove it from the "
|
log_error("--sw-opti has been deprecated, please remove it from the "
|
||||||
"command line options");
|
"command line options");
|
||||||
|
failed = true;
|
||||||
break;
|
break;
|
||||||
case 275:
|
case 275:
|
||||||
// --vsync-aggressive
|
// --vsync-aggressive
|
||||||
log_error("--vsync-aggressive has been removed, please remove it"
|
log_error("--vsync-aggressive has been removed, please remove it"
|
||||||
" from the command line options");
|
" from the command line options");
|
||||||
failed = true;
|
failed = true;
|
||||||
break;
|
break;
|
||||||
P_CASEBOOL(276, use_ewmh_active_win);
|
P_CASEBOOL(276, use_ewmh_active_win);
|
||||||
case 277:
|
case 277:
|
||||||
// --respect-prop-shadow
|
// --respect-prop-shadow
|
||||||
log_warn("--respect-prop-shadow option has been deprecated, its "
|
log_error("--respect-prop-shadow option has been deprecated, its "
|
||||||
"functionality will always be enabled. Please remove it "
|
"functionality will always be enabled. Please remove it "
|
||||||
"from the command line options");
|
"from the command line options");
|
||||||
|
failed = true;
|
||||||
break;
|
break;
|
||||||
P_CASEBOOL(278, unredir_if_possible);
|
P_CASEBOOL(278, unredir_if_possible);
|
||||||
case 279:
|
case 279:
|
||||||
@@ -559,8 +571,9 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
|
|||||||
case 290:
|
case 290:
|
||||||
// --backend
|
// --backend
|
||||||
opt->backend = parse_backend(optarg);
|
opt->backend = parse_backend(optarg);
|
||||||
if (opt->backend >= NUM_BKEND)
|
if (opt->backend >= NUM_BKEND) {
|
||||||
exit(1);
|
exit(1);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
P_CASEBOOL(291, glx_no_stencil);
|
P_CASEBOOL(291, glx_no_stencil);
|
||||||
P_CASEINT(293, benchmark);
|
P_CASEINT(293, benchmark);
|
||||||
@@ -620,8 +633,9 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
|
|||||||
break;
|
break;
|
||||||
case 304:
|
case 304:
|
||||||
// --opacity-rule
|
// --opacity-rule
|
||||||
if (!parse_rule_opacity(&opt->opacity_rules, optarg))
|
if (!parse_numeric_window_rule(&opt->opacity_rules, optarg, 0, 100)) {
|
||||||
exit(1);
|
exit(1);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 305:
|
case 305:
|
||||||
// --shadow-exclude-reg
|
// --shadow-exclude-reg
|
||||||
@@ -634,7 +648,6 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
|
|||||||
// --paint-exclude
|
// --paint-exclude
|
||||||
condlst_add(&opt->paint_blacklist, optarg);
|
condlst_add(&opt->paint_blacklist, optarg);
|
||||||
break;
|
break;
|
||||||
P_CASEBOOL(307, xinerama_shadow_crop);
|
|
||||||
case 308:
|
case 308:
|
||||||
// --unredir-if-possible-exclude
|
// --unredir-if-possible-exclude
|
||||||
condlst_add(&opt->unredir_if_possible_blacklist, optarg);
|
condlst_add(&opt->unredir_if_possible_blacklist, optarg);
|
||||||
@@ -718,13 +731,19 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
|
|||||||
opt->blur_strength = atoi(optarg);
|
opt->blur_strength = atoi(optarg);
|
||||||
break;
|
break;
|
||||||
case 333:
|
case 333:
|
||||||
// --cornor-radius
|
// --corner-radius
|
||||||
opt->corner_radius = atoi(optarg);
|
opt->corner_radius = atoi(optarg);
|
||||||
break;
|
break;
|
||||||
case 334:
|
case 334:
|
||||||
// --rounded-corners-exclude
|
// --rounded-corners-exclude
|
||||||
condlst_add(&opt->rounded_corners_blacklist, optarg);
|
condlst_add(&opt->rounded_corners_blacklist, optarg);
|
||||||
break;
|
break;
|
||||||
|
case 340:
|
||||||
|
// --corner-radius-rules
|
||||||
|
if (!parse_numeric_window_rule(&opt->corner_radius_rules, optarg, 0, INT_MAX)) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 335:
|
case 335:
|
||||||
// --clip-shadow-above
|
// --clip-shadow-above
|
||||||
condlst_add(&opt->shadow_clip_list, optarg);
|
condlst_add(&opt->shadow_clip_list, optarg);
|
||||||
@@ -733,6 +752,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
|
|||||||
// --dithered-present
|
// --dithered-present
|
||||||
opt->dithered_present = true;
|
opt->dithered_present = true;
|
||||||
break;
|
break;
|
||||||
|
P_CASEBOOL(341, no_frame_pacing);
|
||||||
P_CASEBOOL(733, legacy_backends);
|
P_CASEBOOL(733, legacy_backends);
|
||||||
P_CASEBOOL(800, monitor_repaint);
|
P_CASEBOOL(800, monitor_repaint);
|
||||||
case 801:
|
case 801:
|
||||||
@@ -842,14 +862,13 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (opt->window_shader_fg || opt->window_shader_fg_rules) {
|
if (opt->window_shader_fg || opt->window_shader_fg_rules) {
|
||||||
if (opt->legacy_backends || opt->backend != BKEND_GLX) {
|
if (opt->backend == BKEND_XRENDER || opt->legacy_backends) {
|
||||||
log_warn("The new window shader interface does not work with the "
|
log_warn(opt->backend == BKEND_XRENDER
|
||||||
"legacy glx backend.%s",
|
? "Shader interface is not supported by the xrender "
|
||||||
(opt->backend == BKEND_GLX) ? " You may want to use "
|
"backend."
|
||||||
"\"--glx-fshader-win\" "
|
: "The new shader interface is not supported by the "
|
||||||
"instead on the legacy "
|
"legacy glx backend. You may want to use "
|
||||||
"glx backend."
|
"--glx-fshader-win instead.");
|
||||||
: "");
|
|
||||||
opt->window_shader_fg = NULL;
|
opt->window_shader_fg = NULL;
|
||||||
c2_list_free(&opt->window_shader_fg_rules, free);
|
c2_list_free(&opt->window_shader_fg_rules, free);
|
||||||
}
|
}
|
||||||
@@ -864,18 +883,16 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
|
|||||||
opt->inactive_dim = normalize_d(opt->inactive_dim);
|
opt->inactive_dim = normalize_d(opt->inactive_dim);
|
||||||
opt->frame_opacity = normalize_d(opt->frame_opacity);
|
opt->frame_opacity = normalize_d(opt->frame_opacity);
|
||||||
opt->shadow_opacity = normalize_d(opt->shadow_opacity);
|
opt->shadow_opacity = normalize_d(opt->shadow_opacity);
|
||||||
|
|
||||||
opt->max_brightness = normalize_d(opt->max_brightness);
|
opt->max_brightness = normalize_d(opt->max_brightness);
|
||||||
if (opt->max_brightness < 1.0) {
|
if (opt->max_brightness < 1.0) {
|
||||||
if (opt->use_damage) {
|
if (opt->backend == BKEND_XRENDER || opt->legacy_backends) {
|
||||||
log_warn("--max-brightness requires --no-use-damage. Falling "
|
log_warn("--max-brightness is not supported by the %s backend. "
|
||||||
"back to 1.0");
|
"Falling back to 1.0.",
|
||||||
|
opt->backend == BKEND_XRENDER ? "xrender" : "legacy glx");
|
||||||
opt->max_brightness = 1.0;
|
opt->max_brightness = 1.0;
|
||||||
}
|
} else if (opt->use_damage) {
|
||||||
|
log_warn("--max-brightness requires --no-use-damage. Falling "
|
||||||
if (opt->legacy_backends || opt->backend != BKEND_GLX) {
|
"back to 1.0.");
|
||||||
log_warn("--max-brightness requires the new glx "
|
|
||||||
"backend. Falling back to 1.0");
|
|
||||||
opt->max_brightness = 1.0;
|
opt->max_brightness = 1.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1247
src/picom.c
1247
src/picom.c
File diff suppressed because it is too large
Load Diff
10
src/picom.h
10
src/picom.h
@@ -42,8 +42,6 @@ void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce);
|
|||||||
|
|
||||||
void root_damaged(session_t *ps);
|
void root_damaged(session_t *ps);
|
||||||
|
|
||||||
void cxinerama_upd_scrs(session_t *ps);
|
|
||||||
|
|
||||||
void queue_redraw(session_t *ps);
|
void queue_redraw(session_t *ps);
|
||||||
|
|
||||||
void discard_pending(session_t *ps, uint32_t sequence);
|
void discard_pending(session_t *ps, uint32_t sequence);
|
||||||
@@ -62,9 +60,11 @@ uint8_t session_redirection_mode(session_t *ps);
|
|||||||
static inline void wintype_arr_enable_unset(switch_t arr[]) {
|
static inline void wintype_arr_enable_unset(switch_t arr[]) {
|
||||||
wintype_t i;
|
wintype_t i;
|
||||||
|
|
||||||
for (i = 0; i < NUM_WINTYPES; ++i)
|
for (i = 0; i < NUM_WINTYPES; ++i) {
|
||||||
if (UNSET == arr[i])
|
if (UNSET == arr[i]) {
|
||||||
arr[i] = ON;
|
arr[i] = ON;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,7 +96,7 @@ free_win_res_glx(session_t *ps attr_unused, struct managed_win *w attr_unused) {
|
|||||||
* Dump an drawable's info.
|
* Dump an drawable's info.
|
||||||
*/
|
*/
|
||||||
static inline void dump_drawable(session_t *ps, xcb_drawable_t drawable) {
|
static inline void dump_drawable(session_t *ps, xcb_drawable_t drawable) {
|
||||||
auto r = xcb_get_geometry_reply(ps->c, xcb_get_geometry(ps->c, drawable), NULL);
|
auto r = xcb_get_geometry_reply(ps->c.c, xcb_get_geometry(ps->c.c, drawable), NULL);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
log_trace("Drawable %#010x: Failed", drawable);
|
log_trace("Drawable %#010x: Failed", drawable);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ static inline void dump_region(const region_t *x) {
|
|||||||
int nrects;
|
int nrects;
|
||||||
const rect_t *rects = pixman_region32_rectangles((region_t *)x, &nrects);
|
const rect_t *rects = pixman_region32_rectangles((region_t *)x, &nrects);
|
||||||
log_trace("nrects: %d", nrects);
|
log_trace("nrects: %d", nrects);
|
||||||
for (int i = 0; i < nrects; i++)
|
for (int i = 0; i < nrects; i++) {
|
||||||
log_trace("(%d, %d) - (%d, %d)", rects[i].x1, rects[i].y1, rects[i].x2,
|
log_trace("(%d, %d) - (%d, %d)", rects[i].x1, rects[i].y1, rects[i].x2,
|
||||||
rects[i].y2);
|
rects[i].y2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert one xcb rectangle to our rectangle type
|
/// Convert one xcb rectangle to our rectangle type
|
||||||
@@ -51,8 +52,7 @@ static inline rect_t *from_x_rects(int nrects, const xcb_rectangle_t *rects) {
|
|||||||
/**
|
/**
|
||||||
* Resize a region.
|
* Resize a region.
|
||||||
*/
|
*/
|
||||||
static inline void _resize_region(const region_t *region, region_t *output, int dx,
|
static inline void _resize_region(const region_t *region, region_t *output, int dx, int dy) {
|
||||||
int dy) {
|
|
||||||
if (!region || !output) {
|
if (!region || !output) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -77,8 +77,7 @@ static inline void _resize_region(const region_t *region, region_t *output, int
|
|||||||
if (wid <= 0 || hei <= 0) {
|
if (wid <= 0 || hei <= 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
newrects[nnewrects] =
|
newrects[nnewrects] = (rect_t){.x1 = x1, .x2 = x2, .y1 = y1, .y2 = y2};
|
||||||
(rect_t){.x1 = x1, .x2 = x2, .y1 = y1, .y2 = y2};
|
|
||||||
++nnewrects;
|
++nnewrects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
465
src/render.c
465
src/render.c
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@ typedef struct paint {
|
|||||||
xcb_render_picture_t pict;
|
xcb_render_picture_t pict;
|
||||||
glx_texture_t *ptex;
|
glx_texture_t *ptex;
|
||||||
#ifdef CONFIG_OPENGL
|
#ifdef CONFIG_OPENGL
|
||||||
struct glx_fbconfig_info *fbcfg;
|
struct glx_fbconfig_info fbcfg;
|
||||||
#endif
|
#endif
|
||||||
} paint_t;
|
} paint_t;
|
||||||
|
|
||||||
@@ -37,9 +37,7 @@ void render(session_t *ps, int x, int y, int dx, int dy, int w, int h, int fullw
|
|||||||
const glx_prog_main_t *pprogram, clip_t *clip);
|
const glx_prog_main_t *pprogram, clip_t *clip);
|
||||||
void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint);
|
void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint);
|
||||||
|
|
||||||
void paint_all(session_t *ps, struct managed_win *const t, bool ignore_damage);
|
void paint_all(session_t *ps, struct managed_win *const t);
|
||||||
|
|
||||||
void free_picture(xcb_connection_t *c, xcb_render_picture_t *p);
|
|
||||||
|
|
||||||
void free_paint(session_t *ps, paint_t *ppaint);
|
void free_paint(session_t *ps, paint_t *ppaint);
|
||||||
void free_root_tile(session_t *ps);
|
void free_root_tile(session_t *ps);
|
||||||
|
|||||||
85
src/statistics.c
Normal file
85
src/statistics.c
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
//! Rendering statistics
|
||||||
|
//!
|
||||||
|
//! Tracks how long it takes to render a frame, for measuring performance, and for pacing
|
||||||
|
//! the frames.
|
||||||
|
|
||||||
|
#include "statistics.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
void render_statistics_init(struct render_statistics *rs, int window_size) {
|
||||||
|
*rs = (struct render_statistics){0};
|
||||||
|
|
||||||
|
rolling_window_init(&rs->render_times, window_size);
|
||||||
|
rolling_quantile_init_with_tolerance(&rs->render_time_quantile, window_size,
|
||||||
|
/* q */ 0.98, /* tolerance */ 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_statistics_add_vblank_time_sample(struct render_statistics *rs, int time_us) {
|
||||||
|
auto sample_sd = sqrt(cumulative_mean_and_var_get_var(&rs->vblank_time_us));
|
||||||
|
auto current_estimate = render_statistics_get_vblank_time(rs);
|
||||||
|
if (current_estimate != 0 && fabs((double)time_us - current_estimate) > sample_sd * 3) {
|
||||||
|
// Deviated from the mean by more than 3 sigma (p < 0.003)
|
||||||
|
log_debug("vblank time outlier: %d %f %f", time_us, rs->vblank_time_us.mean,
|
||||||
|
cumulative_mean_and_var_get_var(&rs->vblank_time_us));
|
||||||
|
// An outlier sample, this could mean things like refresh rate changes, so
|
||||||
|
// we reset the statistics. This could also be benign, but we like to be
|
||||||
|
// cautious.
|
||||||
|
cumulative_mean_and_var_init(&rs->vblank_time_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rs->vblank_time_us.mean != 0) {
|
||||||
|
auto nframes_in_10_seconds =
|
||||||
|
(unsigned int)(10. * 1000000. / rs->vblank_time_us.mean);
|
||||||
|
if (rs->vblank_time_us.n > 20 && rs->vblank_time_us.n > nframes_in_10_seconds) {
|
||||||
|
// We collected 10 seconds worth of samples, we assume the
|
||||||
|
// estimated refresh rate is stable. We will still reset the
|
||||||
|
// statistics if we get an outlier sample though, see above.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cumulative_mean_and_var_update(&rs->vblank_time_us, time_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_statistics_add_render_time_sample(struct render_statistics *rs, int time_us) {
|
||||||
|
int oldest;
|
||||||
|
if (rolling_window_push_back(&rs->render_times, time_us, &oldest)) {
|
||||||
|
rolling_quantile_pop_front(&rs->render_time_quantile, oldest);
|
||||||
|
}
|
||||||
|
|
||||||
|
rolling_quantile_push_back(&rs->render_time_quantile, time_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How much time budget we should give to the backend for rendering, in microseconds.
|
||||||
|
unsigned int render_statistics_get_budget(struct render_statistics *rs) {
|
||||||
|
if (rs->render_times.nelem < rs->render_times.window_size) {
|
||||||
|
// No valid render time estimates yet. Assume maximum budget.
|
||||||
|
return UINT_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
// N-th percentile of render times, see render_statistics_init for N.
|
||||||
|
auto render_time_percentile =
|
||||||
|
rolling_quantile_estimate(&rs->render_time_quantile, &rs->render_times);
|
||||||
|
return (unsigned int)render_time_percentile;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int render_statistics_get_vblank_time(struct render_statistics *rs) {
|
||||||
|
if (rs->vblank_time_us.n <= 20 || rs->vblank_time_us.mean < 100) {
|
||||||
|
// Not enough samples yet, or the vblank time is too short to be
|
||||||
|
// meaningful. Assume maximum budget.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (unsigned int)rs->vblank_time_us.mean;
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_statistics_reset(struct render_statistics *rs) {
|
||||||
|
rolling_window_reset(&rs->render_times);
|
||||||
|
rolling_quantile_reset(&rs->render_time_quantile);
|
||||||
|
rs->vblank_time_us = (struct cumulative_mean_and_var){0};
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_statistics_destroy(struct render_statistics *rs) {
|
||||||
|
render_statistics_reset(rs);
|
||||||
|
rolling_window_destroy(&rs->render_times);
|
||||||
|
rolling_quantile_destroy(&rs->render_time_quantile);
|
||||||
|
}
|
||||||
30
src/statistics.h
Normal file
30
src/statistics.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
#define NTIERS (3)
|
||||||
|
|
||||||
|
struct render_statistics {
|
||||||
|
/// Rolling window of rendering times (in us) and the tiers they belong to.
|
||||||
|
/// We keep track of the tiers because the vblank time estimate can change over
|
||||||
|
/// time.
|
||||||
|
struct rolling_window render_times;
|
||||||
|
/// Estimate the 95-th percentile of rendering times
|
||||||
|
struct rolling_quantile render_time_quantile;
|
||||||
|
/// Time between each vblanks
|
||||||
|
struct cumulative_mean_and_var vblank_time_us;
|
||||||
|
};
|
||||||
|
|
||||||
|
void render_statistics_init(struct render_statistics *rs, int window_size);
|
||||||
|
void render_statistics_reset(struct render_statistics *rs);
|
||||||
|
void render_statistics_destroy(struct render_statistics *rs);
|
||||||
|
|
||||||
|
void render_statistics_add_vblank_time_sample(struct render_statistics *rs, int time_us);
|
||||||
|
void render_statistics_add_render_time_sample(struct render_statistics *rs, int time_us);
|
||||||
|
|
||||||
|
/// How much time budget we should give to the backend for rendering, in microseconds.
|
||||||
|
unsigned int render_statistics_get_budget(struct render_statistics *rs);
|
||||||
|
|
||||||
|
/// Return the measured vblank interval in microseconds. Returns 0 if not enough
|
||||||
|
/// samples have been collected yet.
|
||||||
|
unsigned int render_statistics_get_vblank_time(struct render_statistics *rs);
|
||||||
@@ -24,8 +24,9 @@ static inline int uitostr(unsigned int n, char *buf) {
|
|||||||
ret++;
|
ret++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret == 0)
|
if (ret == 0) {
|
||||||
ret = 1;
|
ret = 1;
|
||||||
|
}
|
||||||
|
|
||||||
int pos = ret;
|
int pos = ret;
|
||||||
while (pos--) {
|
while (pos--) {
|
||||||
@@ -36,18 +37,22 @@ static inline int uitostr(unsigned int n, char *buf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline const char *skip_space_const(const char *src) {
|
static inline const char *skip_space_const(const char *src) {
|
||||||
if (!src)
|
if (!src) {
|
||||||
return NULL;
|
return NULL;
|
||||||
while (*src && isspace((unsigned char)*src))
|
}
|
||||||
|
while (*src && isspace((unsigned char)*src)) {
|
||||||
src++;
|
src++;
|
||||||
|
}
|
||||||
return src;
|
return src;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline char *skip_space_mut(char *src) {
|
static inline char *skip_space_mut(char *src) {
|
||||||
if (!src)
|
if (!src) {
|
||||||
return NULL;
|
return NULL;
|
||||||
while (*src && isspace((unsigned char)*src))
|
}
|
||||||
|
while (*src && isspace((unsigned char)*src)) {
|
||||||
src++;
|
src++;
|
||||||
|
}
|
||||||
return src;
|
return src;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
239
src/utils.c
239
src/utils.c
@@ -30,7 +30,7 @@ void report_allocation_failure(const char *func, const char *file, unsigned int
|
|||||||
ssize_t _ attr_unused = writev(STDERR_FILENO, v, ARR_SIZE(v));
|
ssize_t _ attr_unused = writev(STDERR_FILENO, v, ARR_SIZE(v));
|
||||||
abort();
|
abort();
|
||||||
|
|
||||||
unreachable;
|
unreachable();
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@@ -48,7 +48,43 @@ int next_power_of_two(int n) {
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Track the rolling maximum of a stream of integers.
|
void rolling_window_destroy(struct rolling_window *rw) {
|
||||||
|
free(rw->elem);
|
||||||
|
rw->elem = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rolling_window_reset(struct rolling_window *rw) {
|
||||||
|
rw->nelem = 0;
|
||||||
|
rw->elem_head = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rolling_window_init(struct rolling_window *rw, int size) {
|
||||||
|
rw->elem = ccalloc(size, int);
|
||||||
|
rw->window_size = size;
|
||||||
|
rolling_window_reset(rw);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rolling_window_pop_front(struct rolling_window *rw) {
|
||||||
|
assert(rw->nelem > 0);
|
||||||
|
auto ret = rw->elem[rw->elem_head];
|
||||||
|
rw->elem_head = (rw->elem_head + 1) % rw->window_size;
|
||||||
|
rw->nelem--;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rolling_window_push_back(struct rolling_window *rw, int val, int *front) {
|
||||||
|
bool full = rw->nelem == rw->window_size;
|
||||||
|
if (full) {
|
||||||
|
*front = rolling_window_pop_front(rw);
|
||||||
|
}
|
||||||
|
rw->elem[(rw->elem_head + rw->nelem) % rw->window_size] = val;
|
||||||
|
rw->nelem++;
|
||||||
|
return full;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Track the maximum member of a FIFO queue of integers. Integers are pushed to the back
|
||||||
|
/// and popped from the front, the maximum of the current members in the queue is
|
||||||
|
/// tracked.
|
||||||
struct rolling_max {
|
struct rolling_max {
|
||||||
/// A priority queue holding the indices of the maximum element candidates.
|
/// A priority queue holding the indices of the maximum element candidates.
|
||||||
/// The head of the queue is the index of the maximum element.
|
/// The head of the queue is the index of the maximum element.
|
||||||
@@ -59,32 +95,26 @@ struct rolling_max {
|
|||||||
/// it's called the "original" indices.
|
/// it's called the "original" indices.
|
||||||
int *p;
|
int *p;
|
||||||
int p_head, np;
|
int p_head, np;
|
||||||
|
/// The maximum number of in flight elements.
|
||||||
/// The elemets
|
int capacity;
|
||||||
int *elem;
|
|
||||||
int elem_head, nelem;
|
|
||||||
|
|
||||||
int window_size;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void rolling_max_destroy(struct rolling_max *rm) {
|
void rolling_max_destroy(struct rolling_max *rm) {
|
||||||
free(rm->elem);
|
|
||||||
free(rm->p);
|
free(rm->p);
|
||||||
free(rm);
|
free(rm);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct rolling_max *rolling_max_new(int size) {
|
struct rolling_max *rolling_max_new(int capacity) {
|
||||||
auto rm = ccalloc(1, struct rolling_max);
|
auto rm = ccalloc(1, struct rolling_max);
|
||||||
if (!rm) {
|
if (!rm) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
rm->p = ccalloc(size, int);
|
rm->p = ccalloc(capacity, int);
|
||||||
rm->elem = ccalloc(size, int);
|
if (!rm->p) {
|
||||||
rm->window_size = size;
|
|
||||||
if (!rm->p || !rm->elem) {
|
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
rm->capacity = capacity;
|
||||||
|
|
||||||
return rm;
|
return rm;
|
||||||
|
|
||||||
@@ -96,41 +126,29 @@ err:
|
|||||||
void rolling_max_reset(struct rolling_max *rm) {
|
void rolling_max_reset(struct rolling_max *rm) {
|
||||||
rm->p_head = 0;
|
rm->p_head = 0;
|
||||||
rm->np = 0;
|
rm->np = 0;
|
||||||
rm->nelem = 0;
|
|
||||||
rm->elem_head = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void rolling_max_push(struct rolling_max *rm, int val) {
|
#define IDX(n) ((n) % rm->capacity)
|
||||||
#define IDX(n) ((n) % rm->window_size)
|
/// Remove the oldest element in the window. The caller must maintain the list of elements
|
||||||
if (rm->nelem == rm->window_size) {
|
/// themselves, i.e. the behavior is undefined if `front` does not 1match the oldest
|
||||||
auto old_head = rm->elem_head;
|
/// element.
|
||||||
// Discard the oldest element.
|
void rolling_max_pop_front(struct rolling_max *rm, int front) {
|
||||||
// rm->elem.pop_front();
|
if (rm->p[rm->p_head] == front) {
|
||||||
rm->nelem--;
|
// rm->p.pop_front()
|
||||||
rm->elem_head = IDX(rm->elem_head + 1);
|
rm->p_head = IDX(rm->p_head + 1);
|
||||||
|
rm->np--;
|
||||||
// Remove discarded element from the priority queue too.
|
|
||||||
assert(rm->np);
|
|
||||||
if (rm->p[rm->p_head] == old_head) {
|
|
||||||
// rm->p.pop_front()
|
|
||||||
rm->p_head = IDX(rm->p_head + 1);
|
|
||||||
rm->np--;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add the new element to the queue.
|
void rolling_max_push_back(struct rolling_max *rm, int val) {
|
||||||
// rm->elem.push_back(val)
|
// Update the priority queue.
|
||||||
rm->elem[IDX(rm->elem_head + rm->nelem)] = val;
|
|
||||||
rm->nelem++;
|
|
||||||
|
|
||||||
// Update the prority queue.
|
|
||||||
// Remove all elements smaller than the new element from the queue. Because
|
// Remove all elements smaller than the new element from the queue. Because
|
||||||
// the new element will become the maximum element before them, and since they
|
// the new element will become the maximum element before them, and since they
|
||||||
// come b1efore the new element, they will have been popped before the new
|
// come before the new element, they will have been popped before the new
|
||||||
// element, so they will never become the maximum element.
|
// element, so they will never become the maximum element.
|
||||||
while (rm->np) {
|
while (rm->np) {
|
||||||
int p_tail = IDX(rm->p_head + rm->np - 1);
|
int p_tail = IDX(rm->p_head + rm->np - 1);
|
||||||
if (rm->elem[rm->p[p_tail]] > val) {
|
if (rm->p[p_tail] > val) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// rm->p.pop_back()
|
// rm->p.pop_back()
|
||||||
@@ -138,108 +156,119 @@ void rolling_max_push(struct rolling_max *rm, int val) {
|
|||||||
}
|
}
|
||||||
// Add the new element to the end of the queue.
|
// Add the new element to the end of the queue.
|
||||||
// rm->p.push_back(rm->start_index + rm->nelem - 1)
|
// rm->p.push_back(rm->start_index + rm->nelem - 1)
|
||||||
rm->p[IDX(rm->p_head + rm->np)] = IDX(rm->elem_head + rm->nelem - 1);
|
assert(rm->np < rm->capacity);
|
||||||
|
rm->p[IDX(rm->p_head + rm->np)] = val;
|
||||||
rm->np++;
|
rm->np++;
|
||||||
#undef IDX
|
|
||||||
}
|
}
|
||||||
|
#undef IDX
|
||||||
|
|
||||||
int rolling_max_get_max(struct rolling_max *rm) {
|
int rolling_max_get_max(struct rolling_max *rm) {
|
||||||
if (rm->np == 0) {
|
if (rm->np == 0) {
|
||||||
return INT_MIN;
|
return INT_MIN;
|
||||||
}
|
}
|
||||||
return rm->elem[rm->p[rm->p_head]];
|
return rm->p[rm->p_head];
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE(rolling_max_test) {
|
TEST_CASE(rolling_max_test) {
|
||||||
#define NELEM 15
|
#define NELEM 15
|
||||||
|
struct rolling_window queue;
|
||||||
|
rolling_window_init(&queue, 3);
|
||||||
auto rm = rolling_max_new(3);
|
auto rm = rolling_max_new(3);
|
||||||
const int data[NELEM] = {1, 2, 3, 1, 4, 5, 2, 3, 6, 5, 4, 3, 2, 0, 0};
|
const int data[NELEM] = {1, 2, 3, 1, 4, 5, 2, 3, 6, 5, 4, 3, 2, 0, 0};
|
||||||
const int expected_max[NELEM] = {1, 2, 3, 3, 4, 5, 5, 5, 6, 6, 6, 5, 4, 3, 2};
|
const int expected_max[NELEM] = {1, 2, 3, 3, 4, 5, 5, 5, 6, 6, 6, 5, 4, 3, 2};
|
||||||
int max[NELEM] = {0};
|
int max[NELEM] = {0};
|
||||||
for (int i = 0; i < NELEM; i++) {
|
for (int i = 0; i < NELEM; i++) {
|
||||||
rolling_max_push(rm, data[i]);
|
int front;
|
||||||
|
bool full = rolling_window_push_back(&queue, data[i], &front);
|
||||||
|
if (full) {
|
||||||
|
rolling_max_pop_front(rm, front);
|
||||||
|
}
|
||||||
|
rolling_max_push_back(rm, data[i]);
|
||||||
max[i] = rolling_max_get_max(rm);
|
max[i] = rolling_max_get_max(rm);
|
||||||
}
|
}
|
||||||
|
rolling_window_destroy(&queue);
|
||||||
|
rolling_max_destroy(rm);
|
||||||
TEST_TRUE(memcmp(max, expected_max, sizeof(max)) == 0);
|
TEST_TRUE(memcmp(max, expected_max, sizeof(max)) == 0);
|
||||||
#undef NELEM
|
#undef NELEM
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A rolling average of a stream of integers.
|
// Find the k-th smallest element in an array.
|
||||||
struct rolling_avg {
|
int quickselect(int *elems, int nelem, int k) {
|
||||||
/// The sum of the elements in the window.
|
int l = 0, r = nelem; // [l, r) is the range of candidates
|
||||||
int64_t sum;
|
while (l != r) {
|
||||||
|
int pivot = elems[l];
|
||||||
|
int i = l, j = r;
|
||||||
|
while (i < j) {
|
||||||
|
while (i < j && elems[--j] >= pivot) {
|
||||||
|
}
|
||||||
|
elems[i] = elems[j];
|
||||||
|
while (i < j && elems[++i] <= pivot) {
|
||||||
|
}
|
||||||
|
elems[j] = elems[i];
|
||||||
|
}
|
||||||
|
elems[i] = pivot;
|
||||||
|
|
||||||
/// The elements in the window.
|
if (i == k) {
|
||||||
int *elem;
|
break;
|
||||||
int head, nelem;
|
}
|
||||||
|
|
||||||
int window_size;
|
if (i < k) {
|
||||||
};
|
l = i + 1;
|
||||||
|
} else {
|
||||||
struct rolling_avg *rolling_avg_new(int size) {
|
r = i;
|
||||||
auto rm = ccalloc(1, struct rolling_avg);
|
}
|
||||||
if (!rm) {
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
return elems[k];
|
||||||
rm->elem = ccalloc(size, int);
|
|
||||||
rm->window_size = size;
|
|
||||||
if (!rm->elem) {
|
|
||||||
free(rm);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void rolling_avg_destroy(struct rolling_avg *rm) {
|
void rolling_quantile_init(struct rolling_quantile *rq, int capacity, int mink, int maxk) {
|
||||||
free(rm->elem);
|
*rq = (struct rolling_quantile){0};
|
||||||
free(rm);
|
rq->tmp_buffer = malloc(sizeof(int) * (size_t)capacity);
|
||||||
|
rq->capacity = capacity;
|
||||||
|
rq->min_target_rank = mink;
|
||||||
|
rq->max_target_rank = maxk;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rolling_avg_reset(struct rolling_avg *ra) {
|
void rolling_quantile_init_with_tolerance(struct rolling_quantile *rq, int window_size,
|
||||||
ra->sum = 0;
|
double target, double tolerance) {
|
||||||
ra->nelem = 0;
|
rolling_quantile_init(rq, window_size, (int)((target - tolerance) * window_size),
|
||||||
ra->head = 0;
|
(int)((target + tolerance) * window_size));
|
||||||
}
|
}
|
||||||
|
|
||||||
void rolling_avg_push(struct rolling_avg *ra, int val) {
|
void rolling_quantile_reset(struct rolling_quantile *rq) {
|
||||||
if (ra->nelem == ra->window_size) {
|
rq->current_rank = 0;
|
||||||
// Discard the oldest element.
|
rq->estimate = 0;
|
||||||
// rm->elem.pop_front();
|
}
|
||||||
ra->sum -= ra->elem[ra->head % ra->window_size];
|
|
||||||
ra->nelem--;
|
void rolling_quantile_destroy(struct rolling_quantile *rq) {
|
||||||
ra->head = (ra->head + 1) % ra->window_size;
|
free(rq->tmp_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rolling_quantile_estimate(struct rolling_quantile *rq, struct rolling_window *elements) {
|
||||||
|
if (rq->current_rank < rq->min_target_rank || rq->current_rank > rq->max_target_rank) {
|
||||||
|
if (elements->nelem != elements->window_size) {
|
||||||
|
return INT_MIN;
|
||||||
|
}
|
||||||
|
// Re-estimate the quantile.
|
||||||
|
assert(elements->nelem <= rq->capacity);
|
||||||
|
rolling_window_copy_to_array(elements, rq->tmp_buffer);
|
||||||
|
const int target_rank =
|
||||||
|
rq->min_target_rank + (rq->max_target_rank - rq->min_target_rank) / 2;
|
||||||
|
rq->estimate = quickselect(rq->tmp_buffer, elements->nelem, target_rank);
|
||||||
|
rq->current_rank = target_rank;
|
||||||
}
|
}
|
||||||
|
return rq->estimate;
|
||||||
// Add the new element to the queue.
|
|
||||||
// rm->elem.push_back(val)
|
|
||||||
ra->elem[(ra->head + ra->nelem) % ra->window_size] = val;
|
|
||||||
ra->sum += val;
|
|
||||||
ra->nelem++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double rolling_avg_get_avg(struct rolling_avg *ra) {
|
void rolling_quantile_push_back(struct rolling_quantile *rq, int x) {
|
||||||
if (ra->nelem == 0) {
|
if (x <= rq->estimate) {
|
||||||
return 0;
|
rq->current_rank++;
|
||||||
}
|
}
|
||||||
return (double)ra->sum / (double)ra->nelem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE(rolling_avg_test) {
|
void rolling_quantile_pop_front(struct rolling_quantile *rq, int x) {
|
||||||
#define NELEM 15
|
if (x <= rq->estimate) {
|
||||||
auto rm = rolling_avg_new(3);
|
rq->current_rank--;
|
||||||
const int data[NELEM] = {1, 2, 3, 1, 4, 5, 2, 3, 6, 5, 4, 3, 2, 0, 0};
|
|
||||||
const double expected_avg[NELEM] = {
|
|
||||||
1, 1.5, 2, 2, 8.0 / 3.0, 10.0 / 3.0, 11.0 / 3.0, 10.0 / 3.0,
|
|
||||||
11.0 / 3.0, 14.0 / 3.0, 5, 4, 3, 5.0 / 3.0, 2.0 / 3.0};
|
|
||||||
double avg[NELEM] = {0};
|
|
||||||
for (int i = 0; i < NELEM; i++) {
|
|
||||||
rolling_avg_push(rm, data[i]);
|
|
||||||
avg[i] = rolling_avg_get_avg(rm);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < NELEM; i++) {
|
|
||||||
TEST_EQUAL(avg[i], expected_avg[i]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
147
src/utils.h
147
src/utils.h
@@ -17,6 +17,7 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include "compiler.h"
|
#include "compiler.h"
|
||||||
|
#include "log.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
#define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
|
#define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
|
||||||
@@ -58,11 +59,11 @@ safe_isnan(double a) {
|
|||||||
/// being always true or false.
|
/// being always true or false.
|
||||||
#define ASSERT_IN_RANGE(var, lower, upper) \
|
#define ASSERT_IN_RANGE(var, lower, upper) \
|
||||||
do { \
|
do { \
|
||||||
auto __tmp attr_unused = (var); \
|
auto __assert_in_range_tmp attr_unused = (var); \
|
||||||
_Pragma("GCC diagnostic push"); \
|
_Pragma("GCC diagnostic push"); \
|
||||||
_Pragma("GCC diagnostic ignored \"-Wtype-limits\""); \
|
_Pragma("GCC diagnostic ignored \"-Wtype-limits\""); \
|
||||||
assert(__tmp >= lower); \
|
assert(__assert_in_range_tmp >= lower); \
|
||||||
assert(__tmp <= upper); \
|
assert(__assert_in_range_tmp <= upper); \
|
||||||
_Pragma("GCC diagnostic pop"); \
|
_Pragma("GCC diagnostic pop"); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
@@ -112,11 +113,26 @@ safe_isnan(double a) {
|
|||||||
#define to_u32_checked(val) \
|
#define to_u32_checked(val) \
|
||||||
({ \
|
({ \
|
||||||
auto __to_tmp = (val); \
|
auto __to_tmp = (val); \
|
||||||
int64_t max attr_unused = UINT32_MAX; /* silence clang tautological \
|
int64_t __to_u32_max attr_unused = UINT32_MAX; /* silence clang \
|
||||||
comparison warning*/ \
|
tautological \
|
||||||
ASSERT_IN_RANGE(__to_tmp, 0, max); \
|
comparison warning */ \
|
||||||
|
ASSERT_IN_RANGE(__to_tmp, 0, __to_u32_max); \
|
||||||
(uint32_t) __to_tmp; \
|
(uint32_t) __to_tmp; \
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* container_of - cast a member of a structure out to the containing structure
|
||||||
|
* @ptr: the pointer to the member.
|
||||||
|
* @type: the type of the container struct this is embedded in.
|
||||||
|
* @member: the name of the member within the struct.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#define container_of(ptr, type, member) \
|
||||||
|
({ \
|
||||||
|
const __typeof__(((type *)0)->member) *__mptr = (ptr); \
|
||||||
|
(type *)((char *)__mptr - offsetof(type, member)); \
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize an int value to a specific range.
|
* Normalize an int value to a specific range.
|
||||||
*
|
*
|
||||||
@@ -125,11 +141,13 @@ safe_isnan(double a) {
|
|||||||
* @param max maximum value
|
* @param max maximum value
|
||||||
* @return normalized value
|
* @return normalized value
|
||||||
*/
|
*/
|
||||||
static inline int attr_const normalize_i_range(int i, int min, int max) {
|
static inline int attr_const attr_unused normalize_i_range(int i, int min, int max) {
|
||||||
if (i > max)
|
if (i > max) {
|
||||||
return max;
|
return max;
|
||||||
if (i < min)
|
}
|
||||||
|
if (i < min) {
|
||||||
return min;
|
return min;
|
||||||
|
}
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +164,12 @@ static inline int attr_const lerp_range(int a, int b, int c, int d, int value) {
|
|||||||
return (d-c)*(value-a)/(b-a) + c;
|
return (d-c)*(value-a)/(b-a) + c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generic integer abs()
|
||||||
|
#define iabs(val) \
|
||||||
|
({ \
|
||||||
|
__auto_type __tmp = (val); \
|
||||||
|
__tmp > 0 ? __tmp : -__tmp; \
|
||||||
|
})
|
||||||
#define min2(a, b) ((a) > (b) ? (b) : (a))
|
#define min2(a, b) ((a) > (b) ? (b) : (a))
|
||||||
#define max2(a, b) ((a) > (b) ? (a) : (b))
|
#define max2(a, b) ((a) > (b) ? (a) : (b))
|
||||||
#define min3(a, b, c) min2(a, min2(b, c))
|
#define min3(a, b, c) min2(a, min2(b, c))
|
||||||
@@ -162,10 +186,12 @@ static inline int attr_const lerp_range(int a, int b, int c, int d, int value) {
|
|||||||
* @return normalized value
|
* @return normalized value
|
||||||
*/
|
*/
|
||||||
static inline double attr_const normalize_d_range(double d, double min, double max) {
|
static inline double attr_const normalize_d_range(double d, double min, double max) {
|
||||||
if (d > max)
|
if (d > max) {
|
||||||
return max;
|
return max;
|
||||||
if (d < min)
|
}
|
||||||
|
if (d < min) {
|
||||||
return min;
|
return min;
|
||||||
|
}
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +201,7 @@ static inline double attr_const normalize_d_range(double d, double min, double m
|
|||||||
* @param d double value to normalize
|
* @param d double value to normalize
|
||||||
* @return normalized value
|
* @return normalized value
|
||||||
*/
|
*/
|
||||||
static inline double attr_const normalize_d(double d) {
|
static inline double attr_const attr_unused normalize_d(double d) {
|
||||||
return normalize_d_range(d, 0.0, 1.0);
|
return normalize_d_range(d, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +251,7 @@ allocchk_(const char *func_name, const char *file, unsigned int line, void *ptr)
|
|||||||
((type *)allocchk(calloc((size_t)tmp, sizeof(type)))); \
|
((type *)allocchk(calloc((size_t)tmp, sizeof(type)))); \
|
||||||
})
|
})
|
||||||
|
|
||||||
/// @brief Wrapper of ealloc().
|
/// @brief Wrapper of realloc().
|
||||||
#define crealloc(ptr, nmemb) \
|
#define crealloc(ptr, nmemb) \
|
||||||
({ \
|
({ \
|
||||||
auto tmp = (nmemb); \
|
auto tmp = (nmemb); \
|
||||||
@@ -303,20 +329,97 @@ static inline void free_charpp(char **str) {
|
|||||||
///
|
///
|
||||||
int next_power_of_two(int n);
|
int next_power_of_two(int n);
|
||||||
|
|
||||||
|
struct rolling_window {
|
||||||
|
int *elem;
|
||||||
|
int elem_head, nelem;
|
||||||
|
int window_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
void rolling_window_destroy(struct rolling_window *rw);
|
||||||
|
void rolling_window_reset(struct rolling_window *rw);
|
||||||
|
void rolling_window_init(struct rolling_window *rw, int size);
|
||||||
|
int rolling_window_pop_front(struct rolling_window *rw);
|
||||||
|
bool rolling_window_push_back(struct rolling_window *rw, int val, int *front);
|
||||||
|
|
||||||
|
/// Copy the contents of the rolling window to an array. The array is assumed to
|
||||||
|
/// have enough space to hold the contents of the rolling window.
|
||||||
|
static inline void attr_unused rolling_window_copy_to_array(struct rolling_window *rw,
|
||||||
|
int *arr) {
|
||||||
|
// The length from head to the end of the array
|
||||||
|
auto head_len = (size_t)(rw->window_size - rw->elem_head);
|
||||||
|
if (head_len >= (size_t)rw->nelem) {
|
||||||
|
memcpy(arr, rw->elem + rw->elem_head, sizeof(int) * (size_t)rw->nelem);
|
||||||
|
} else {
|
||||||
|
auto tail_len = (size_t)((size_t)rw->nelem - head_len);
|
||||||
|
memcpy(arr, rw->elem + rw->elem_head, sizeof(int) * head_len);
|
||||||
|
memcpy(arr + head_len, rw->elem, sizeof(int) * tail_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct rolling_max;
|
struct rolling_max;
|
||||||
|
|
||||||
struct rolling_max *rolling_max_new(int window_size);
|
struct rolling_max *rolling_max_new(int capacity);
|
||||||
void rolling_max_free(struct rolling_max *rm);
|
void rolling_max_destroy(struct rolling_max *rm);
|
||||||
void rolling_max_reset(struct rolling_max *rm);
|
void rolling_max_reset(struct rolling_max *rm);
|
||||||
void rolling_max_push(struct rolling_max *rm, int val);
|
void rolling_max_pop_front(struct rolling_max *rm, int front);
|
||||||
|
void rolling_max_push_back(struct rolling_max *rm, int val);
|
||||||
int rolling_max_get_max(struct rolling_max *rm);
|
int rolling_max_get_max(struct rolling_max *rm);
|
||||||
|
|
||||||
struct rolling_avg;
|
/// Estimate the mean and variance of random variable X using Welford's online
|
||||||
struct rolling_avg *rolling_avg_new(int window_size);
|
/// algorithm.
|
||||||
void rolling_avg_free(struct rolling_avg *ra);
|
struct cumulative_mean_and_var {
|
||||||
void rolling_avg_reset(struct rolling_avg *ra);
|
double mean;
|
||||||
void rolling_avg_push(struct rolling_avg *ra, int val);
|
double m2;
|
||||||
double rolling_avg_get_avg(struct rolling_avg *ra);
|
unsigned int n;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline attr_unused void
|
||||||
|
cumulative_mean_and_var_init(struct cumulative_mean_and_var *cmv) {
|
||||||
|
*cmv = (struct cumulative_mean_and_var){0};
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline attr_unused void
|
||||||
|
cumulative_mean_and_var_update(struct cumulative_mean_and_var *cmv, double x) {
|
||||||
|
if (cmv->n == UINT_MAX) {
|
||||||
|
// We have too many elements, let's keep the mean and variance.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cmv->n++;
|
||||||
|
double delta = x - cmv->mean;
|
||||||
|
cmv->mean += delta / (double)cmv->n;
|
||||||
|
cmv->m2 += delta * (x - cmv->mean);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline attr_unused double
|
||||||
|
cumulative_mean_and_var_get_var(struct cumulative_mean_and_var *cmv) {
|
||||||
|
if (cmv->n < 2) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return cmv->m2 / (double)(cmv->n - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the k-th smallest element in an array.
|
||||||
|
int quickselect(int *elems, int nelem, int k);
|
||||||
|
|
||||||
|
/// A naive quantile estimator.
|
||||||
|
///
|
||||||
|
/// Estimates the N-th percentile of a random variable X in a sliding window.
|
||||||
|
struct rolling_quantile {
|
||||||
|
int current_rank;
|
||||||
|
int min_target_rank, max_target_rank;
|
||||||
|
int estimate;
|
||||||
|
int capacity;
|
||||||
|
int *tmp_buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
void rolling_quantile_init(struct rolling_quantile *rq, int capacity, int mink, int maxk);
|
||||||
|
void rolling_quantile_init_with_tolerance(struct rolling_quantile *rq, int window_size,
|
||||||
|
double target, double tolerance);
|
||||||
|
void rolling_quantile_reset(struct rolling_quantile *rq);
|
||||||
|
void rolling_quantile_destroy(struct rolling_quantile *rq);
|
||||||
|
int rolling_quantile_estimate(struct rolling_quantile *rq, struct rolling_window *elements);
|
||||||
|
void rolling_quantile_push_back(struct rolling_quantile *rq, int x);
|
||||||
|
void rolling_quantile_pop_front(struct rolling_quantile *rq, int x);
|
||||||
|
|
||||||
// Some versions of the Android libc do not have timespec_get(), use
|
// Some versions of the Android libc do not have timespec_get(), use
|
||||||
// clock_gettime() instead.
|
// clock_gettime() instead.
|
||||||
|
|||||||
569
src/vblank.c
Normal file
569
src/vblank.c
Normal file
@@ -0,0 +1,569 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <ev.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
#include <xcb/xproto.h>
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#ifdef CONFIG_OPENGL
|
||||||
|
// Enable sgi_video_sync_vblank_scheduler
|
||||||
|
#include <X11/X.h>
|
||||||
|
#include <X11/Xlib-xcb.h>
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
#include <epoxy/glx.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "compiler.h"
|
||||||
|
#include "list.h" // for container_of
|
||||||
|
#include "log.h"
|
||||||
|
#include "vblank.h"
|
||||||
|
#include "x.h"
|
||||||
|
|
||||||
|
struct vblank_closure {
|
||||||
|
vblank_callback_t fn;
|
||||||
|
void *user_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define VBLANK_WIND_DOWN 4
|
||||||
|
|
||||||
|
struct vblank_scheduler {
|
||||||
|
struct x_connection *c;
|
||||||
|
size_t callback_capacity, callback_count;
|
||||||
|
struct vblank_closure *callbacks;
|
||||||
|
struct ev_loop *loop;
|
||||||
|
/// Request extra vblank events even when no callbacks are scheduled.
|
||||||
|
/// This is because when callbacks are scheduled too close to a vblank,
|
||||||
|
/// we might send PresentNotifyMsc request too late and miss the vblank event.
|
||||||
|
/// So we request extra vblank events right after the last vblank event
|
||||||
|
/// to make sure this doesn't happen.
|
||||||
|
unsigned int wind_down;
|
||||||
|
xcb_window_t target_window;
|
||||||
|
enum vblank_scheduler_type type;
|
||||||
|
bool vblank_event_requested;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct present_vblank_scheduler {
|
||||||
|
struct vblank_scheduler base;
|
||||||
|
|
||||||
|
uint64_t last_msc;
|
||||||
|
/// The timestamp for the end of last vblank.
|
||||||
|
uint64_t last_ust;
|
||||||
|
ev_timer callback_timer;
|
||||||
|
xcb_present_event_t event_id;
|
||||||
|
xcb_special_event_t *event;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct vblank_scheduler_ops {
|
||||||
|
size_t size;
|
||||||
|
bool (*init)(struct vblank_scheduler *self);
|
||||||
|
void (*deinit)(struct vblank_scheduler *self);
|
||||||
|
bool (*schedule)(struct vblank_scheduler *self);
|
||||||
|
bool (*handle_x_events)(struct vblank_scheduler *self);
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_event *event);
|
||||||
|
|
||||||
|
#ifdef CONFIG_OPENGL
|
||||||
|
struct sgi_video_sync_vblank_scheduler {
|
||||||
|
struct vblank_scheduler base;
|
||||||
|
|
||||||
|
// Since glXWaitVideoSyncSGI blocks, we need to run it in a separate thread.
|
||||||
|
// ... and all the thread shenanigans that come with it.
|
||||||
|
_Atomic unsigned int current_msc;
|
||||||
|
_Atomic uint64_t current_ust;
|
||||||
|
ev_async notify;
|
||||||
|
pthread_t sync_thread;
|
||||||
|
bool running, error;
|
||||||
|
unsigned int last_msc;
|
||||||
|
|
||||||
|
/// Protects `running`, and `base.vblank_event_requested`
|
||||||
|
pthread_mutex_t vblank_requested_mtx;
|
||||||
|
pthread_cond_t vblank_requested_cnd;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sgi_video_sync_thread_args {
|
||||||
|
struct sgi_video_sync_vblank_scheduler *self;
|
||||||
|
int start_status;
|
||||||
|
pthread_mutex_t start_mtx;
|
||||||
|
pthread_cond_t start_cnd;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool check_sgi_video_sync_extension(Display *dpy, int screen) {
|
||||||
|
const char *glx_ext = glXQueryExtensionsString(dpy, screen);
|
||||||
|
const char *needle = "GLX_SGI_video_sync";
|
||||||
|
char *found = strstr(glx_ext, needle);
|
||||||
|
if (!found) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (found != glx_ext && found[-1] != ' ') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (found[strlen(needle)] != ' ' && found[strlen(needle)] != '\0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *sgi_video_sync_thread(void *data) {
|
||||||
|
auto args = (struct sgi_video_sync_thread_args *)data;
|
||||||
|
auto self = args->self;
|
||||||
|
Display *dpy = XOpenDisplay(NULL);
|
||||||
|
int error_code = 0;
|
||||||
|
if (!dpy) {
|
||||||
|
error_code = 1;
|
||||||
|
goto start_failed;
|
||||||
|
}
|
||||||
|
Window root = DefaultRootWindow(dpy), dummy = None;
|
||||||
|
int screen = DefaultScreen(dpy);
|
||||||
|
int ncfg = 0;
|
||||||
|
GLXFBConfig *cfg_ = glXChooseFBConfig(
|
||||||
|
dpy, screen,
|
||||||
|
(int[]){GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 0},
|
||||||
|
&ncfg);
|
||||||
|
GLXContext ctx = NULL;
|
||||||
|
GLXDrawable drawable = None;
|
||||||
|
|
||||||
|
if (!cfg_) {
|
||||||
|
error_code = 2;
|
||||||
|
goto start_failed;
|
||||||
|
}
|
||||||
|
GLXFBConfig cfg = cfg_[0];
|
||||||
|
XFree(cfg_);
|
||||||
|
|
||||||
|
XVisualInfo *vi = glXGetVisualFromFBConfig(dpy, cfg);
|
||||||
|
if (!vi) {
|
||||||
|
error_code = 3;
|
||||||
|
goto start_failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
Visual *visual = vi->visual;
|
||||||
|
const int depth = vi->depth;
|
||||||
|
XFree(vi);
|
||||||
|
|
||||||
|
Colormap colormap = XCreateColormap(dpy, root, visual, AllocNone);
|
||||||
|
XSetWindowAttributes attributes;
|
||||||
|
attributes.colormap = colormap;
|
||||||
|
|
||||||
|
dummy = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, depth, InputOutput, visual,
|
||||||
|
CWColormap, &attributes);
|
||||||
|
XFreeColormap(dpy, colormap);
|
||||||
|
if (dummy == None) {
|
||||||
|
error_code = 4;
|
||||||
|
goto start_failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawable = glXCreateWindow(dpy, cfg, dummy, NULL);
|
||||||
|
if (drawable == None) {
|
||||||
|
error_code = 5;
|
||||||
|
goto start_failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = glXCreateNewContext(dpy, cfg, GLX_RGBA_TYPE, 0, true);
|
||||||
|
if (ctx == NULL) {
|
||||||
|
error_code = 6;
|
||||||
|
goto start_failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!glXMakeContextCurrent(dpy, drawable, drawable, ctx)) {
|
||||||
|
error_code = 7;
|
||||||
|
goto start_failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!check_sgi_video_sync_extension(dpy, screen)) {
|
||||||
|
error_code = 8;
|
||||||
|
goto start_failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&args->start_mtx);
|
||||||
|
args->start_status = 0;
|
||||||
|
pthread_cond_signal(&args->start_cnd);
|
||||||
|
pthread_mutex_unlock(&args->start_mtx);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&self->vblank_requested_mtx);
|
||||||
|
while (self->running) {
|
||||||
|
if (!self->base.vblank_event_requested) {
|
||||||
|
pthread_cond_wait(&self->vblank_requested_cnd,
|
||||||
|
&self->vblank_requested_mtx);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&self->vblank_requested_mtx);
|
||||||
|
|
||||||
|
unsigned int last_msc;
|
||||||
|
glXWaitVideoSyncSGI(1, 0, &last_msc);
|
||||||
|
|
||||||
|
struct timespec now;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
atomic_store(&self->current_msc, last_msc);
|
||||||
|
atomic_store(&self->current_ust,
|
||||||
|
(uint64_t)(now.tv_sec * 1000000 + now.tv_nsec / 1000));
|
||||||
|
ev_async_send(self->base.loop, &self->notify);
|
||||||
|
pthread_mutex_lock(&self->vblank_requested_mtx);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&self->vblank_requested_mtx);
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
start_failed:
|
||||||
|
pthread_mutex_lock(&args->start_mtx);
|
||||||
|
args->start_status = error_code;
|
||||||
|
pthread_cond_signal(&args->start_cnd);
|
||||||
|
pthread_mutex_unlock(&args->start_mtx);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (dpy) {
|
||||||
|
glXMakeCurrent(dpy, None, NULL);
|
||||||
|
if (ctx) {
|
||||||
|
glXDestroyContext(dpy, ctx);
|
||||||
|
}
|
||||||
|
if (drawable) {
|
||||||
|
glXDestroyWindow(dpy, drawable);
|
||||||
|
}
|
||||||
|
if (dummy) {
|
||||||
|
XDestroyWindow(dpy, dummy);
|
||||||
|
}
|
||||||
|
XCloseDisplay(dpy);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool sgi_video_sync_scheduler_schedule(struct vblank_scheduler *base) {
|
||||||
|
auto self = (struct sgi_video_sync_vblank_scheduler *)base;
|
||||||
|
if (self->error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
log_verbose("Requesting vblank event for msc %d", self->current_msc + 1);
|
||||||
|
pthread_mutex_lock(&self->vblank_requested_mtx);
|
||||||
|
assert(!base->vblank_event_requested);
|
||||||
|
base->vblank_event_requested = true;
|
||||||
|
pthread_cond_signal(&self->vblank_requested_cnd);
|
||||||
|
pthread_mutex_unlock(&self->vblank_requested_mtx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sgi_video_sync_scheduler_callback(EV_P attr_unused, ev_async *w, int attr_unused revents);
|
||||||
|
|
||||||
|
static bool sgi_video_sync_scheduler_init(struct vblank_scheduler *base) {
|
||||||
|
auto self = (struct sgi_video_sync_vblank_scheduler *)base;
|
||||||
|
auto args = (struct sgi_video_sync_thread_args){
|
||||||
|
.self = self,
|
||||||
|
.start_status = -1,
|
||||||
|
};
|
||||||
|
bool succeeded = true;
|
||||||
|
pthread_mutex_init(&args.start_mtx, NULL);
|
||||||
|
pthread_cond_init(&args.start_cnd, NULL);
|
||||||
|
|
||||||
|
base->type = VBLANK_SCHEDULER_SGI_VIDEO_SYNC;
|
||||||
|
ev_async_init(&self->notify, sgi_video_sync_scheduler_callback);
|
||||||
|
ev_async_start(base->loop, &self->notify);
|
||||||
|
pthread_mutex_init(&self->vblank_requested_mtx, NULL);
|
||||||
|
pthread_cond_init(&self->vblank_requested_cnd, NULL);
|
||||||
|
|
||||||
|
self->running = true;
|
||||||
|
pthread_create(&self->sync_thread, NULL, sgi_video_sync_thread, &args);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&args.start_mtx);
|
||||||
|
while (args.start_status == -1) {
|
||||||
|
pthread_cond_wait(&args.start_cnd, &args.start_mtx);
|
||||||
|
}
|
||||||
|
if (args.start_status != 0) {
|
||||||
|
log_fatal("Failed to start sgi_video_sync_thread, error code: %d",
|
||||||
|
args.start_status);
|
||||||
|
succeeded = false;
|
||||||
|
} else {
|
||||||
|
log_info("Started sgi_video_sync_thread");
|
||||||
|
}
|
||||||
|
self->error = !succeeded;
|
||||||
|
self->last_msc = 0;
|
||||||
|
pthread_mutex_destroy(&args.start_mtx);
|
||||||
|
pthread_cond_destroy(&args.start_cnd);
|
||||||
|
return succeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sgi_video_sync_scheduler_deinit(struct vblank_scheduler *base) {
|
||||||
|
auto self = (struct sgi_video_sync_vblank_scheduler *)base;
|
||||||
|
ev_async_stop(base->loop, &self->notify);
|
||||||
|
pthread_mutex_lock(&self->vblank_requested_mtx);
|
||||||
|
self->running = false;
|
||||||
|
pthread_cond_signal(&self->vblank_requested_cnd);
|
||||||
|
pthread_mutex_unlock(&self->vblank_requested_mtx);
|
||||||
|
|
||||||
|
pthread_join(self->sync_thread, NULL);
|
||||||
|
|
||||||
|
pthread_mutex_destroy(&self->vblank_requested_mtx);
|
||||||
|
pthread_cond_destroy(&self->vblank_requested_cnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sgi_video_sync_scheduler_callback(EV_P attr_unused, ev_async *w, int attr_unused revents) {
|
||||||
|
auto sched = container_of(w, struct sgi_video_sync_vblank_scheduler, notify);
|
||||||
|
auto msc = atomic_load(&sched->current_msc);
|
||||||
|
if (sched->last_msc == msc) {
|
||||||
|
// NVIDIA spams us with duplicate vblank events after a suspend/resume
|
||||||
|
// cycle. Recreating the X connection and GLX context seems to fix this.
|
||||||
|
// Oh NVIDIA.
|
||||||
|
log_warn("Duplicate vblank event found with msc %d. Possible NVIDIA bug?", msc);
|
||||||
|
log_warn("Resetting the vblank scheduler");
|
||||||
|
sgi_video_sync_scheduler_deinit(&sched->base);
|
||||||
|
sched->base.vblank_event_requested = false;
|
||||||
|
if (!sgi_video_sync_scheduler_init(&sched->base)) {
|
||||||
|
log_error("Failed to reset the vblank scheduler");
|
||||||
|
} else {
|
||||||
|
sgi_video_sync_scheduler_schedule(&sched->base);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto event = (struct vblank_event){
|
||||||
|
.msc = msc,
|
||||||
|
.ust = atomic_load(&sched->current_ust),
|
||||||
|
};
|
||||||
|
sched->base.vblank_event_requested = false;
|
||||||
|
sched->last_msc = msc;
|
||||||
|
log_verbose("Received vblank event for msc %" PRIu64, event.msc);
|
||||||
|
vblank_scheduler_invoke_callbacks(&sched->base, &event);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool present_vblank_scheduler_schedule(struct vblank_scheduler *base) {
|
||||||
|
auto self = (struct present_vblank_scheduler *)base;
|
||||||
|
log_verbose("Requesting vblank event for window 0x%08x, msc %" PRIu64,
|
||||||
|
base->target_window, self->last_msc + 1);
|
||||||
|
assert(!base->vblank_event_requested);
|
||||||
|
x_request_vblank_event(base->c, base->target_window, self->last_msc + 1);
|
||||||
|
base->vblank_event_requested = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void present_vblank_callback(EV_P attr_unused, ev_timer *w, int attr_unused revents) {
|
||||||
|
auto sched = container_of(w, struct present_vblank_scheduler, callback_timer);
|
||||||
|
auto event = (struct vblank_event){
|
||||||
|
.msc = sched->last_msc,
|
||||||
|
.ust = sched->last_ust,
|
||||||
|
};
|
||||||
|
sched->base.vblank_event_requested = false;
|
||||||
|
vblank_scheduler_invoke_callbacks(&sched->base, &event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool present_vblank_scheduler_init(struct vblank_scheduler *base) {
|
||||||
|
auto self = (struct present_vblank_scheduler *)base;
|
||||||
|
base->type = VBLANK_SCHEDULER_PRESENT;
|
||||||
|
ev_timer_init(&self->callback_timer, present_vblank_callback, 0, 0);
|
||||||
|
|
||||||
|
self->event_id = x_new_id(base->c);
|
||||||
|
auto select_input =
|
||||||
|
xcb_present_select_input(base->c->c, self->event_id, base->target_window,
|
||||||
|
XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY);
|
||||||
|
set_cant_fail_cookie(base->c, select_input);
|
||||||
|
self->event =
|
||||||
|
xcb_register_for_special_xge(base->c->c, &xcb_present_id, self->event_id, NULL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void present_vblank_scheduler_deinit(struct vblank_scheduler *base) {
|
||||||
|
auto self = (struct present_vblank_scheduler *)base;
|
||||||
|
ev_timer_stop(base->loop, &self->callback_timer);
|
||||||
|
auto select_input =
|
||||||
|
xcb_present_select_input(base->c->c, self->event_id, base->target_window, 0);
|
||||||
|
set_cant_fail_cookie(base->c, select_input);
|
||||||
|
xcb_unregister_for_special_event(base->c->c, self->event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle PresentCompleteNotify events
|
||||||
|
///
|
||||||
|
/// Schedule the registered callback to be called when the current vblank ends.
|
||||||
|
static void handle_present_complete_notify(struct present_vblank_scheduler *self,
|
||||||
|
xcb_present_complete_notify_event_t *cne) {
|
||||||
|
assert(self->base.type == VBLANK_SCHEDULER_PRESENT);
|
||||||
|
|
||||||
|
if (cne->kind != XCB_PRESENT_COMPLETE_KIND_NOTIFY_MSC) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(self->base.vblank_event_requested);
|
||||||
|
|
||||||
|
// X sometimes sends duplicate/bogus MSC events, when screen has just been turned
|
||||||
|
// off. Don't use the msc value in these events. We treat this as not receiving a
|
||||||
|
// vblank event at all, and try to get a new one.
|
||||||
|
//
|
||||||
|
// See:
|
||||||
|
// https://gitlab.freedesktop.org/xorg/xserver/-/issues/1418
|
||||||
|
bool event_is_invalid = cne->msc <= self->last_msc || cne->ust == 0;
|
||||||
|
if (event_is_invalid) {
|
||||||
|
log_debug("Invalid PresentCompleteNotify event, %" PRIu64 " %" PRIu64,
|
||||||
|
cne->msc, cne->ust);
|
||||||
|
x_request_vblank_event(self->base.c, cne->window, self->last_msc + 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->last_ust = cne->ust;
|
||||||
|
self->last_msc = cne->msc;
|
||||||
|
|
||||||
|
struct timespec now;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
auto now_us = (unsigned long)(now.tv_sec * 1000000L + now.tv_nsec / 1000);
|
||||||
|
double delay_sec = 0.0;
|
||||||
|
if (now_us < cne->ust) {
|
||||||
|
log_trace("The end of this vblank is %" PRIu64 " us into the "
|
||||||
|
"future",
|
||||||
|
cne->ust - now_us);
|
||||||
|
delay_sec = (double)(cne->ust - now_us) / 1000000.0;
|
||||||
|
}
|
||||||
|
// Wait until the end of the current vblank to invoke callbacks. If we
|
||||||
|
// call it too early, it can mistakenly think the render missed the
|
||||||
|
// vblank, and doesn't schedule render for the next vblank, causing frame
|
||||||
|
// drops.
|
||||||
|
assert(!ev_is_active(&self->callback_timer));
|
||||||
|
ev_timer_set(&self->callback_timer, delay_sec, 0);
|
||||||
|
ev_timer_start(self->base.loop, &self->callback_timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_present_events(struct vblank_scheduler *base) {
|
||||||
|
auto self = (struct present_vblank_scheduler *)base;
|
||||||
|
xcb_present_generic_event_t *ev;
|
||||||
|
while ((ev = (void *)xcb_poll_for_special_event(base->c->c, self->event))) {
|
||||||
|
if (ev->event != self->event_id) {
|
||||||
|
// This event doesn't have the right event context, it's not meant
|
||||||
|
// for us.
|
||||||
|
goto next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only subscribed to the complete notify event.
|
||||||
|
assert(ev->evtype == XCB_PRESENT_EVENT_COMPLETE_NOTIFY);
|
||||||
|
handle_present_complete_notify(self, (void *)ev);
|
||||||
|
next:
|
||||||
|
free(ev);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct vblank_scheduler_ops vblank_scheduler_ops[LAST_VBLANK_SCHEDULER] = {
|
||||||
|
[VBLANK_SCHEDULER_PRESENT] =
|
||||||
|
{
|
||||||
|
.size = sizeof(struct present_vblank_scheduler),
|
||||||
|
.init = present_vblank_scheduler_init,
|
||||||
|
.deinit = present_vblank_scheduler_deinit,
|
||||||
|
.schedule = present_vblank_scheduler_schedule,
|
||||||
|
.handle_x_events = handle_present_events,
|
||||||
|
},
|
||||||
|
#ifdef CONFIG_OPENGL
|
||||||
|
[VBLANK_SCHEDULER_SGI_VIDEO_SYNC] =
|
||||||
|
{
|
||||||
|
.size = sizeof(struct sgi_video_sync_vblank_scheduler),
|
||||||
|
.init = sgi_video_sync_scheduler_init,
|
||||||
|
.deinit = sgi_video_sync_scheduler_deinit,
|
||||||
|
.schedule = sgi_video_sync_scheduler_schedule,
|
||||||
|
.handle_x_events = NULL,
|
||||||
|
},
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool vblank_scheduler_schedule_internal(struct vblank_scheduler *self) {
|
||||||
|
assert(self->type < LAST_VBLANK_SCHEDULER);
|
||||||
|
auto fn = vblank_scheduler_ops[self->type].schedule;
|
||||||
|
assert(fn != NULL);
|
||||||
|
return fn(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool vblank_scheduler_schedule(struct vblank_scheduler *self,
|
||||||
|
vblank_callback_t vblank_callback, void *user_data) {
|
||||||
|
if (self->callback_count == 0 && self->wind_down == 0) {
|
||||||
|
if (!vblank_scheduler_schedule_internal(self)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (self->callback_count == self->callback_capacity) {
|
||||||
|
size_t new_capacity =
|
||||||
|
self->callback_capacity ? self->callback_capacity * 2 : 1;
|
||||||
|
void *new_buffer =
|
||||||
|
realloc(self->callbacks, new_capacity * sizeof(*self->callbacks));
|
||||||
|
if (!new_buffer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self->callbacks = new_buffer;
|
||||||
|
self->callback_capacity = new_capacity;
|
||||||
|
}
|
||||||
|
self->callbacks[self->callback_count++] = (struct vblank_closure){
|
||||||
|
.fn = vblank_callback,
|
||||||
|
.user_data = user_data,
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_event *event) {
|
||||||
|
// callbacks might be added during callback invocation, so we need to
|
||||||
|
// copy the callback_count.
|
||||||
|
size_t count = self->callback_count, write_head = 0;
|
||||||
|
if (count == 0) {
|
||||||
|
self->wind_down--;
|
||||||
|
} else {
|
||||||
|
self->wind_down = VBLANK_WIND_DOWN;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
auto action = self->callbacks[i].fn(event, self->callbacks[i].user_data);
|
||||||
|
switch (action) {
|
||||||
|
case VBLANK_CALLBACK_AGAIN:
|
||||||
|
if (i != write_head) {
|
||||||
|
self->callbacks[write_head] = self->callbacks[i];
|
||||||
|
}
|
||||||
|
write_head++;
|
||||||
|
case VBLANK_CALLBACK_DONE:
|
||||||
|
default: // nothing to do
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memset(self->callbacks + write_head, 0,
|
||||||
|
(count - write_head) * sizeof(*self->callbacks));
|
||||||
|
assert(count == self->callback_count && "callbacks should not be added when "
|
||||||
|
"callbacks are being invoked.");
|
||||||
|
self->callback_count = write_head;
|
||||||
|
if (self->callback_count || self->wind_down) {
|
||||||
|
vblank_scheduler_schedule_internal(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void vblank_scheduler_free(struct vblank_scheduler *self) {
|
||||||
|
assert(self->type < LAST_VBLANK_SCHEDULER);
|
||||||
|
auto fn = vblank_scheduler_ops[self->type].deinit;
|
||||||
|
if (fn != NULL) {
|
||||||
|
fn(self);
|
||||||
|
}
|
||||||
|
free(self->callbacks);
|
||||||
|
free(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct vblank_scheduler *
|
||||||
|
vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c,
|
||||||
|
xcb_window_t target_window, enum vblank_scheduler_type type) {
|
||||||
|
size_t object_size = vblank_scheduler_ops[type].size;
|
||||||
|
auto init_fn = vblank_scheduler_ops[type].init;
|
||||||
|
if (!object_size || !init_fn) {
|
||||||
|
log_error("Unsupported or invalid vblank scheduler type: %d", type);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(object_size >= sizeof(struct vblank_scheduler));
|
||||||
|
struct vblank_scheduler *self = calloc(1, object_size);
|
||||||
|
self->target_window = target_window;
|
||||||
|
self->c = c;
|
||||||
|
self->loop = loop;
|
||||||
|
init_fn(self);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool vblank_handle_x_events(struct vblank_scheduler *self) {
|
||||||
|
assert(self->type < LAST_VBLANK_SCHEDULER);
|
||||||
|
auto fn = vblank_scheduler_ops[self->type].handle_x_events;
|
||||||
|
if (fn != NULL) {
|
||||||
|
return fn(self);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
47
src/vblank.h
Normal file
47
src/vblank.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <xcb/present.h>
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
|
#include <ev.h>
|
||||||
|
#include <xcb/xproto.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "x.h"
|
||||||
|
|
||||||
|
/// An object that schedule vblank events.
|
||||||
|
struct vblank_scheduler;
|
||||||
|
|
||||||
|
struct vblank_event {
|
||||||
|
uint64_t msc;
|
||||||
|
uint64_t ust;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum vblank_callback_action {
|
||||||
|
/// The callback should be called again in the next vblank.
|
||||||
|
VBLANK_CALLBACK_AGAIN,
|
||||||
|
/// The callback is done and should not be called again.
|
||||||
|
VBLANK_CALLBACK_DONE,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum vblank_callback_action (*vblank_callback_t)(struct vblank_event *event,
|
||||||
|
void *user_data);
|
||||||
|
|
||||||
|
/// Schedule a vblank event.
|
||||||
|
///
|
||||||
|
/// Schedule for `cb` to be called when the current vblank ends. If this is called
|
||||||
|
/// from a callback function for the current vblank, the newly scheduled callback
|
||||||
|
/// will be called in the next vblank.
|
||||||
|
///
|
||||||
|
/// Returns whether the scheduling is successful. Scheduling can fail if there
|
||||||
|
/// is not enough memory.
|
||||||
|
bool vblank_scheduler_schedule(struct vblank_scheduler *self, vblank_callback_t cb,
|
||||||
|
void *user_data);
|
||||||
|
struct vblank_scheduler *
|
||||||
|
vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c,
|
||||||
|
xcb_window_t target_window, enum vblank_scheduler_type type);
|
||||||
|
void vblank_scheduler_free(struct vblank_scheduler *);
|
||||||
|
|
||||||
|
bool vblank_handle_x_events(struct vblank_scheduler *self);
|
||||||
20
src/vsync.c
20
src/vsync.c
@@ -77,31 +77,35 @@ static bool vsync_drm_init(session_t *ps) {
|
|||||||
* @return true for success, false otherwise
|
* @return true for success, false otherwise
|
||||||
*/
|
*/
|
||||||
static bool vsync_opengl_init(session_t *ps) {
|
static bool vsync_opengl_init(session_t *ps) {
|
||||||
if (!ensure_glx_context(ps))
|
if (!ensure_glx_context(ps)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return glxext.has_GLX_SGI_video_sync;
|
return glxext.has_GLX_SGI_video_sync;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool vsync_opengl_oml_init(session_t *ps) {
|
static bool vsync_opengl_oml_init(session_t *ps) {
|
||||||
if (!ensure_glx_context(ps))
|
if (!ensure_glx_context(ps)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return glxext.has_GLX_OML_sync_control;
|
return glxext.has_GLX_OML_sync_control;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool vsync_opengl_swc_swap_interval(session_t *ps, int interval) {
|
static inline bool vsync_opengl_swc_swap_interval(session_t *ps, int interval) {
|
||||||
if (glxext.has_GLX_MESA_swap_control)
|
if (glxext.has_GLX_MESA_swap_control) {
|
||||||
return glXSwapIntervalMESA((uint)interval) == 0;
|
return glXSwapIntervalMESA((uint)interval) == 0;
|
||||||
else if (glxext.has_GLX_SGI_swap_control)
|
}
|
||||||
|
if (glxext.has_GLX_SGI_swap_control) {
|
||||||
return glXSwapIntervalSGI(interval) == 0;
|
return glXSwapIntervalSGI(interval) == 0;
|
||||||
else if (glxext.has_GLX_EXT_swap_control) {
|
}
|
||||||
|
if (glxext.has_GLX_EXT_swap_control) {
|
||||||
GLXDrawable d = glXGetCurrentDrawable();
|
GLXDrawable d = glXGetCurrentDrawable();
|
||||||
if (d == None) {
|
if (d == None) {
|
||||||
// We don't have a context??
|
// We don't have a context??
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
glXSwapIntervalEXT(ps->dpy, glXGetCurrentDrawable(), interval);
|
glXSwapIntervalEXT(ps->c.dpy, glXGetCurrentDrawable(), interval);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -140,8 +144,8 @@ static int vsync_opengl_wait(session_t *ps attr_unused) {
|
|||||||
static int vsync_opengl_oml_wait(session_t *ps) {
|
static int vsync_opengl_oml_wait(session_t *ps) {
|
||||||
int64_t ust = 0, msc = 0, sbc = 0;
|
int64_t ust = 0, msc = 0, sbc = 0;
|
||||||
|
|
||||||
glXGetSyncValuesOML(ps->dpy, ps->reg_win, &ust, &msc, &sbc);
|
glXGetSyncValuesOML(ps->c.dpy, ps->reg_win, &ust, &msc, &sbc);
|
||||||
glXWaitForMscOML(ps->dpy, ps->reg_win, 0, 2, (msc + 1) % 2, &ust, &msc, &sbc);
|
glXWaitForMscOML(ps->c.dpy, ps->reg_win, 0, 2, (msc + 1) % 2, &ust, &msc, &sbc);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
424
src/win.c
424
src/win.c
@@ -14,7 +14,6 @@
|
|||||||
#include <xcb/render.h>
|
#include <xcb/render.h>
|
||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
#include <xcb/xcb_renderutil.h>
|
#include <xcb/xcb_renderutil.h>
|
||||||
#include <xcb/xinerama.h>
|
|
||||||
|
|
||||||
#include "atom.h"
|
#include "atom.h"
|
||||||
#include "backend/backend.h"
|
#include "backend/backend.h"
|
||||||
@@ -74,6 +73,11 @@ static void
|
|||||||
win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t client);
|
win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t client);
|
||||||
static void win_update_prop_shadow_raw(session_t *ps, struct managed_win *w);
|
static void win_update_prop_shadow_raw(session_t *ps, struct managed_win *w);
|
||||||
static void win_update_prop_shadow(session_t *ps, struct managed_win *w);
|
static void win_update_prop_shadow(session_t *ps, struct managed_win *w);
|
||||||
|
/**
|
||||||
|
* Update window EWMH fullscreen state.
|
||||||
|
*/
|
||||||
|
bool win_update_prop_fullscreen(struct x_connection *c, const struct atom *atoms,
|
||||||
|
struct managed_win *w);
|
||||||
/**
|
/**
|
||||||
* Update leader of a window.
|
* Update leader of a window.
|
||||||
*/
|
*/
|
||||||
@@ -135,10 +139,10 @@ static inline bool attr_pure win_is_real_visible(const struct managed_win *w) {
|
|||||||
* Update focused state of a window.
|
* Update focused state of a window.
|
||||||
*/
|
*/
|
||||||
static void win_update_focused(session_t *ps, struct managed_win *w) {
|
static void win_update_focused(session_t *ps, struct managed_win *w) {
|
||||||
if (UNSET != w->focused_force) {
|
if (w->focused_force != UNSET) {
|
||||||
w->focused = w->focused_force;
|
w->focused = w->focused_force;
|
||||||
} else {
|
} else {
|
||||||
w->focused = win_is_focused_raw(ps, w);
|
w->focused = win_is_focused_raw(w);
|
||||||
|
|
||||||
// Use wintype_focus, and treat WM windows and override-redirected
|
// Use wintype_focus, and treat WM windows and override-redirected
|
||||||
// windows specially
|
// windows specially
|
||||||
@@ -206,7 +210,7 @@ static inline bool group_is_focused(session_t *ps, xcb_window_t leader) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto mw = (struct managed_win *)w;
|
auto mw = (struct managed_win *)w;
|
||||||
if (win_get_leader(ps, mw) == leader && win_is_focused_raw(ps, mw)) {
|
if (win_get_leader(ps, mw) == leader && win_is_focused_raw(mw)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,7 +336,7 @@ static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w
|
|||||||
assert(!w->win_image);
|
assert(!w->win_image);
|
||||||
auto pixmap = x_new_id(b->c);
|
auto pixmap = x_new_id(b->c);
|
||||||
auto e = xcb_request_check(
|
auto e = xcb_request_check(
|
||||||
b->c, xcb_composite_name_window_pixmap_checked(b->c, w->base.id, pixmap));
|
b->c->c, xcb_composite_name_window_pixmap_checked(b->c->c, w->base.id, pixmap));
|
||||||
if (e) {
|
if (e) {
|
||||||
log_error("Failed to get named pixmap for window %#010x(%s)", w->base.id,
|
log_error("Failed to get named pixmap for window %#010x(%s)", w->base.id,
|
||||||
w->name);
|
w->name);
|
||||||
@@ -376,7 +380,11 @@ bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color
|
|||||||
b->ops->shadow_from_mask == NULL) {
|
b->ops->shadow_from_mask == NULL) {
|
||||||
w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, sctx, c);
|
w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, sctx, c);
|
||||||
} else {
|
} else {
|
||||||
win_bind_mask(b, w);
|
if (!w->mask_image) {
|
||||||
|
// It's possible we already allocated a mask because of background
|
||||||
|
// blur
|
||||||
|
win_bind_mask(b, w);
|
||||||
|
}
|
||||||
w->shadow_image = b->ops->shadow_from_mask(b, w->mask_image, sctx, c);
|
w->shadow_image = b->ops->shadow_from_mask(b, w->mask_image, sctx, c);
|
||||||
}
|
}
|
||||||
if (!w->shadow_image) {
|
if (!w->shadow_image) {
|
||||||
@@ -462,6 +470,12 @@ static void win_update_properties(session_t *ps, struct managed_win *w) {
|
|||||||
win_update_prop_shadow(ps, w);
|
win_update_prop_shadow(ps, w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_WM_STATE)) {
|
||||||
|
if (win_update_prop_fullscreen(&ps->c, ps->atoms, w)) {
|
||||||
|
win_set_flags(w, WIN_FLAGS_FACTOR_CHANGED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (win_fetch_and_unset_property_stale(w, ps->atoms->aWM_CLIENT_LEADER) ||
|
if (win_fetch_and_unset_property_stale(w, ps->atoms->aWM_CLIENT_LEADER) ||
|
||||||
win_fetch_and_unset_property_stale(w, ps->atoms->aWM_TRANSIENT_FOR)) {
|
win_fetch_and_unset_property_stale(w, ps->atoms->aWM_TRANSIENT_FOR)) {
|
||||||
win_update_leader(ps, w);
|
win_update_leader(ps, w);
|
||||||
@@ -472,20 +486,29 @@ static void win_update_properties(session_t *ps, struct managed_win *w) {
|
|||||||
|
|
||||||
static void init_animation(session_t *ps, struct managed_win *w) {
|
static void init_animation(session_t *ps, struct managed_win *w) {
|
||||||
CLEAR_MASK(w->animation_is_tag)
|
CLEAR_MASK(w->animation_is_tag)
|
||||||
|
static int32_t randr_mon_center_x, randr_mon_center_y;
|
||||||
|
if (w->randr_monitor != -1) {
|
||||||
|
auto e = pixman_region32_extents(&ps->monitors.regions[w->randr_monitor]);
|
||||||
|
randr_mon_center_x = (e->x2 + e->x1) / 2, randr_mon_center_y = (e->y2 + e->y1) / 2;
|
||||||
|
} else {
|
||||||
|
randr_mon_center_x = ps->root_width / 2, randr_mon_center_y = ps->root_height / 2;
|
||||||
|
}
|
||||||
static double *anim_x, *anim_y, *anim_w, *anim_h;
|
static double *anim_x, *anim_y, *anim_w, *anim_h;
|
||||||
enum open_window_animation animation;
|
enum open_window_animation animation;
|
||||||
if (ps->o.wintype_option[w->window_type].animation != OPEN_WINDOW_ANIMATION_INVALID
|
|
||||||
&& !w->dwm_mask) {
|
|
||||||
animation = ps->o.wintype_option[w->window_type].animation;
|
animation = ps->o.animation_for_open_window;
|
||||||
}
|
|
||||||
else
|
|
||||||
animation = ps->o.animation_for_open_window;
|
|
||||||
|
|
||||||
if (w->window_type != WINTYPE_TOOLTIP &&
|
if (w->window_type != WINTYPE_TOOLTIP &&
|
||||||
wid_has_prop(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR)) {
|
wid_has_prop(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR)) {
|
||||||
animation = ps->o.animation_for_transient_window;
|
animation = ps->o.animation_for_transient_window;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps->o.wintype_option[w->window_type].animation != OPEN_WINDOW_ANIMATION_INVALID
|
||||||
|
&& !w->dwm_mask) {
|
||||||
|
animation = ps->o.wintype_option[w->window_type].animation;
|
||||||
|
}
|
||||||
|
|
||||||
anim_x = &w->animation_center_x, anim_y = &w->animation_center_y;
|
anim_x = &w->animation_center_x, anim_y = &w->animation_center_y;
|
||||||
anim_w = &w->animation_w, anim_h = &w->animation_h;
|
anim_w = &w->animation_w, anim_h = &w->animation_h;
|
||||||
|
|
||||||
@@ -532,9 +555,9 @@ static void init_animation(session_t *ps, struct managed_win *w) {
|
|||||||
sqrt(ps->root_width * ps->root_width + ps->root_height * ps->root_height);
|
sqrt(ps->root_width * ps->root_width + ps->root_height * ps->root_height);
|
||||||
|
|
||||||
// Set animation
|
// Set animation
|
||||||
*anim_x = ps->selmon_center_x + radius * cos(angle);
|
*anim_x = randr_mon_center_x + radius * cos(angle);
|
||||||
*anim_y = ps->selmon_center_y + radius * sin(angle);
|
*anim_y = randr_mon_center_y + radius * sin(angle);
|
||||||
*anim_w = 0;
|
*anim_w = 0;
|
||||||
*anim_h = 0;
|
*anim_h = 0;
|
||||||
break;
|
break;
|
||||||
case OPEN_WINDOW_ANIMATION_SLIDE_UP: // Slide up the image, without changing its location
|
case OPEN_WINDOW_ANIMATION_SLIDE_UP: // Slide up the image, without changing its location
|
||||||
@@ -568,7 +591,7 @@ static void init_animation(session_t *ps, struct managed_win *w) {
|
|||||||
*anim_h = w->pending_g.height;
|
*anim_h = w->pending_g.height;
|
||||||
break;
|
break;
|
||||||
case OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER:
|
case OPEN_WINDOW_ANIMATION_SLIDE_IN_CENTER:
|
||||||
*anim_x = ps->selmon_center_x;
|
*anim_x = randr_mon_center_x;
|
||||||
*anim_y = w->g.y + w->pending_g.height * 0.5;
|
*anim_y = w->g.y + w->pending_g.height * 0.5;
|
||||||
*anim_w = w->pending_g.width;
|
*anim_w = w->pending_g.width;
|
||||||
*anim_h = w->pending_g.height;
|
*anim_h = w->pending_g.height;
|
||||||
@@ -580,7 +603,7 @@ static void init_animation(session_t *ps, struct managed_win *w) {
|
|||||||
w->animation_dest_h = w->pending_g.height;
|
w->animation_dest_h = w->pending_g.height;
|
||||||
break;
|
break;
|
||||||
case OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER:
|
case OPEN_WINDOW_ANIMATION_SLIDE_OUT_CENTER:
|
||||||
w->animation_dest_center_x = ps->selmon_center_x;
|
w->animation_dest_center_x = randr_mon_center_x;
|
||||||
w->animation_dest_center_y = w->pending_g.y;
|
w->animation_dest_center_y = w->pending_g.y;
|
||||||
w->animation_dest_w = w->pending_g.width;
|
w->animation_dest_w = w->pending_g.width;
|
||||||
w->animation_dest_h = w->pending_g.height;
|
w->animation_dest_h = w->pending_g.height;
|
||||||
@@ -597,8 +620,8 @@ static void init_animation(session_t *ps, struct managed_win *w) {
|
|||||||
*anim_h = 0;
|
*anim_h = 0;
|
||||||
break;
|
break;
|
||||||
case OPEN_WINDOW_ANIMATION_MINIMIZE:
|
case OPEN_WINDOW_ANIMATION_MINIMIZE:
|
||||||
*anim_x = ps->selmon_center_x;
|
*anim_x = randr_mon_center_x;
|
||||||
*anim_y = ps->selmon_center_y;
|
*anim_y = randr_mon_center_y;
|
||||||
*anim_w = 0;
|
*anim_w = 0;
|
||||||
*anim_h = 0;
|
*anim_h = 0;
|
||||||
break;
|
break;
|
||||||
@@ -633,8 +656,9 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
|
|||||||
// Whether the window was visible before we process the mapped flag. i.e.
|
// Whether the window was visible before we process the mapped flag. i.e.
|
||||||
// is the window just mapped.
|
// is the window just mapped.
|
||||||
bool was_visible = win_is_real_visible(w);
|
bool was_visible = win_is_real_visible(w);
|
||||||
log_trace("Processing flags for window %#010x (%s), was visible: %d", w->base.id,
|
log_trace("Processing flags for window %#010x (%s), was visible: %d, flags: "
|
||||||
w->name, was_visible);
|
"%#" PRIx64,
|
||||||
|
w->base.id, w->name, was_visible, w->flags);
|
||||||
|
|
||||||
if (win_check_flags_all(w, WIN_FLAGS_MAPPED)) {
|
if (win_check_flags_all(w, WIN_FLAGS_MAPPED)) {
|
||||||
map_win_start(ps, w);
|
map_win_start(ps, w);
|
||||||
@@ -672,7 +696,6 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
|
|||||||
|
|
||||||
// Determine if a window should animate
|
// Determine if a window should animate
|
||||||
if (win_should_animate(ps, w)) {
|
if (win_should_animate(ps, w)) {
|
||||||
win_update_bounding_shape(ps, w);
|
|
||||||
if (w->pending_g.y < 0 && w->g.y > 0 && abs(w->pending_g.y - w->g.y) >= w->pending_g.height)
|
if (w->pending_g.y < 0 && w->g.y > 0 && abs(w->pending_g.y - w->g.y) >= w->pending_g.height)
|
||||||
w->dwm_mask = ANIM_PREV_TAG;
|
w->dwm_mask = ANIM_PREV_TAG;
|
||||||
else if (w->pending_g.y > 0 && w->g.y < 0 && abs(w->pending_g.y - w->g.y) >= w->pending_g.height)
|
else if (w->pending_g.y > 0 && w->g.y < 0 && abs(w->pending_g.y - w->g.y) >= w->pending_g.height)
|
||||||
@@ -743,19 +766,22 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
|
|||||||
w->g = w->pending_g;
|
w->g = w->pending_g;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) {
|
// Whether a window is fullscreen changes based on its geometry
|
||||||
win_on_win_size_change(ps, w);
|
win_update_is_fullscreen(ps, w);
|
||||||
win_update_bounding_shape(ps, w);
|
|
||||||
damaged = true;
|
if (win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) {
|
||||||
win_clear_flags(w, WIN_FLAGS_SIZE_STALE);
|
win_on_win_size_change(ps, w);
|
||||||
}
|
win_update_bounding_shape(ps, w);
|
||||||
|
damaged = true;
|
||||||
|
win_clear_flags(w, WIN_FLAGS_SIZE_STALE);
|
||||||
|
}
|
||||||
|
|
||||||
if (win_check_flags_all(w, WIN_FLAGS_POSITION_STALE)) {
|
if (win_check_flags_all(w, WIN_FLAGS_POSITION_STALE)) {
|
||||||
damaged = true;
|
damaged = true;
|
||||||
win_clear_flags(w, WIN_FLAGS_POSITION_STALE);
|
win_clear_flags(w, WIN_FLAGS_POSITION_STALE);
|
||||||
}
|
}
|
||||||
|
|
||||||
win_update_screen(ps->xinerama_nscrs, ps->xinerama_scr_regs, w);
|
win_update_monitor(&ps->monitors, w);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (win_check_flags_all(w, WIN_FLAGS_PROPERTY_STALE)) {
|
if (win_check_flags_all(w, WIN_FLAGS_PROPERTY_STALE)) {
|
||||||
@@ -878,12 +904,14 @@ int win_update_name(session_t *ps, struct managed_win *w) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(wid_get_text_prop(ps, w->client_win, ps->atoms->a_NET_WM_NAME, &strlst, &nstr))) {
|
if (!(wid_get_text_prop(&ps->c, ps->atoms, w->client_win,
|
||||||
|
ps->atoms->a_NET_WM_NAME, &strlst, &nstr))) {
|
||||||
log_debug("(%#010x): _NET_WM_NAME unset, falling back to "
|
log_debug("(%#010x): _NET_WM_NAME unset, falling back to "
|
||||||
"WM_NAME.",
|
"WM_NAME.",
|
||||||
w->client_win);
|
w->client_win);
|
||||||
|
|
||||||
if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_NAME, &strlst, &nstr)) {
|
if (!wid_get_text_prop(&ps->c, ps->atoms, w->client_win,
|
||||||
|
ps->atoms->aWM_NAME, &strlst, &nstr)) {
|
||||||
log_debug("Unsetting window name for %#010x", w->client_win);
|
log_debug("Unsetting window name for %#010x", w->client_win);
|
||||||
free(w->name);
|
free(w->name);
|
||||||
w->name = NULL;
|
w->name = NULL;
|
||||||
@@ -910,7 +938,8 @@ static int win_update_role(session_t *ps, struct managed_win *w) {
|
|||||||
char **strlst = NULL;
|
char **strlst = NULL;
|
||||||
int nstr = 0;
|
int nstr = 0;
|
||||||
|
|
||||||
if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_WINDOW_ROLE, &strlst, &nstr)) {
|
if (!wid_get_text_prop(&ps->c, ps->atoms, w->client_win,
|
||||||
|
ps->atoms->aWM_WINDOW_ROLE, &strlst, &nstr)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -938,7 +967,7 @@ static inline bool win_bounding_shaped(const session_t *ps, xcb_window_t wid) {
|
|||||||
Bool bounding_shaped;
|
Bool bounding_shaped;
|
||||||
|
|
||||||
reply = xcb_shape_query_extents_reply(
|
reply = xcb_shape_query_extents_reply(
|
||||||
ps->c, xcb_shape_query_extents(ps->c, wid), NULL);
|
ps->c.c, xcb_shape_query_extents(ps->c.c, wid), NULL);
|
||||||
bounding_shaped = reply && reply->bounding_shaped;
|
bounding_shaped = reply && reply->bounding_shaped;
|
||||||
free(reply);
|
free(reply);
|
||||||
|
|
||||||
@@ -950,7 +979,7 @@ static inline bool win_bounding_shaped(const session_t *ps, xcb_window_t wid) {
|
|||||||
|
|
||||||
static wintype_t wid_get_prop_wintype(session_t *ps, xcb_window_t wid) {
|
static wintype_t wid_get_prop_wintype(session_t *ps, xcb_window_t wid) {
|
||||||
winprop_t prop =
|
winprop_t prop =
|
||||||
x_get_prop(ps->c, wid, ps->atoms->a_NET_WM_WINDOW_TYPE, 32L, XCB_ATOM_ATOM, 32);
|
x_get_prop(&ps->c, wid, ps->atoms->a_NET_WM_WINDOW_TYPE, 32L, XCB_ATOM_ATOM, 32);
|
||||||
|
|
||||||
for (unsigned i = 0; i < prop.nitems; ++i) {
|
for (unsigned i = 0; i < prop.nitems; ++i) {
|
||||||
for (wintype_t j = 1; j < NUM_WINTYPES; ++j) {
|
for (wintype_t j = 1; j < NUM_WINTYPES; ++j) {
|
||||||
@@ -971,7 +1000,7 @@ wid_get_opacity_prop(session_t *ps, xcb_window_t wid, opacity_t def, opacity_t *
|
|||||||
bool ret = false;
|
bool ret = false;
|
||||||
*out = def;
|
*out = def;
|
||||||
|
|
||||||
winprop_t prop = x_get_prop(ps->c, wid, ps->atoms->a_NET_WM_WINDOW_OPACITY, 1L,
|
winprop_t prop = x_get_prop(&ps->c, wid, ps->atoms->a_NET_WM_WINDOW_OPACITY, 1L,
|
||||||
XCB_ATOM_CARDINAL, 32);
|
XCB_ATOM_CARDINAL, 32);
|
||||||
|
|
||||||
if (prop.nitems) {
|
if (prop.nitems) {
|
||||||
@@ -1067,11 +1096,12 @@ double win_calc_opacity_target(session_t *ps, const struct managed_win *w) {
|
|||||||
} else {
|
} else {
|
||||||
// Respect active_opacity only when the window is physically
|
// Respect active_opacity only when the window is physically
|
||||||
// focused
|
// focused
|
||||||
if (win_is_focused_raw(ps, w))
|
if (win_is_focused_raw(w)) {
|
||||||
opacity = ps->o.active_opacity;
|
opacity = ps->o.active_opacity;
|
||||||
else if (!w->focused)
|
} else if (!w->focused) {
|
||||||
// Respect inactive_opacity in some cases
|
// Respect inactive_opacity in some cases
|
||||||
opacity = ps->o.inactive_opacity;
|
opacity = ps->o.inactive_opacity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// respect inactive override
|
// respect inactive override
|
||||||
@@ -1093,9 +1123,8 @@ bool win_should_dim(session_t *ps, const struct managed_win *w) {
|
|||||||
|
|
||||||
if (ps->o.inactive_dim > 0 && !(w->focused)) {
|
if (ps->o.inactive_dim > 0 && !(w->focused)) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1145,7 +1174,7 @@ bool win_should_animate(session_t *ps, const struct managed_win *w) {
|
|||||||
* The property must be set on the outermost window, usually the WM frame.
|
* The property must be set on the outermost window, usually the WM frame.
|
||||||
*/
|
*/
|
||||||
void win_update_prop_shadow_raw(session_t *ps, struct managed_win *w) {
|
void win_update_prop_shadow_raw(session_t *ps, struct managed_win *w) {
|
||||||
winprop_t prop = x_get_prop(ps->c, w->base.id, ps->atoms->a_COMPTON_SHADOW, 1,
|
winprop_t prop = x_get_prop(&ps->c, w->base.id, ps->atoms->a_COMPTON_SHADOW, 1,
|
||||||
XCB_ATOM_CARDINAL, 32);
|
XCB_ATOM_CARDINAL, 32);
|
||||||
|
|
||||||
if (!prop.nitems) {
|
if (!prop.nitems) {
|
||||||
@@ -1211,7 +1240,7 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new
|
|||||||
|
|
||||||
// Delayed update of shadow image
|
// Delayed update of shadow image
|
||||||
// By setting WIN_FLAGS_SHADOW_STALE, we ask win_process_flags to
|
// By setting WIN_FLAGS_SHADOW_STALE, we ask win_process_flags to
|
||||||
// re-create or release the shaodw in based on whether w->shadow
|
// re-create or release the shadow in based on whether w->shadow
|
||||||
// is set.
|
// is set.
|
||||||
win_set_flags(w, WIN_FLAGS_SHADOW_STALE);
|
win_set_flags(w, WIN_FLAGS_SHADOW_STALE);
|
||||||
|
|
||||||
@@ -1268,6 +1297,30 @@ void win_update_prop_shadow(session_t *ps, struct managed_win *w) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update window EWMH fullscreen state.
|
||||||
|
*/
|
||||||
|
bool win_update_prop_fullscreen(struct x_connection *c, const struct atom *atoms,
|
||||||
|
struct managed_win *w) {
|
||||||
|
auto prop = x_get_prop(c, w->client_win, atoms->a_NET_WM_STATE, 12, XCB_ATOM_ATOM, 0);
|
||||||
|
if (!prop.nitems) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_fullscreen = false;
|
||||||
|
for (uint32_t i = 0; i < prop.nitems; i++) {
|
||||||
|
if (prop.atom[i] == atoms->a_NET_WM_STATE_FULLSCREEN) {
|
||||||
|
is_fullscreen = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free_winprop(&prop);
|
||||||
|
|
||||||
|
bool changed = w->is_ewmh_fullscreen != is_fullscreen;
|
||||||
|
w->is_ewmh_fullscreen = is_fullscreen;
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
static void win_determine_clip_shadow_above(session_t *ps, struct managed_win *w) {
|
static void win_determine_clip_shadow_above(session_t *ps, struct managed_win *w) {
|
||||||
bool should_crop = (ps->o.wintype_option[w->window_type].clip_shadow_above ||
|
bool should_crop = (ps->o.wintype_option[w->window_type].clip_shadow_above ||
|
||||||
c2_match(ps, w, ps->o.shadow_clip_list, NULL));
|
c2_match(ps, w, ps->o.shadow_clip_list, NULL));
|
||||||
@@ -1343,8 +1396,9 @@ void win_set_shadow_force(session_t *ps, struct managed_win *w, switch_t val) {
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
win_set_blur_background(session_t *ps, struct managed_win *w, bool blur_background_new) {
|
win_set_blur_background(session_t *ps, struct managed_win *w, bool blur_background_new) {
|
||||||
if (w->blur_background == blur_background_new)
|
if (w->blur_background == blur_background_new) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
w->blur_background = blur_background_new;
|
w->blur_background = blur_background_new;
|
||||||
|
|
||||||
@@ -1395,18 +1449,29 @@ static void win_determine_blur_background(session_t *ps, struct managed_win *w)
|
|||||||
* Determine if a window should have rounded corners.
|
* Determine if a window should have rounded corners.
|
||||||
*/
|
*/
|
||||||
static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) {
|
static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) {
|
||||||
if (ps->o.corner_radius == 0) {
|
void *radius_override = NULL;
|
||||||
|
if (c2_match(ps, w, ps->o.corner_radius_rules, &radius_override)) {
|
||||||
|
log_debug("Matched corner rule! %d", w->corner_radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps->o.corner_radius == 0 && !radius_override) {
|
||||||
w->corner_radius = 0;
|
w->corner_radius = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't round full screen windows & excluded windows
|
// Don't round full screen windows & excluded windows,
|
||||||
if ((w && win_is_fullscreen(ps, w)) ||
|
// unless we find a corner override in corner_radius_rules
|
||||||
c2_match(ps, w, ps->o.rounded_corners_blacklist, NULL)) {
|
if (!radius_override && ((w && w->is_fullscreen) ||
|
||||||
|
c2_match(ps, w, ps->o.rounded_corners_blacklist, NULL))) {
|
||||||
w->corner_radius = 0;
|
w->corner_radius = 0;
|
||||||
log_debug("Not rounding corners for window %#010x", w->base.id);
|
log_debug("Not rounding corners for window %#010x", w->base.id);
|
||||||
} else {
|
} else {
|
||||||
w->corner_radius = ps->o.corner_radius;
|
if (radius_override) {
|
||||||
|
w->corner_radius = (int)(long)radius_override;
|
||||||
|
} else {
|
||||||
|
w->corner_radius = ps->o.corner_radius;
|
||||||
|
}
|
||||||
|
|
||||||
log_debug("Rounding corners for window %#010x", w->base.id);
|
log_debug("Rounding corners for window %#010x", w->base.id);
|
||||||
// Initialize the border color to an invalid value
|
// Initialize the border color to an invalid value
|
||||||
w->border_col[0] = w->border_col[1] = w->border_col[2] =
|
w->border_col[0] = w->border_col[1] = w->border_col[2] =
|
||||||
@@ -1463,14 +1528,13 @@ void win_update_opacity_rule(session_t *ps, struct managed_win *w) {
|
|||||||
*/
|
*/
|
||||||
void win_on_factor_change(session_t *ps, struct managed_win *w) {
|
void win_on_factor_change(session_t *ps, struct managed_win *w) {
|
||||||
log_debug("Window %#010x (%s) factor change", w->base.id, w->name);
|
log_debug("Window %#010x (%s) factor change", w->base.id, w->name);
|
||||||
// Focus needs to be updated first, as other rules might depend on the
|
// Focus and is_fullscreen needs to be updated first, as other rules might depend
|
||||||
// focused state of the window
|
// on the focused state of the window
|
||||||
win_update_focused(ps, w);
|
win_update_focused(ps, w);
|
||||||
|
win_update_is_fullscreen(ps, w);
|
||||||
|
|
||||||
if (w->animation_progress > 0.9999 || w->animation_progress < 0.0001) {
|
win_determine_shadow(ps, w);
|
||||||
win_determine_shadow(ps, w);
|
win_determine_clip_shadow_above(ps, w);
|
||||||
win_determine_clip_shadow_above(ps, w);
|
|
||||||
}
|
|
||||||
win_determine_invert_color(ps, w);
|
win_determine_invert_color(ps, w);
|
||||||
win_determine_blur_background(ps, w);
|
win_determine_blur_background(ps, w);
|
||||||
win_determine_rounded_corners(ps, w);
|
win_determine_rounded_corners(ps, w);
|
||||||
@@ -1500,6 +1564,10 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) {
|
|||||||
* Update cache data in struct _win that depends on window size.
|
* Update cache data in struct _win that depends on window size.
|
||||||
*/
|
*/
|
||||||
void win_on_win_size_change(session_t *ps, struct managed_win *w) {
|
void win_on_win_size_change(session_t *ps, struct managed_win *w) {
|
||||||
|
log_trace("Window %#010x (%s) size changed, was %dx%d, now %dx%d", w->base.id,
|
||||||
|
w->name, w->widthb, w->heightb, w->g.width + w->g.border_width * 2,
|
||||||
|
w->g.height + w->g.border_width * 2);
|
||||||
|
|
||||||
w->widthb = w->g.width + w->g.border_width * 2;
|
w->widthb = w->g.width + w->g.border_width * 2;
|
||||||
w->heightb = w->g.height + w->g.border_width * 2;
|
w->heightb = w->g.height + w->g.border_width * 2;
|
||||||
w->shadow_dx = ps->o.shadow_offset_x;
|
w->shadow_dx = ps->o.shadow_offset_x;
|
||||||
@@ -1513,8 +1581,12 @@ void win_on_win_size_change(session_t *ps, struct managed_win *w) {
|
|||||||
w->state != WSTATE_UNMAPPING);
|
w->state != WSTATE_UNMAPPING);
|
||||||
|
|
||||||
// Invalidate the shadow we built
|
// Invalidate the shadow we built
|
||||||
win_set_flags(w, WIN_FLAGS_IMAGES_STALE);
|
// Do not set flags if window is unmapping and animation is running
|
||||||
win_release_mask(ps->backend_data, w);
|
if (w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING &&
|
||||||
|
w->state != WSTATE_UNMAPPING) {
|
||||||
|
win_set_flags(w, WIN_FLAGS_IMAGES_STALE);
|
||||||
|
win_release_mask(ps->backend_data, w);
|
||||||
|
}
|
||||||
ps->pending_updates = true;
|
ps->pending_updates = true;
|
||||||
free_paint(ps, &w->shadow_paint);
|
free_paint(ps, &w->shadow_paint);
|
||||||
}
|
}
|
||||||
@@ -1533,10 +1605,11 @@ void win_update_wintype(session_t *ps, struct managed_win *w) {
|
|||||||
// _NET_WM_WINDOW_TYPE_NORMAL, otherwise as _NET_WM_WINDOW_TYPE_DIALOG.
|
// _NET_WM_WINDOW_TYPE_NORMAL, otherwise as _NET_WM_WINDOW_TYPE_DIALOG.
|
||||||
if (WINTYPE_UNKNOWN == w->window_type) {
|
if (WINTYPE_UNKNOWN == w->window_type) {
|
||||||
if (w->a.override_redirect ||
|
if (w->a.override_redirect ||
|
||||||
!wid_has_prop(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR))
|
!wid_has_prop(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR)) {
|
||||||
w->window_type = WINTYPE_NORMAL;
|
w->window_type = WINTYPE_NORMAL;
|
||||||
else
|
} else {
|
||||||
w->window_type = WINTYPE_DIALOG;
|
w->window_type = WINTYPE_DIALOG;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (w->window_type != wtype_old) {
|
if (w->window_type != wtype_old) {
|
||||||
@@ -1561,9 +1634,9 @@ void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto e = xcb_request_check(
|
auto e = xcb_request_check(
|
||||||
ps->c, xcb_change_window_attributes_checked(
|
ps->c.c, xcb_change_window_attributes_checked(
|
||||||
ps->c, client, XCB_CW_EVENT_MASK,
|
ps->c.c, client, XCB_CW_EVENT_MASK,
|
||||||
(const uint32_t[]){determine_evmask(ps, client, WIN_EVMODE_CLIENT)}));
|
(const uint32_t[]){determine_evmask(ps, client, WIN_EVMODE_CLIENT)}));
|
||||||
if (e) {
|
if (e) {
|
||||||
log_error("Failed to change event mask of window %#010x", client);
|
log_error("Failed to change event mask of window %#010x", client);
|
||||||
free(e);
|
free(e);
|
||||||
@@ -1588,13 +1661,13 @@ void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client)
|
|||||||
win_on_factor_change(ps, w);
|
win_on_factor_change(ps, w);
|
||||||
|
|
||||||
auto r = xcb_get_window_attributes_reply(
|
auto r = xcb_get_window_attributes_reply(
|
||||||
ps->c, xcb_get_window_attributes(ps->c, w->client_win), &e);
|
ps->c.c, xcb_get_window_attributes(ps->c.c, w->client_win), &e);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
log_error_x_error(e, "Failed to get client window attributes");
|
log_error_x_error(e, "Failed to get client window attributes");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
w->client_pictfmt = x_get_pictform_for_visual(ps->c, r->visual);
|
w->client_pictfmt = x_get_pictform_for_visual(&ps->c, r->visual);
|
||||||
free(r);
|
free(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1613,7 +1686,7 @@ void win_unmark_client(session_t *ps, struct managed_win *w) {
|
|||||||
|
|
||||||
// Recheck event mask
|
// Recheck event mask
|
||||||
xcb_change_window_attributes(
|
xcb_change_window_attributes(
|
||||||
ps->c, client, XCB_CW_EVENT_MASK,
|
ps->c.c, client, XCB_CW_EVENT_MASK,
|
||||||
(const uint32_t[]){determine_evmask(ps, client, WIN_EVMODE_UNKNOWN)});
|
(const uint32_t[]){determine_evmask(ps, client, WIN_EVMODE_UNKNOWN)});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1626,7 +1699,7 @@ static xcb_window_t find_client_win(session_t *ps, xcb_window_t w) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
xcb_query_tree_reply_t *reply =
|
xcb_query_tree_reply_t *reply =
|
||||||
xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, w), NULL);
|
xcb_query_tree_reply(ps->c.c, xcb_query_tree(ps->c.c, w), NULL);
|
||||||
if (!reply) {
|
if (!reply) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1700,7 +1773,7 @@ void free_win_res(session_t *ps, struct managed_win *w) {
|
|||||||
|
|
||||||
pixman_region32_fini(&w->bounding_shape);
|
pixman_region32_fini(&w->bounding_shape);
|
||||||
// BadDamage may be thrown if the window is destroyed
|
// BadDamage may be thrown if the window is destroyed
|
||||||
set_ignore_cookie(ps, xcb_damage_destroy(ps->c, w->damage));
|
set_ignore_cookie(&ps->c, xcb_damage_destroy(ps->c.c, w->damage));
|
||||||
rc_region_unref(&w->reg_ignore);
|
rc_region_unref(&w->reg_ignore);
|
||||||
free(w->name);
|
free(w->name);
|
||||||
free(w->class_instance);
|
free(w->class_instance);
|
||||||
@@ -1749,12 +1822,11 @@ struct win *add_win_above(session_t *ps, xcb_window_t id, xcb_window_t below) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return add_win_top(ps, id);
|
return add_win_top(ps, id);
|
||||||
} else {
|
|
||||||
// we found something from the hash table, so if the stack is
|
|
||||||
// empty, we are in an inconsistent state.
|
|
||||||
assert(!list_is_empty(&ps->window_stack));
|
|
||||||
return add_win(ps, id, w->stack_neighbour.prev);
|
|
||||||
}
|
}
|
||||||
|
// we found something from the hash table, so if the stack is
|
||||||
|
// empty, we are in an inconsistent state.
|
||||||
|
assert(!list_is_empty(&ps->window_stack));
|
||||||
|
return add_win(ps, id, w->stack_neighbour.prev);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query the Xorg for information about window `win`
|
/// Query the Xorg for information about window `win`
|
||||||
@@ -1781,7 +1853,7 @@ struct win *fill_win(session_t *ps, struct win *w) {
|
|||||||
.animation_velocity_y = 0.0, // updated by window geometry changes
|
.animation_velocity_y = 0.0, // updated by window geometry changes
|
||||||
.animation_velocity_w = 0.0, // updated by window geometry changes
|
.animation_velocity_w = 0.0, // updated by window geometry changes
|
||||||
.animation_velocity_h = 0.0, // updated by window geometry changes
|
.animation_velocity_h = 0.0, // updated by window geometry changes
|
||||||
.animation_progress = 0.0, // updated by window geometry changes
|
.animation_progress = 1.0, // updated by window geometry changes
|
||||||
.animation_inv_og_distance = NAN, // updated by window geometry changes
|
.animation_inv_og_distance = NAN, // updated by window geometry changes
|
||||||
.reg_ignore_valid = false, // set to true when damaged
|
.reg_ignore_valid = false, // set to true when damaged
|
||||||
.flags = WIN_FLAGS_IMAGES_NONE, // updated by property/attributes/etc
|
.flags = WIN_FLAGS_IMAGES_NONE, // updated by property/attributes/etc
|
||||||
@@ -1818,7 +1890,7 @@ struct win *fill_win(session_t *ps, struct win *w) {
|
|||||||
.shadow = false,
|
.shadow = false,
|
||||||
.clip_shadow_above = false,
|
.clip_shadow_above = false,
|
||||||
.fg_shader = NULL,
|
.fg_shader = NULL,
|
||||||
.xinerama_scr = -1,
|
.randr_monitor = -1,
|
||||||
.mode = WMODE_TRANS,
|
.mode = WMODE_TRANS,
|
||||||
.ever_damaged = false,
|
.ever_damaged = false,
|
||||||
.client_win = XCB_NONE,
|
.client_win = XCB_NONE,
|
||||||
@@ -1874,9 +1946,10 @@ struct win *fill_win(session_t *ps, struct win *w) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log_debug("Managing window %#010x", w->id);
|
log_debug("Managing window %#010x", w->id);
|
||||||
xcb_get_window_attributes_cookie_t acookie = xcb_get_window_attributes(ps->c, w->id);
|
xcb_get_window_attributes_cookie_t acookie =
|
||||||
|
xcb_get_window_attributes(ps->c.c, w->id);
|
||||||
xcb_get_window_attributes_reply_t *a =
|
xcb_get_window_attributes_reply_t *a =
|
||||||
xcb_get_window_attributes_reply(ps->c, acookie, NULL);
|
xcb_get_window_attributes_reply(ps->c.c, acookie, NULL);
|
||||||
if (!a || a->map_state == XCB_MAP_STATE_UNVIEWABLE) {
|
if (!a || a->map_state == XCB_MAP_STATE_UNVIEWABLE) {
|
||||||
// Failed to get window attributes or geometry probably means
|
// Failed to get window attributes or geometry probably means
|
||||||
// the window is gone already. Unviewable means the window is
|
// the window is gone already. Unviewable means the window is
|
||||||
@@ -1911,7 +1984,7 @@ struct win *fill_win(session_t *ps, struct win *w) {
|
|||||||
free(a);
|
free(a);
|
||||||
|
|
||||||
xcb_generic_error_t *e;
|
xcb_generic_error_t *e;
|
||||||
auto g = xcb_get_geometry_reply(ps->c, xcb_get_geometry(ps->c, w->id), &e);
|
auto g = xcb_get_geometry_reply(ps->c.c, xcb_get_geometry(ps->c.c, w->id), &e);
|
||||||
if (!g) {
|
if (!g) {
|
||||||
log_error_x_error(e, "Failed to get geometry of window %#010x", w->id);
|
log_error_x_error(e, "Failed to get geometry of window %#010x", w->id);
|
||||||
free(e);
|
free(e);
|
||||||
@@ -1929,10 +2002,10 @@ struct win *fill_win(session_t *ps, struct win *w) {
|
|||||||
free(g);
|
free(g);
|
||||||
|
|
||||||
// Create Damage for window (if not Input Only)
|
// Create Damage for window (if not Input Only)
|
||||||
new->damage = x_new_id(ps->c);
|
new->damage = x_new_id(&ps->c);
|
||||||
e = xcb_request_check(
|
e = xcb_request_check(
|
||||||
ps->c, xcb_damage_create_checked(ps->c, new->damage, w->id,
|
ps->c.c, xcb_damage_create_checked(ps->c.c, new->damage, w->id,
|
||||||
XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY));
|
XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY));
|
||||||
if (e) {
|
if (e) {
|
||||||
log_error_x_error(e, "Failed to create damage");
|
log_error_x_error(e, "Failed to create damage");
|
||||||
free(e);
|
free(e);
|
||||||
@@ -1942,15 +2015,15 @@ struct win *fill_win(session_t *ps, struct win *w) {
|
|||||||
|
|
||||||
// Set window event mask
|
// Set window event mask
|
||||||
xcb_change_window_attributes(
|
xcb_change_window_attributes(
|
||||||
ps->c, new->base.id, XCB_CW_EVENT_MASK,
|
ps->c.c, new->base.id, XCB_CW_EVENT_MASK,
|
||||||
(const uint32_t[]){determine_evmask(ps, new->base.id, WIN_EVMODE_FRAME)});
|
(const uint32_t[]){determine_evmask(ps, new->base.id, WIN_EVMODE_FRAME)});
|
||||||
|
|
||||||
// Get notification when the shape of a window changes
|
// Get notification when the shape of a window changes
|
||||||
if (ps->shape_exists) {
|
if (ps->shape_exists) {
|
||||||
xcb_shape_select_input(ps->c, new->base.id, 1);
|
xcb_shape_select_input(ps->c.c, new->base.id, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
new->pictfmt = x_get_pictform_for_visual(ps->c, new->a.visual);
|
new->pictfmt = x_get_pictform_for_visual(&ps->c, new->a.visual);
|
||||||
new->client_pictfmt = NULL;
|
new->client_pictfmt = NULL;
|
||||||
|
|
||||||
list_replace(&w->stack_neighbour, &new->base.stack_neighbour);
|
list_replace(&w->stack_neighbour, &new->base.stack_neighbour);
|
||||||
@@ -1970,6 +2043,7 @@ struct win *fill_win(session_t *ps, struct win *w) {
|
|||||||
ps->atoms->a_NET_WM_NAME, ps->atoms->aWM_CLASS,
|
ps->atoms->a_NET_WM_NAME, ps->atoms->aWM_CLASS,
|
||||||
ps->atoms->aWM_WINDOW_ROLE, ps->atoms->a_COMPTON_SHADOW,
|
ps->atoms->aWM_WINDOW_ROLE, ps->atoms->a_COMPTON_SHADOW,
|
||||||
ps->atoms->aWM_CLIENT_LEADER, ps->atoms->aWM_TRANSIENT_FOR,
|
ps->atoms->aWM_CLIENT_LEADER, ps->atoms->aWM_TRANSIENT_FOR,
|
||||||
|
ps->atoms->a_NET_WM_STATE,
|
||||||
};
|
};
|
||||||
win_set_properties_stale(new, init_stale_props, ARR_SIZE(init_stale_props));
|
win_set_properties_stale(new, init_stale_props, ARR_SIZE(init_stale_props));
|
||||||
|
|
||||||
@@ -1999,7 +2073,7 @@ static inline void win_set_leader(session_t *ps, struct managed_win *w, xcb_wind
|
|||||||
// Update the old and new window group and active_leader if the
|
// Update the old and new window group and active_leader if the
|
||||||
// window could affect their state.
|
// window could affect their state.
|
||||||
xcb_window_t cache_leader = win_get_leader(ps, w);
|
xcb_window_t cache_leader = win_get_leader(ps, w);
|
||||||
if (win_is_focused_raw(ps, w) && cache_leader_old != cache_leader) {
|
if (win_is_focused_raw(w) && cache_leader_old != cache_leader) {
|
||||||
ps->active_leader = cache_leader;
|
ps->active_leader = cache_leader;
|
||||||
|
|
||||||
group_on_factor_change(ps, cache_leader_old);
|
group_on_factor_change(ps, cache_leader_old);
|
||||||
@@ -2020,12 +2094,12 @@ void win_update_leader(session_t *ps, struct managed_win *w) {
|
|||||||
// Read the leader properties
|
// Read the leader properties
|
||||||
if (ps->o.detect_transient && !leader) {
|
if (ps->o.detect_transient && !leader) {
|
||||||
leader =
|
leader =
|
||||||
wid_get_prop_window(ps->c, w->client_win, ps->atoms->aWM_TRANSIENT_FOR);
|
wid_get_prop_window(&ps->c, w->client_win, ps->atoms->aWM_TRANSIENT_FOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps->o.detect_client_leader && !leader) {
|
if (ps->o.detect_client_leader && !leader) {
|
||||||
leader =
|
leader =
|
||||||
wid_get_prop_window(ps->c, w->client_win, ps->atoms->aWM_CLIENT_LEADER);
|
wid_get_prop_window(&ps->c, w->client_win, ps->atoms->aWM_CLIENT_LEADER);
|
||||||
}
|
}
|
||||||
|
|
||||||
win_set_leader(ps, w, leader);
|
win_set_leader(ps, w, leader);
|
||||||
@@ -2041,8 +2115,9 @@ static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int
|
|||||||
// Rebuild the cache if needed
|
// Rebuild the cache if needed
|
||||||
if (!w->cache_leader && (w->client_win || w->leader)) {
|
if (!w->cache_leader && (w->client_win || w->leader)) {
|
||||||
// Leader defaults to client window
|
// Leader defaults to client window
|
||||||
if (!(w->cache_leader = w->leader))
|
if (!(w->cache_leader = w->leader)) {
|
||||||
w->cache_leader = w->client_win;
|
w->cache_leader = w->client_win;
|
||||||
|
}
|
||||||
|
|
||||||
// If the leader of this window isn't itself, look for its
|
// If the leader of this window isn't itself, look for its
|
||||||
// ancestors
|
// ancestors
|
||||||
@@ -2050,8 +2125,9 @@ static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int
|
|||||||
auto wp = find_toplevel(ps, w->cache_leader);
|
auto wp = find_toplevel(ps, w->cache_leader);
|
||||||
if (wp) {
|
if (wp) {
|
||||||
// Dead loop?
|
// Dead loop?
|
||||||
if (recursions > WIN_GET_LEADER_MAX_RECURSION)
|
if (recursions > WIN_GET_LEADER_MAX_RECURSION) {
|
||||||
return XCB_NONE;
|
return XCB_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
w->cache_leader = win_get_leader_raw(ps, wp, recursions + 1);
|
w->cache_leader = win_get_leader_raw(ps, wp, recursions + 1);
|
||||||
}
|
}
|
||||||
@@ -2070,8 +2146,9 @@ bool win_update_class(session_t *ps, struct managed_win *w) {
|
|||||||
int nstr = 0;
|
int nstr = 0;
|
||||||
|
|
||||||
// Can't do anything if there's no client window
|
// Can't do anything if there's no client window
|
||||||
if (!w->client_win)
|
if (!w->client_win) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Free and reset old strings
|
// Free and reset old strings
|
||||||
free(w->class_instance);
|
free(w->class_instance);
|
||||||
@@ -2080,7 +2157,8 @@ bool win_update_class(session_t *ps, struct managed_win *w) {
|
|||||||
w->class_general = NULL;
|
w->class_general = NULL;
|
||||||
|
|
||||||
// Retrieve the property string list
|
// Retrieve the property string list
|
||||||
if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_CLASS, &strlst, &nstr)) {
|
if (!wid_get_text_prop(&ps->c, ps->atoms, w->client_win, ps->atoms->aWM_CLASS,
|
||||||
|
&strlst, &nstr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2109,7 +2187,7 @@ static void win_on_focus_change(session_t *ps, struct managed_win *w) {
|
|||||||
xcb_window_t leader = win_get_leader(ps, w);
|
xcb_window_t leader = win_get_leader(ps, w);
|
||||||
|
|
||||||
// If the window gets focused, replace the old active_leader
|
// If the window gets focused, replace the old active_leader
|
||||||
if (win_is_focused_raw(ps, w) && leader != ps->active_leader) {
|
if (win_is_focused_raw(w) && leader != ps->active_leader) {
|
||||||
xcb_window_t active_leader_old = ps->active_leader;
|
xcb_window_t active_leader_old = ps->active_leader;
|
||||||
|
|
||||||
ps->active_leader = leader;
|
ps->active_leader = leader;
|
||||||
@@ -2118,7 +2196,7 @@ static void win_on_focus_change(session_t *ps, struct managed_win *w) {
|
|||||||
group_on_factor_change(ps, leader);
|
group_on_factor_change(ps, leader);
|
||||||
}
|
}
|
||||||
// If the group get unfocused, remove it from active_leader
|
// If the group get unfocused, remove it from active_leader
|
||||||
else if (!win_is_focused_raw(ps, w) && leader &&
|
else if (!win_is_focused_raw(w) && leader &&
|
||||||
leader == ps->active_leader && !group_is_focused(ps, leader)) {
|
leader == ps->active_leader && !group_is_focused(ps, leader)) {
|
||||||
ps->active_leader = XCB_NONE;
|
ps->active_leader = XCB_NONE;
|
||||||
group_on_factor_change(ps, leader);
|
group_on_factor_change(ps, leader);
|
||||||
@@ -2131,7 +2209,7 @@ static void win_on_focus_change(session_t *ps, struct managed_win *w) {
|
|||||||
#ifdef CONFIG_DBUS
|
#ifdef CONFIG_DBUS
|
||||||
// Send D-Bus signal
|
// Send D-Bus signal
|
||||||
if (ps->o.dbus) {
|
if (ps->o.dbus) {
|
||||||
if (win_is_focused_raw(ps, w)) {
|
if (win_is_focused_raw(w)) {
|
||||||
cdbus_ev_win_focusin(ps, &w->base);
|
cdbus_ev_win_focusin(ps, &w->base);
|
||||||
} else {
|
} else {
|
||||||
cdbus_ev_win_focusout(ps, &w->base);
|
cdbus_ev_win_focusout(ps, &w->base);
|
||||||
@@ -2149,15 +2227,18 @@ void win_set_focused(session_t *ps, struct managed_win *w) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (win_is_focused_raw(ps, w)) {
|
if (w->is_ewmh_focused) {
|
||||||
|
assert(ps->active_win == w);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto old_active_win = ps->active_win;
|
auto old_active_win = ps->active_win;
|
||||||
ps->active_win = w;
|
ps->active_win = w;
|
||||||
assert(win_is_focused_raw(ps, w));
|
w->is_ewmh_focused = true;
|
||||||
|
|
||||||
if (old_active_win) {
|
if (old_active_win) {
|
||||||
|
assert(old_active_win->is_ewmh_focused);
|
||||||
|
old_active_win->is_ewmh_focused = false;
|
||||||
win_on_focus_change(ps, old_active_win);
|
win_on_focus_change(ps, old_active_win);
|
||||||
}
|
}
|
||||||
win_on_focus_change(ps, w);
|
win_on_focus_change(ps, w);
|
||||||
@@ -2211,8 +2292,9 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
xcb_shape_get_rectangles_reply_t *r = xcb_shape_get_rectangles_reply(
|
xcb_shape_get_rectangles_reply_t *r = xcb_shape_get_rectangles_reply(
|
||||||
ps->c,
|
ps->c.c,
|
||||||
xcb_shape_get_rectangles(ps->c, w->base.id, XCB_SHAPE_SK_BOUNDING), NULL);
|
xcb_shape_get_rectangles(ps->c.c, w->base.id, XCB_SHAPE_SK_BOUNDING),
|
||||||
|
NULL);
|
||||||
|
|
||||||
if (!r) {
|
if (!r) {
|
||||||
break;
|
break;
|
||||||
@@ -2229,7 +2311,7 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) {
|
|||||||
|
|
||||||
// Add border width because we are using a different origin.
|
// Add border width because we are using a different origin.
|
||||||
// X thinks the top left of the inner window is the origin
|
// X thinks the top left of the inner window is the origin
|
||||||
// (for the bounding shape, althought xcb_get_geometry thinks
|
// (for the bounding shape, although xcb_get_geometry thinks
|
||||||
// the outer top left (outer means outside of the window
|
// the outer top left (outer means outside of the window
|
||||||
// border) is the origin),
|
// border) is the origin),
|
||||||
// We think the top left of the border is the origin
|
// We think the top left of the border is the origin
|
||||||
@@ -2249,14 +2331,12 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) {
|
|||||||
|
|
||||||
// Window shape changed, we should free old wpaint and shadow pict
|
// Window shape changed, we should free old wpaint and shadow pict
|
||||||
// log_trace("free out dated pict");
|
// log_trace("free out dated pict");
|
||||||
win_set_flags(w, WIN_FLAGS_IMAGES_STALE);
|
win_set_flags(w, WIN_FLAGS_IMAGES_STALE | WIN_FLAGS_FACTOR_CHANGED);
|
||||||
win_release_mask(ps->backend_data, w);
|
win_release_mask(ps->backend_data, w);
|
||||||
ps->pending_updates = true;
|
ps->pending_updates = true;
|
||||||
|
|
||||||
free_paint(ps, &w->paint);
|
free_paint(ps, &w->paint);
|
||||||
free_paint(ps, &w->shadow_paint);
|
free_paint(ps, &w->shadow_paint);
|
||||||
|
|
||||||
win_on_factor_change(ps, w);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2285,7 +2365,7 @@ void win_update_opacity_prop(session_t *ps, struct managed_win *w) {
|
|||||||
* Retrieve frame extents from a window.
|
* Retrieve frame extents from a window.
|
||||||
*/
|
*/
|
||||||
void win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t client) {
|
void win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t client) {
|
||||||
winprop_t prop = x_get_prop(ps->c, client, ps->atoms->a_NET_FRAME_EXTENTS, 4L,
|
winprop_t prop = x_get_prop(&ps->c, client, ps->atoms->a_NET_FRAME_EXTENTS, 4L,
|
||||||
XCB_ATOM_CARDINAL, 32);
|
XCB_ATOM_CARDINAL, 32);
|
||||||
|
|
||||||
if (prop.nitems == 4) {
|
if (prop.nitems == 4) {
|
||||||
@@ -2340,7 +2420,7 @@ bool win_is_region_ignore_valid(session_t *ps, const struct managed_win *w) {
|
|||||||
* Stop listening for events on a particular window.
|
* Stop listening for events on a particular window.
|
||||||
*/
|
*/
|
||||||
void win_ev_stop(session_t *ps, const struct win *w) {
|
void win_ev_stop(session_t *ps, const struct win *w) {
|
||||||
xcb_change_window_attributes(ps->c, w->id, XCB_CW_EVENT_MASK, (const uint32_t[]){0});
|
xcb_change_window_attributes(ps->c.c, w->id, XCB_CW_EVENT_MASK, (const uint32_t[]){0});
|
||||||
|
|
||||||
if (!w->managed) {
|
if (!w->managed) {
|
||||||
return;
|
return;
|
||||||
@@ -2348,12 +2428,12 @@ void win_ev_stop(session_t *ps, const struct win *w) {
|
|||||||
|
|
||||||
auto mw = (struct managed_win *)w;
|
auto mw = (struct managed_win *)w;
|
||||||
if (mw->client_win) {
|
if (mw->client_win) {
|
||||||
xcb_change_window_attributes(ps->c, mw->client_win, XCB_CW_EVENT_MASK,
|
xcb_change_window_attributes(ps->c.c, mw->client_win, XCB_CW_EVENT_MASK,
|
||||||
(const uint32_t[]){0});
|
(const uint32_t[]){0});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps->shape_exists) {
|
if (ps->shape_exists) {
|
||||||
xcb_shape_select_input(ps->c, w->id, 0);
|
xcb_shape_select_input(ps->c.c, w->id, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2572,7 +2652,7 @@ bool destroy_win_start(session_t *ps, struct win *w) {
|
|||||||
HASH_DEL(ps->windows, w);
|
HASH_DEL(ps->windows, w);
|
||||||
|
|
||||||
if (!w->managed || mw->state == WSTATE_UNMAPPED) {
|
if (!w->managed || mw->state == WSTATE_UNMAPPED) {
|
||||||
// Window is already unmapped, or is an unmanged window, just
|
// Window is already unmapped, or is an unmanaged window, just
|
||||||
// destroy it
|
// destroy it
|
||||||
destroy_win_finish(ps, w);
|
destroy_win_finish(ps, w);
|
||||||
return true;
|
return true;
|
||||||
@@ -2597,6 +2677,13 @@ bool destroy_win_start(session_t *ps, struct win *w) {
|
|||||||
add_damage_from_win(ps, mw);
|
add_damage_from_win(ps, mw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (win_check_flags_all(mw, WIN_FLAGS_CLIENT_STALE)) {
|
||||||
|
mw->client_win = mw->base.id;
|
||||||
|
mw->wmwin = !mw->a.override_redirect;
|
||||||
|
log_debug("(%#010x): client self (%s)", mw->base.id,
|
||||||
|
(mw->wmwin ? "wmwin" : "override-redirected"));
|
||||||
|
}
|
||||||
|
|
||||||
// Clear some flags about stale window information. Because now
|
// Clear some flags about stale window information. Because now
|
||||||
// the window is destroyed, we can't update them anyway.
|
// the window is destroyed, we can't update them anyway.
|
||||||
win_clear_flags(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE |
|
win_clear_flags(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE |
|
||||||
@@ -2663,8 +2750,8 @@ void unmap_win_start(session_t *ps, struct managed_win *w) {
|
|||||||
w->opacity_target = win_calc_opacity_target(ps, w);
|
w->opacity_target = win_calc_opacity_target(ps, w);
|
||||||
|
|
||||||
if (ps->o.animations && ps->o.animation_for_unmap_window != OPEN_WINDOW_ANIMATION_NONE && ps->o.wintype_option[w->window_type].animation) {
|
if (ps->o.animations && ps->o.animation_for_unmap_window != OPEN_WINDOW_ANIMATION_NONE && ps->o.wintype_option[w->window_type].animation) {
|
||||||
w->dwm_mask = ANIM_UNMAP;
|
w->dwm_mask = ANIM_UNMAP;
|
||||||
init_animation(ps, w);
|
init_animation(ps, w);
|
||||||
|
|
||||||
double x_dist = w->animation_dest_center_x - w->animation_center_x;
|
double x_dist = w->animation_dest_center_x - w->animation_center_x;
|
||||||
double y_dist = w->animation_dest_center_y - w->animation_center_y;
|
double y_dist = w->animation_dest_center_y - w->animation_center_y;
|
||||||
@@ -2719,7 +2806,7 @@ bool win_check_fade_finished(session_t *ps, struct managed_win *w) {
|
|||||||
case WSTATE_DESTROYING: destroy_win_finish(ps, &w->base); return true;
|
case WSTATE_DESTROYING: destroy_win_finish(ps, &w->base); return true;
|
||||||
case WSTATE_MAPPING: map_win_finish(w); return false;
|
case WSTATE_MAPPING: map_win_finish(w); return false;
|
||||||
case WSTATE_FADING: w->state = WSTATE_MAPPED; break;
|
case WSTATE_FADING: w->state = WSTATE_MAPPED; break;
|
||||||
default: unreachable;
|
default: unreachable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2740,33 +2827,26 @@ bool win_skip_fading(session_t *ps, struct managed_win *w) {
|
|||||||
return win_check_fade_finished(ps, w);
|
return win_check_fade_finished(ps, w);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// TODO(absolutelynothelix): rename to x_update_win_(randr_?)monitor and move to
|
||||||
* Get the Xinerama screen a window is on.
|
// the x.c.
|
||||||
*
|
void win_update_monitor(struct x_monitors *monitors, struct managed_win *mw) {
|
||||||
* Return an index >= 0, or -1 if not found.
|
for (int i = 0; i < monitors->count; i++) {
|
||||||
*
|
auto e = pixman_region32_extents(&monitors->regions[i]);
|
||||||
* TODO(yshui) move to x.c
|
if (((e->x1 <= mw->g.x || e->x1 <= mw->pending_g.x) &&
|
||||||
* TODO(yshui) use xrandr
|
(e->x2 >= mw->g.x + mw->widthb || e->x2 >= mw->pending_g.x + mw->widthb)) &&
|
||||||
*/
|
(e->y1 <= mw->g.y || e->y1 <= mw->pending_g.y) &&
|
||||||
void win_update_screen(int nscreens, region_t *screens, struct managed_win *w) {
|
(e->y2 >= mw->g.y + mw->heightb || e->y2 >= mw->pending_g.y + mw->heightb)) {
|
||||||
w->xinerama_scr = -1;
|
mw->randr_monitor = i;
|
||||||
|
log_debug("Window %#010x (%s), %dx%d+%dx%d, is entirely on the "
|
||||||
for (int i = 0; i < nscreens; i++) {
|
"monitor %d (%dx%d+%dx%d)",
|
||||||
auto e = pixman_region32_extents(&screens[i]);
|
mw->base.id, mw->name, mw->g.x, mw->g.y, mw->widthb,
|
||||||
if (e->x1 <= w->g.x && e->y1 <= w->g.y && e->x2 >= w->g.x + w->widthb &&
|
mw->heightb, i, e->x1, e->y1, e->x2 - e->x1, e->y2 - e->y1);
|
||||||
e->y2 >= w->g.y + w->heightb) {
|
|
||||||
w->xinerama_scr = i;
|
|
||||||
log_debug("Window %#010x (%s), %dx%d+%dx%d, is on screen "
|
|
||||||
"%d "
|
|
||||||
"(%dx%d+%dx%d)",
|
|
||||||
w->base.id, w->name, w->g.x, w->g.y, w->widthb, w->heightb,
|
|
||||||
i, e->x1, e->y1, e->x2 - e->x1, e->y2 - e->y1);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log_debug("Window %#010x (%s), %dx%d+%dx%d, is not contained by any "
|
mw->randr_monitor = -1;
|
||||||
"screen",
|
log_debug("Window %#010x (%s), %dx%d+%dx%d, is not entirely on any monitor",
|
||||||
w->base.id, w->name, w->g.x, w->g.y, w->g.width, w->g.height);
|
mw->base.id, mw->name, mw->g.x, mw->g.y, mw->widthb, mw->heightb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map an already registered window
|
/// Map an already registered window
|
||||||
@@ -2972,11 +3052,12 @@ struct managed_win *find_managed_window_or_parent(session_t *ps, xcb_window_t wi
|
|||||||
// We traverse through its ancestors to find out the frame
|
// We traverse through its ancestors to find out the frame
|
||||||
// Using find_win here because if we found a unmanaged window we know
|
// Using find_win here because if we found a unmanaged window we know
|
||||||
// about, we can stop early.
|
// about, we can stop early.
|
||||||
while (wid && wid != ps->root && !(w = find_win(ps, wid))) {
|
while (wid && wid != ps->c.screen_info->root && !(w = find_win(ps, wid))) {
|
||||||
// xcb_query_tree probably fails if you run picom when X is
|
// xcb_query_tree probably fails if you run picom when X is
|
||||||
// somehow initializing (like add it in .xinitrc). In this case
|
// somehow initializing (like add it in .xinitrc). In this case
|
||||||
// just leave it alone.
|
// just leave it alone.
|
||||||
auto reply = xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, wid), NULL);
|
auto reply =
|
||||||
|
xcb_query_tree_reply(ps->c.c, xcb_query_tree(ps->c.c, wid), NULL);
|
||||||
if (reply == NULL) {
|
if (reply == NULL) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2992,49 +3073,10 @@ struct managed_win *find_managed_window_or_parent(session_t *ps, xcb_window_t wi
|
|||||||
return (struct managed_win *)w;
|
return (struct managed_win *)w;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a rectangle includes the whole screen.
|
|
||||||
*/
|
|
||||||
static inline bool rect_is_fullscreen(const session_t *ps, int x, int y, int wid, int hei) {
|
|
||||||
return (x <= 0 && y <= 0 && (x + wid) >= ps->root_width && (y + hei) >= ps->root_height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a window is fulscreen using EWMH
|
|
||||||
*
|
|
||||||
* TODO(yshui) cache this property
|
|
||||||
*/
|
|
||||||
static inline bool
|
|
||||||
win_is_fullscreen_xcb(xcb_connection_t *c, const struct atom *a, const xcb_window_t w) {
|
|
||||||
xcb_get_property_cookie_t prop =
|
|
||||||
xcb_get_property(c, 0, w, a->a_NET_WM_STATE, XCB_ATOM_ATOM, 0, 12);
|
|
||||||
xcb_get_property_reply_t *reply = xcb_get_property_reply(c, prop, NULL);
|
|
||||||
if (!reply) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reply->length) {
|
|
||||||
xcb_atom_t *val = xcb_get_property_value(reply);
|
|
||||||
for (uint32_t i = 0; i < reply->length; i++) {
|
|
||||||
if (val[i] != a->a_NET_WM_STATE_FULLSCREEN) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
free(reply);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(reply);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set flags on a window. Some sanity checks are performed
|
/// Set flags on a window. Some sanity checks are performed
|
||||||
void win_set_flags(struct managed_win *w, uint64_t flags) {
|
void win_set_flags(struct managed_win *w, uint64_t flags) {
|
||||||
log_debug("Set flags %" PRIu64 " to window %#010x (%s)", flags, w->base.id, w->name);
|
log_debug("Set flags %" PRIu64 " to window %#010x (%s)", flags, w->base.id, w->name);
|
||||||
if (unlikely(w->state == WSTATE_DESTROYING)) {
|
if (unlikely(w->state == WSTATE_DESTROYING)) {
|
||||||
if (w->animation_progress != 1.0) {
|
|
||||||
// Return because animation will trigger some of the flags
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log_error("Flags set on a destroyed window %#010x (%s)", w->base.id, w->name);
|
log_error("Flags set on a destroyed window %#010x (%s)", w->base.id, w->name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3047,10 +3089,6 @@ void win_clear_flags(struct managed_win *w, uint64_t flags) {
|
|||||||
log_debug("Clear flags %" PRIu64 " from window %#010x (%s)", flags, w->base.id,
|
log_debug("Clear flags %" PRIu64 " from window %#010x (%s)", flags, w->base.id,
|
||||||
w->name);
|
w->name);
|
||||||
if (unlikely(w->state == WSTATE_DESTROYING)) {
|
if (unlikely(w->state == WSTATE_DESTROYING)) {
|
||||||
if (w->animation_progress != 1.0) {
|
|
||||||
// Return because animation will trigger some of the flags
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log_warn("Flags cleared on a destroyed window %#010x (%s)", w->base.id,
|
log_warn("Flags cleared on a destroyed window %#010x (%s)", w->base.id,
|
||||||
w->name);
|
w->name);
|
||||||
return;
|
return;
|
||||||
@@ -3060,7 +3098,7 @@ void win_clear_flags(struct managed_win *w, uint64_t flags) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void win_set_properties_stale(struct managed_win *w, const xcb_atom_t *props, int nprops) {
|
void win_set_properties_stale(struct managed_win *w, const xcb_atom_t *props, int nprops) {
|
||||||
const auto bits_per_element = sizeof(*w->stale_props) * 8;
|
auto const bits_per_element = sizeof(*w->stale_props) * 8;
|
||||||
size_t new_capacity = w->stale_props_capacity;
|
size_t new_capacity = w->stale_props_capacity;
|
||||||
|
|
||||||
// Calculate the new capacity of the properties array
|
// Calculate the new capacity of the properties array
|
||||||
@@ -3095,12 +3133,12 @@ static void win_clear_all_properties_stale(struct managed_win *w) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool win_fetch_and_unset_property_stale(struct managed_win *w, xcb_atom_t prop) {
|
static bool win_fetch_and_unset_property_stale(struct managed_win *w, xcb_atom_t prop) {
|
||||||
const auto bits_per_element = sizeof(*w->stale_props) * 8;
|
auto const bits_per_element = sizeof(*w->stale_props) * 8;
|
||||||
if (prop >= w->stale_props_capacity * bits_per_element) {
|
if (prop >= w->stale_props_capacity * bits_per_element) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto mask = 1UL << (prop % bits_per_element);
|
auto const mask = 1UL << (prop % bits_per_element);
|
||||||
bool ret = w->stale_props[prop / bits_per_element] & mask;
|
bool ret = w->stale_props[prop / bits_per_element] & mask;
|
||||||
w->stale_props[prop / bits_per_element] &= ~mask;
|
w->stale_props[prop / bits_per_element] &= ~mask;
|
||||||
return ret;
|
return ret;
|
||||||
@@ -3119,13 +3157,15 @@ bool win_check_flags_all(struct managed_win *w, uint64_t flags) {
|
|||||||
*
|
*
|
||||||
* It's not using w->border_size for performance measures.
|
* It's not using w->border_size for performance measures.
|
||||||
*/
|
*/
|
||||||
bool win_is_fullscreen(const session_t *ps, const struct managed_win *w) {
|
void win_update_is_fullscreen(const session_t *ps, struct managed_win *w) {
|
||||||
if (!ps->o.no_ewmh_fullscreen &&
|
if (!ps->o.no_ewmh_fullscreen && w->is_ewmh_fullscreen) {
|
||||||
win_is_fullscreen_xcb(ps->c, ps->atoms, w->client_win)) {
|
w->is_fullscreen = true;
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
return rect_is_fullscreen(ps, w->g.x, w->g.y, w->widthb, w->heightb) &&
|
w->is_fullscreen = w->g.x <= 0 && w->g.y <= 0 &&
|
||||||
(!w->bounding_shaped || w->rounded_corners);
|
(w->g.x + w->widthb) >= ps->root_width &&
|
||||||
|
(w->g.y + w->heightb) >= ps->root_height &&
|
||||||
|
(!w->bounding_shaped || w->rounded_corners);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -3136,7 +3176,7 @@ bool win_is_fullscreen(const session_t *ps, const struct managed_win *w) {
|
|||||||
bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win *w) {
|
bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win *w) {
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
auto prop = x_get_prop(ps->c, w->client_win, ps->atoms->a_NET_WM_BYPASS_COMPOSITOR,
|
auto prop = x_get_prop(&ps->c, w->client_win, ps->atoms->a_NET_WM_BYPASS_COMPOSITOR,
|
||||||
1L, XCB_ATOM_CARDINAL, 32);
|
1L, XCB_ATOM_CARDINAL, 32);
|
||||||
|
|
||||||
if (prop.nitems && *prop.c32 == 1) {
|
if (prop.nitems && *prop.c32 == 1) {
|
||||||
@@ -3151,19 +3191,19 @@ bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win *
|
|||||||
* Check if a window is focused, without using any focus rules or forced focus
|
* Check if a window is focused, without using any focus rules or forced focus
|
||||||
* settings
|
* settings
|
||||||
*/
|
*/
|
||||||
bool win_is_focused_raw(const session_t *ps, const struct managed_win *w) {
|
bool win_is_focused_raw(const struct managed_win *w) {
|
||||||
return w->a.map_state == XCB_MAP_STATE_VIEWABLE && ps->active_win == w;
|
return w->a.map_state == XCB_MAP_STATE_VIEWABLE && w->is_ewmh_focused;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the managed window immediately below `i` in the window stack
|
// Find the managed window immediately below `i` in the window stack
|
||||||
struct managed_win *
|
struct managed_win *
|
||||||
win_stack_find_next_managed(const session_t *ps, const struct list_node *i) {
|
win_stack_find_next_managed(const session_t *ps, const struct list_node *w) {
|
||||||
while (!list_node_is_last(&ps->window_stack, i)) {
|
while (!list_node_is_last(&ps->window_stack, w)) {
|
||||||
auto next = list_entry(i->next, struct win, stack_neighbour);
|
auto next = list_entry(w->next, struct win, stack_neighbour);
|
||||||
if (next->managed) {
|
if (next->managed) {
|
||||||
return (struct managed_win *)next;
|
return (struct managed_win *)next;
|
||||||
}
|
}
|
||||||
i = &next->stack_neighbour;
|
w = &next->stack_neighbour;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|||||||
67
src/win.h
67
src/win.h
@@ -11,11 +11,6 @@
|
|||||||
|
|
||||||
#include "uthash_extra.h"
|
#include "uthash_extra.h"
|
||||||
|
|
||||||
// FIXME shouldn't need this
|
|
||||||
#ifdef CONFIG_OPENGL
|
|
||||||
#include <GL/gl.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "c2.h"
|
#include "c2.h"
|
||||||
#include "compiler.h"
|
#include "compiler.h"
|
||||||
#include "list.h"
|
#include "list.h"
|
||||||
@@ -31,11 +26,12 @@ typedef struct session session_t;
|
|||||||
typedef struct _glx_texture glx_texture_t;
|
typedef struct _glx_texture glx_texture_t;
|
||||||
|
|
||||||
#define win_stack_foreach_managed(w, win_stack) \
|
#define win_stack_foreach_managed(w, win_stack) \
|
||||||
list_foreach(struct managed_win, w, win_stack, base.stack_neighbour) if (w->base.managed)
|
list_foreach(struct managed_win, w, win_stack, \
|
||||||
|
base.stack_neighbour) if ((w)->base.managed)
|
||||||
|
|
||||||
#define win_stack_foreach_managed_safe(w, win_stack) \
|
#define win_stack_foreach_managed_safe(w, win_stack) \
|
||||||
list_foreach_safe(struct managed_win, w, win_stack, \
|
list_foreach_safe(struct managed_win, w, win_stack, \
|
||||||
base.stack_neighbour) if (w->base.managed)
|
base.stack_neighbour) if ((w)->base.managed)
|
||||||
|
|
||||||
#ifdef CONFIG_OPENGL
|
#ifdef CONFIG_OPENGL
|
||||||
// FIXME this type should be in opengl.h
|
// FIXME this type should be in opengl.h
|
||||||
@@ -117,10 +113,10 @@ struct managed_win {
|
|||||||
struct win base;
|
struct win base;
|
||||||
/// backend data attached to this window. Only available when
|
/// backend data attached to this window. Only available when
|
||||||
/// `state` is not UNMAPPED
|
/// `state` is not UNMAPPED
|
||||||
void *win_image;
|
image_handle win_image;
|
||||||
void *old_win_image; // Old window image for interpolating window contents during animations
|
image_handle old_win_image; // Old window image for interpolating window contents during animations
|
||||||
void *shadow_image;
|
image_handle shadow_image;
|
||||||
void *mask_image;
|
image_handle mask_image;
|
||||||
/// Pointer to the next higher window to paint.
|
/// Pointer to the next higher window to paint.
|
||||||
struct managed_win *prev_trans;
|
struct managed_win *prev_trans;
|
||||||
/// Number of windows above this window
|
/// Number of windows above this window
|
||||||
@@ -137,8 +133,8 @@ struct managed_win {
|
|||||||
struct win_geometry g;
|
struct win_geometry g;
|
||||||
/// Updated geometry received in events
|
/// Updated geometry received in events
|
||||||
struct win_geometry pending_g;
|
struct win_geometry pending_g;
|
||||||
/// Xinerama screen this window is on.
|
/// X RandR monitor this window is on.
|
||||||
int xinerama_scr;
|
int randr_monitor;
|
||||||
/// Window visual pict format
|
/// Window visual pict format
|
||||||
const xcb_render_pictforminfo_t *pictfmt;
|
const xcb_render_pictforminfo_t *pictfmt;
|
||||||
/// Client window visual pict format
|
/// Client window visual pict format
|
||||||
@@ -170,8 +166,8 @@ struct managed_win {
|
|||||||
/// opacity state, window geometry, window mapped/unmapped state,
|
/// opacity state, window geometry, window mapped/unmapped state,
|
||||||
/// window mode of the windows above. DOES NOT INCLUDE the body of THIS WINDOW.
|
/// window mode of the windows above. DOES NOT INCLUDE the body of THIS WINDOW.
|
||||||
/// NULL means reg_ignore has not been calculated for this window.
|
/// NULL means reg_ignore has not been calculated for this window.
|
||||||
/// 1 = tag prev , 2 = tag next, 4 = unmap
|
/// 1 = tag prev , 2 = tag next, 4 = unmap
|
||||||
uint32_t dwm_mask;
|
uint32_t dwm_mask;
|
||||||
rc_region_t *reg_ignore;
|
rc_region_t *reg_ignore;
|
||||||
/// Whether the reg_ignore of all windows beneath this window are valid
|
/// Whether the reg_ignore of all windows beneath this window are valid
|
||||||
bool reg_ignore_valid;
|
bool reg_ignore_valid;
|
||||||
@@ -202,11 +198,11 @@ struct managed_win {
|
|||||||
/// Inverse of the window distance at the start of animation, for
|
/// Inverse of the window distance at the start of animation, for
|
||||||
/// tracking animation progress
|
/// tracking animation progress
|
||||||
double animation_inv_og_distance;
|
double animation_inv_og_distance;
|
||||||
/// Animation info if it is a tag change & check if its changing window sizes
|
/// Animation info if it is a tag change & check if its changing window sizes
|
||||||
/// 0: no tag change
|
/// 0: no tag change
|
||||||
/// 1: normal tag change animation
|
/// 1: normal tag change animation
|
||||||
/// 2: tag change animation that effects window size
|
/// 2: tag change animation that effects window size
|
||||||
uint16_t animation_is_tag;
|
uint16_t animation_is_tag;
|
||||||
|
|
||||||
// Client window related members
|
// Client window related members
|
||||||
/// ID of the top-level client window of the window.
|
/// ID of the top-level client window of the window.
|
||||||
@@ -237,6 +233,14 @@ struct managed_win {
|
|||||||
char *class_general;
|
char *class_general;
|
||||||
/// <code>WM_WINDOW_ROLE</code> value of the window.
|
/// <code>WM_WINDOW_ROLE</code> value of the window.
|
||||||
char *role;
|
char *role;
|
||||||
|
/// Whether the window sets the EWMH fullscreen property.
|
||||||
|
bool is_ewmh_fullscreen;
|
||||||
|
/// Whether the window should be considered fullscreen. Based on
|
||||||
|
/// `is_ewmh_fullscreen`, or the windows spatial relation with the
|
||||||
|
/// root window. Which one is used is determined by user configuration.
|
||||||
|
bool is_fullscreen;
|
||||||
|
/// Whether the window is the EWMH active window.
|
||||||
|
bool is_ewmh_focused;
|
||||||
|
|
||||||
// Opacity-related members
|
// Opacity-related members
|
||||||
/// Current window opacity.
|
/// Current window opacity.
|
||||||
@@ -282,13 +286,13 @@ struct managed_win {
|
|||||||
switch_t shadow_force;
|
switch_t shadow_force;
|
||||||
/// Opacity of the shadow. Affected by window opacity and frame opacity.
|
/// Opacity of the shadow. Affected by window opacity and frame opacity.
|
||||||
double shadow_opacity;
|
double shadow_opacity;
|
||||||
/// X offset of shadow. Affected by commandline argument.
|
/// X offset of shadow. Affected by command line argument.
|
||||||
int shadow_dx;
|
int shadow_dx;
|
||||||
/// Y offset of shadow. Affected by commandline argument.
|
/// Y offset of shadow. Affected by command line argument.
|
||||||
int shadow_dy;
|
int shadow_dy;
|
||||||
/// Width of shadow. Affected by window size and commandline argument.
|
/// Width of shadow. Affected by window size and command line argument.
|
||||||
int shadow_width;
|
int shadow_width;
|
||||||
/// Height of shadow. Affected by window size and commandline argument.
|
/// Height of shadow. Affected by window size and command line argument.
|
||||||
int shadow_height;
|
int shadow_height;
|
||||||
/// Picture to render shadow. Affected by window size.
|
/// Picture to render shadow. Affected by window size.
|
||||||
paint_t shadow_paint;
|
paint_t shadow_paint;
|
||||||
@@ -379,12 +383,16 @@ void win_recheck_client(session_t *ps, struct managed_win *w);
|
|||||||
*/
|
*/
|
||||||
double attr_pure win_calc_opacity_target(session_t *ps, const struct managed_win *w);
|
double attr_pure win_calc_opacity_target(session_t *ps, const struct managed_win *w);
|
||||||
bool attr_pure win_should_dim(session_t *ps, const struct managed_win *w);
|
bool attr_pure win_should_dim(session_t *ps, const struct managed_win *w);
|
||||||
void win_update_screen(int nscreens, region_t *screens, struct managed_win *w);
|
|
||||||
|
void win_update_monitor(struct x_monitors *monitors, struct managed_win *mw);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the bounding shape of a window.
|
* Retrieve the bounding shape of a window.
|
||||||
*/
|
*/
|
||||||
// XXX was win_border_size
|
// XXX was win_border_size
|
||||||
void win_update_bounding_shape(session_t *ps, struct managed_win *w);
|
void win_update_bounding_shape(session_t *ps, struct managed_win *w);
|
||||||
|
/// Recheck if a window is fullscreen
|
||||||
|
void win_update_is_fullscreen(const session_t *ps, struct managed_win *w);
|
||||||
/**
|
/**
|
||||||
* Check if a window has BYPASS_COMPOSITOR property set
|
* Check if a window has BYPASS_COMPOSITOR property set
|
||||||
*/
|
*/
|
||||||
@@ -460,17 +468,10 @@ struct managed_win *find_toplevel(session_t *ps, xcb_window_t id);
|
|||||||
*/
|
*/
|
||||||
struct managed_win *find_managed_window_or_parent(session_t *ps, xcb_window_t wid);
|
struct managed_win *find_managed_window_or_parent(session_t *ps, xcb_window_t wid);
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a window is a fullscreen window.
|
|
||||||
*
|
|
||||||
* It's not using w->border_size for performance measures.
|
|
||||||
*/
|
|
||||||
bool attr_pure win_is_fullscreen(const session_t *ps, const struct managed_win *w);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a window is focused, without using any focus rules or forced focus settings
|
* Check if a window is focused, without using any focus rules or forced focus settings
|
||||||
*/
|
*/
|
||||||
bool attr_pure win_is_focused_raw(const session_t *ps, const struct managed_win *w);
|
bool attr_pure win_is_focused_raw(const struct managed_win *w);
|
||||||
|
|
||||||
/// check if window has ARGB visual
|
/// check if window has ARGB visual
|
||||||
bool attr_pure win_has_alpha(const struct managed_win *w);
|
bool attr_pure win_has_alpha(const struct managed_win *w);
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ typedef enum {
|
|||||||
} winstate_t;
|
} winstate_t;
|
||||||
|
|
||||||
enum win_flags {
|
enum win_flags {
|
||||||
// Note: *_NONE flags are mostly redudant and meant for detecting logical errors
|
// Note: *_NONE flags are mostly redundant and meant for detecting logical errors
|
||||||
// in the code
|
// in the code
|
||||||
|
|
||||||
/// pixmap is out of date, will be update in win_process_flags
|
/// pixmap is out of date, will be update in win_process_flags
|
||||||
|
|||||||
377
src/x.c
377
src/x.c
@@ -4,14 +4,19 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <X11/Xlib-xcb.h>
|
||||||
#include <X11/Xutil.h>
|
#include <X11/Xutil.h>
|
||||||
#include <pixman.h>
|
#include <pixman.h>
|
||||||
#include <xcb/composite.h>
|
#include <xcb/composite.h>
|
||||||
#include <xcb/damage.h>
|
#include <xcb/damage.h>
|
||||||
|
#include <xcb/dpms.h>
|
||||||
#include <xcb/glx.h>
|
#include <xcb/glx.h>
|
||||||
|
#include <xcb/present.h>
|
||||||
|
#include <xcb/randr.h>
|
||||||
#include <xcb/render.h>
|
#include <xcb/render.h>
|
||||||
#include <xcb/sync.h>
|
#include <xcb/sync.h>
|
||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
|
#include <xcb/xcb_aux.h>
|
||||||
#include <xcb/xcb_renderutil.h>
|
#include <xcb/xcb_renderutil.h>
|
||||||
#include <xcb/xfixes.h>
|
#include <xcb/xfixes.h>
|
||||||
|
|
||||||
@@ -27,6 +32,72 @@
|
|||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "x.h"
|
#include "x.h"
|
||||||
|
|
||||||
|
// === Error handling ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Xlib error handler function.
|
||||||
|
*/
|
||||||
|
static int xerror(Display attr_unused *dpy, XErrorEvent *ev) {
|
||||||
|
if (!ps_g) {
|
||||||
|
// Do not ignore errors until the session has been initialized
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fake a xcb error, fill in just enough information
|
||||||
|
xcb_generic_error_t xcb_err;
|
||||||
|
xcb_err.full_sequence = (uint32_t)ev->serial;
|
||||||
|
xcb_err.major_code = ev->request_code;
|
||||||
|
xcb_err.minor_code = ev->minor_code;
|
||||||
|
xcb_err.error_code = ev->error_code;
|
||||||
|
x_handle_error(&ps_g->c, &xcb_err);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void x_discard_pending(struct x_connection *c, uint32_t sequence) {
|
||||||
|
while (c->pending_reply_head && sequence > c->pending_reply_head->sequence) {
|
||||||
|
auto next = c->pending_reply_head->next;
|
||||||
|
free(c->pending_reply_head);
|
||||||
|
c->pending_reply_head = next;
|
||||||
|
}
|
||||||
|
if (!c->pending_reply_head) {
|
||||||
|
c->pending_reply_tail = &c->pending_reply_head;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void x_handle_error(struct x_connection *c, xcb_generic_error_t *ev) {
|
||||||
|
x_discard_pending(c, ev->full_sequence);
|
||||||
|
if (c->pending_reply_head && c->pending_reply_head->sequence == ev->full_sequence) {
|
||||||
|
if (c->pending_reply_head->action != PENDING_REPLY_ACTION_IGNORE) {
|
||||||
|
x_log_error(LOG_LEVEL_ERROR, ev->full_sequence, ev->major_code,
|
||||||
|
ev->minor_code, ev->error_code);
|
||||||
|
}
|
||||||
|
switch (c->pending_reply_head->action) {
|
||||||
|
case PENDING_REPLY_ACTION_ABORT:
|
||||||
|
log_fatal("An unrecoverable X error occurred, aborting...");
|
||||||
|
abort();
|
||||||
|
case PENDING_REPLY_ACTION_DEBUG_ABORT: assert(false); break;
|
||||||
|
case PENDING_REPLY_ACTION_IGNORE: break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
x_log_error(LOG_LEVEL_WARN, ev->full_sequence, ev->major_code, ev->minor_code,
|
||||||
|
ev->error_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize x_connection struct from an Xlib Display.
|
||||||
|
///
|
||||||
|
/// Note this function doesn't take ownership of the Display, the caller is still
|
||||||
|
/// responsible for closing it after `free_x_connection` is called.
|
||||||
|
void x_connection_init(struct x_connection *c, Display *dpy) {
|
||||||
|
c->dpy = dpy;
|
||||||
|
c->c = XGetXCBConnection(dpy);
|
||||||
|
c->pending_reply_tail = &c->pending_reply_head;
|
||||||
|
c->previous_xerror_handler = XSetErrorHandler(xerror);
|
||||||
|
|
||||||
|
c->screen = DefaultScreen(dpy);
|
||||||
|
c->screen_info = xcb_aux_get_screen(c->c, c->screen);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a specific attribute of a window.
|
* Get a specific attribute of a window.
|
||||||
*
|
*
|
||||||
@@ -42,11 +113,11 @@
|
|||||||
* @return a <code>winprop_t</code> structure containing the attribute
|
* @return a <code>winprop_t</code> structure containing the attribute
|
||||||
* and number of items. A blank one on failure.
|
* and number of items. A blank one on failure.
|
||||||
*/
|
*/
|
||||||
winprop_t x_get_prop_with_offset(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom,
|
winprop_t x_get_prop_with_offset(const struct x_connection *c, xcb_window_t w, xcb_atom_t atom,
|
||||||
int offset, int length, xcb_atom_t rtype, int rformat) {
|
int offset, int length, xcb_atom_t rtype, int rformat) {
|
||||||
xcb_get_property_reply_t *r = xcb_get_property_reply(
|
xcb_get_property_reply_t *r = xcb_get_property_reply(
|
||||||
c,
|
c->c,
|
||||||
xcb_get_property(c, 0, w, atom, rtype, to_u32_checked(offset),
|
xcb_get_property(c->c, 0, w, atom, rtype, to_u32_checked(offset),
|
||||||
to_u32_checked(length)),
|
to_u32_checked(length)),
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
@@ -70,10 +141,10 @@ winprop_t x_get_prop_with_offset(xcb_connection_t *c, xcb_window_t w, xcb_atom_t
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the type, format and size in bytes of a window's specific attribute.
|
/// Get the type, format and size in bytes of a window's specific attribute.
|
||||||
winprop_info_t x_get_prop_info(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom) {
|
winprop_info_t x_get_prop_info(const struct x_connection *c, xcb_window_t w, xcb_atom_t atom) {
|
||||||
xcb_generic_error_t *e = NULL;
|
xcb_generic_error_t *e = NULL;
|
||||||
auto r = xcb_get_property_reply(
|
auto r = xcb_get_property_reply(
|
||||||
c, xcb_get_property(c, 0, w, atom, XCB_ATOM_ANY, 0, 0), &e);
|
c->c, xcb_get_property(c->c, 0, w, atom, XCB_ATOM_ANY, 0, 0), &e);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
log_debug_x_error(e, "Failed to get property info for window %#010x", w);
|
log_debug_x_error(e, "Failed to get property info for window %#010x", w);
|
||||||
free(e);
|
free(e);
|
||||||
@@ -93,7 +164,7 @@ winprop_info_t x_get_prop_info(xcb_connection_t *c, xcb_window_t w, xcb_atom_t a
|
|||||||
*
|
*
|
||||||
* @return the value if successful, 0 otherwise
|
* @return the value if successful, 0 otherwise
|
||||||
*/
|
*/
|
||||||
xcb_window_t wid_get_prop_window(xcb_connection_t *c, xcb_window_t wid, xcb_atom_t aprop) {
|
xcb_window_t wid_get_prop_window(struct x_connection *c, xcb_window_t wid, xcb_atom_t aprop) {
|
||||||
// Get the attribute
|
// Get the attribute
|
||||||
xcb_window_t p = XCB_NONE;
|
xcb_window_t p = XCB_NONE;
|
||||||
winprop_t prop = x_get_prop(c, wid, aprop, 1L, XCB_ATOM_WINDOW, 32);
|
winprop_t prop = x_get_prop(c, wid, aprop, 1L, XCB_ATOM_WINDOW, 32);
|
||||||
@@ -111,10 +182,9 @@ xcb_window_t wid_get_prop_window(xcb_connection_t *c, xcb_window_t wid, xcb_atom
|
|||||||
/**
|
/**
|
||||||
* Get the value of a text property of a window.
|
* Get the value of a text property of a window.
|
||||||
*/
|
*/
|
||||||
bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ***pstrlst,
|
bool wid_get_text_prop(struct x_connection *c, struct atom *atoms, xcb_window_t wid,
|
||||||
int *pnstr) {
|
xcb_atom_t prop, char ***pstrlst, int *pnstr) {
|
||||||
assert(ps->server_grabbed);
|
auto prop_info = x_get_prop_info(c, wid, prop);
|
||||||
auto prop_info = x_get_prop_info(ps->c, wid, prop);
|
|
||||||
auto type = prop_info.type;
|
auto type = prop_info.type;
|
||||||
auto format = prop_info.format;
|
auto format = prop_info.format;
|
||||||
auto length = prop_info.length;
|
auto length = prop_info.length;
|
||||||
@@ -123,8 +193,7 @@ bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char **
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type != XCB_ATOM_STRING && type != ps->atoms->aUTF8_STRING &&
|
if (type != XCB_ATOM_STRING && type != atoms->aUTF8_STRING && type != atoms->aC_STRING) {
|
||||||
type != ps->atoms->aC_STRING) {
|
|
||||||
log_warn("Text property %d of window %#010x has unsupported type: %d",
|
log_warn("Text property %d of window %#010x has unsupported type: %d",
|
||||||
prop, wid, type);
|
prop, wid, type);
|
||||||
return false;
|
return false;
|
||||||
@@ -139,7 +208,7 @@ bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char **
|
|||||||
xcb_generic_error_t *e = NULL;
|
xcb_generic_error_t *e = NULL;
|
||||||
auto word_count = (length + 4 - 1) / 4;
|
auto word_count = (length + 4 - 1) / 4;
|
||||||
auto r = xcb_get_property_reply(
|
auto r = xcb_get_property_reply(
|
||||||
ps->c, xcb_get_property(ps->c, 0, wid, prop, type, 0, word_count), &e);
|
c->c, xcb_get_property(c->c, 0, wid, prop, type, 0, word_count), &e);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
log_debug_x_error(e, "Failed to get window property for %#010x", wid);
|
log_debug_x_error(e, "Failed to get window property for %#010x", wid);
|
||||||
free(e);
|
free(e);
|
||||||
@@ -197,14 +266,14 @@ bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char **
|
|||||||
// of this program
|
// of this program
|
||||||
static thread_local xcb_render_query_pict_formats_reply_t *g_pictfmts = NULL;
|
static thread_local xcb_render_query_pict_formats_reply_t *g_pictfmts = NULL;
|
||||||
|
|
||||||
static inline void x_get_server_pictfmts(xcb_connection_t *c) {
|
static inline void x_get_server_pictfmts(struct x_connection *c) {
|
||||||
if (g_pictfmts) {
|
if (g_pictfmts) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
xcb_generic_error_t *e = NULL;
|
xcb_generic_error_t *e = NULL;
|
||||||
// Get window picture format
|
// Get window picture format
|
||||||
g_pictfmts =
|
g_pictfmts = xcb_render_query_pict_formats_reply(
|
||||||
xcb_render_query_pict_formats_reply(c, xcb_render_query_pict_formats(c), &e);
|
c->c, xcb_render_query_pict_formats(c->c), &e);
|
||||||
if (e || !g_pictfmts) {
|
if (e || !g_pictfmts) {
|
||||||
log_fatal("failed to get pict formats\n");
|
log_fatal("failed to get pict formats\n");
|
||||||
abort();
|
abort();
|
||||||
@@ -212,7 +281,7 @@ static inline void x_get_server_pictfmts(xcb_connection_t *c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const xcb_render_pictforminfo_t *
|
const xcb_render_pictforminfo_t *
|
||||||
x_get_pictform_for_visual(xcb_connection_t *c, xcb_visualid_t visual) {
|
x_get_pictform_for_visual(struct x_connection *c, xcb_visualid_t visual) {
|
||||||
x_get_server_pictfmts(c);
|
x_get_server_pictfmts(c);
|
||||||
|
|
||||||
xcb_render_pictvisual_t *pv = xcb_render_util_find_visual_format(g_pictfmts, visual);
|
xcb_render_pictvisual_t *pv = xcb_render_util_find_visual_format(g_pictfmts, visual);
|
||||||
@@ -243,7 +312,7 @@ static xcb_visualid_t attr_pure x_get_visual_for_pictfmt(xcb_render_query_pict_f
|
|||||||
return XCB_NONE;
|
return XCB_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_t std) {
|
xcb_visualid_t x_get_visual_for_standard(struct x_connection *c, xcb_pict_standard_t std) {
|
||||||
x_get_server_pictfmts(c);
|
x_get_server_pictfmts(c);
|
||||||
|
|
||||||
auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, std);
|
auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, std);
|
||||||
@@ -251,8 +320,19 @@ xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_
|
|||||||
return x_get_visual_for_pictfmt(g_pictfmts, pictfmt->id);
|
return x_get_visual_for_pictfmt(g_pictfmts, pictfmt->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xcb_visualid_t x_get_visual_for_depth(xcb_screen_t *screen, uint8_t depth) {
|
||||||
|
xcb_depth_iterator_t depth_it = xcb_screen_allowed_depths_iterator(screen);
|
||||||
|
for (; depth_it.rem; xcb_depth_next(&depth_it)) {
|
||||||
|
if (depth_it.data->depth == depth) {
|
||||||
|
return xcb_depth_visuals_iterator(depth_it.data).data->visual_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return XCB_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
xcb_render_pictformat_t
|
xcb_render_pictformat_t
|
||||||
x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std) {
|
x_get_pictfmt_for_standard(struct x_connection *c, xcb_pict_standard_t std) {
|
||||||
x_get_server_pictfmts(c);
|
x_get_server_pictfmts(c);
|
||||||
|
|
||||||
auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, std);
|
auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, std);
|
||||||
@@ -260,26 +340,8 @@ x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std) {
|
|||||||
return pictfmt->id;
|
return pictfmt->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
int x_get_visual_depth(xcb_connection_t *c, xcb_visualid_t visual) {
|
|
||||||
auto setup = xcb_get_setup(c);
|
|
||||||
for (auto screen = xcb_setup_roots_iterator(setup); screen.rem;
|
|
||||||
xcb_screen_next(&screen)) {
|
|
||||||
for (auto depth = xcb_screen_allowed_depths_iterator(screen.data);
|
|
||||||
depth.rem; xcb_depth_next(&depth)) {
|
|
||||||
const int len = xcb_depth_visuals_length(depth.data);
|
|
||||||
const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data);
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
if (visual == visuals[i].visual_id) {
|
|
||||||
return depth.data->depth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_render_picture_t
|
xcb_render_picture_t
|
||||||
x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *c,
|
x_create_picture_with_pictfmt_and_pixmap(struct x_connection *c,
|
||||||
const xcb_render_pictforminfo_t *pictfmt,
|
const xcb_render_pictforminfo_t *pictfmt,
|
||||||
xcb_pixmap_t pixmap, uint32_t valuemask,
|
xcb_pixmap_t pixmap, uint32_t valuemask,
|
||||||
const xcb_render_create_picture_value_list_t *attr) {
|
const xcb_render_create_picture_value_list_t *attr) {
|
||||||
@@ -293,9 +355,9 @@ x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *c,
|
|||||||
}
|
}
|
||||||
|
|
||||||
xcb_render_picture_t tmp_picture = x_new_id(c);
|
xcb_render_picture_t tmp_picture = x_new_id(c);
|
||||||
xcb_generic_error_t *e =
|
xcb_generic_error_t *e = xcb_request_check(
|
||||||
xcb_request_check(c, xcb_render_create_picture_checked(
|
c->c, xcb_render_create_picture_checked(c->c, tmp_picture, pixmap,
|
||||||
c, tmp_picture, pixmap, pictfmt->id, valuemask, buf));
|
pictfmt->id, valuemask, buf));
|
||||||
free(buf);
|
free(buf);
|
||||||
if (e) {
|
if (e) {
|
||||||
log_error_x_error(e, "failed to create picture");
|
log_error_x_error(e, "failed to create picture");
|
||||||
@@ -306,7 +368,7 @@ x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *c,
|
|||||||
}
|
}
|
||||||
|
|
||||||
xcb_render_picture_t
|
xcb_render_picture_t
|
||||||
x_create_picture_with_visual_and_pixmap(xcb_connection_t *c, xcb_visualid_t visual,
|
x_create_picture_with_visual_and_pixmap(struct x_connection *c, xcb_visualid_t visual,
|
||||||
xcb_pixmap_t pixmap, uint32_t valuemask,
|
xcb_pixmap_t pixmap, uint32_t valuemask,
|
||||||
const xcb_render_create_picture_value_list_t *attr) {
|
const xcb_render_create_picture_value_list_t *attr) {
|
||||||
const xcb_render_pictforminfo_t *pictfmt = x_get_pictform_for_visual(c, visual);
|
const xcb_render_pictforminfo_t *pictfmt = x_get_pictform_for_visual(c, visual);
|
||||||
@@ -314,7 +376,7 @@ x_create_picture_with_visual_and_pixmap(xcb_connection_t *c, xcb_visualid_t visu
|
|||||||
}
|
}
|
||||||
|
|
||||||
xcb_render_picture_t
|
xcb_render_picture_t
|
||||||
x_create_picture_with_standard_and_pixmap(xcb_connection_t *c, xcb_pict_standard_t standard,
|
x_create_picture_with_standard_and_pixmap(struct x_connection *c, xcb_pict_standard_t standard,
|
||||||
xcb_pixmap_t pixmap, uint32_t valuemask,
|
xcb_pixmap_t pixmap, uint32_t valuemask,
|
||||||
const xcb_render_create_picture_value_list_t *attr) {
|
const xcb_render_create_picture_value_list_t *attr) {
|
||||||
x_get_server_pictfmts(c);
|
x_get_server_pictfmts(c);
|
||||||
@@ -325,26 +387,26 @@ x_create_picture_with_standard_and_pixmap(xcb_connection_t *c, xcb_pict_standard
|
|||||||
}
|
}
|
||||||
|
|
||||||
xcb_render_picture_t
|
xcb_render_picture_t
|
||||||
x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int h,
|
x_create_picture_with_standard(struct x_connection *c, int w, int h,
|
||||||
xcb_pict_standard_t standard, uint32_t valuemask,
|
xcb_pict_standard_t standard, uint32_t valuemask,
|
||||||
const xcb_render_create_picture_value_list_t *attr) {
|
const xcb_render_create_picture_value_list_t *attr) {
|
||||||
x_get_server_pictfmts(c);
|
x_get_server_pictfmts(c);
|
||||||
|
|
||||||
auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, standard);
|
auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, standard);
|
||||||
assert(pictfmt);
|
assert(pictfmt);
|
||||||
return x_create_picture_with_pictfmt(c, d, w, h, pictfmt, valuemask, attr);
|
return x_create_picture_with_pictfmt(c, w, h, pictfmt, valuemask, attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an picture.
|
* Create an picture.
|
||||||
*/
|
*/
|
||||||
xcb_render_picture_t
|
xcb_render_picture_t
|
||||||
x_create_picture_with_pictfmt(xcb_connection_t *c, xcb_drawable_t d, int w, int h,
|
x_create_picture_with_pictfmt(struct x_connection *c, int w, int h,
|
||||||
const xcb_render_pictforminfo_t *pictfmt, uint32_t valuemask,
|
const xcb_render_pictforminfo_t *pictfmt, uint32_t valuemask,
|
||||||
const xcb_render_create_picture_value_list_t *attr) {
|
const xcb_render_create_picture_value_list_t *attr) {
|
||||||
uint8_t depth = pictfmt->depth;
|
uint8_t depth = pictfmt->depth;
|
||||||
|
|
||||||
xcb_pixmap_t tmp_pixmap = x_create_pixmap(c, depth, d, w, h);
|
xcb_pixmap_t tmp_pixmap = x_create_pixmap(c, depth, w, h);
|
||||||
if (!tmp_pixmap) {
|
if (!tmp_pixmap) {
|
||||||
return XCB_NONE;
|
return XCB_NONE;
|
||||||
}
|
}
|
||||||
@@ -352,23 +414,23 @@ x_create_picture_with_pictfmt(xcb_connection_t *c, xcb_drawable_t d, int w, int
|
|||||||
xcb_render_picture_t picture = x_create_picture_with_pictfmt_and_pixmap(
|
xcb_render_picture_t picture = x_create_picture_with_pictfmt_and_pixmap(
|
||||||
c, pictfmt, tmp_pixmap, valuemask, attr);
|
c, pictfmt, tmp_pixmap, valuemask, attr);
|
||||||
|
|
||||||
xcb_free_pixmap(c, tmp_pixmap);
|
set_cant_fail_cookie(c, xcb_free_pixmap(c->c, tmp_pixmap));
|
||||||
|
|
||||||
return picture;
|
return picture;
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_render_picture_t
|
xcb_render_picture_t
|
||||||
x_create_picture_with_visual(xcb_connection_t *c, xcb_drawable_t d, int w, int h,
|
x_create_picture_with_visual(struct x_connection *c, int w, int h, xcb_visualid_t visual,
|
||||||
xcb_visualid_t visual, uint32_t valuemask,
|
uint32_t valuemask,
|
||||||
const xcb_render_create_picture_value_list_t *attr) {
|
const xcb_render_create_picture_value_list_t *attr) {
|
||||||
auto pictfmt = x_get_pictform_for_visual(c, visual);
|
auto pictfmt = x_get_pictform_for_visual(c, visual);
|
||||||
return x_create_picture_with_pictfmt(c, d, w, h, pictfmt, valuemask, attr);
|
return x_create_picture_with_pictfmt(c, w, h, pictfmt, valuemask, attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool x_fetch_region(xcb_connection_t *c, xcb_xfixes_region_t r, pixman_region32_t *res) {
|
bool x_fetch_region(struct x_connection *c, xcb_xfixes_region_t r, pixman_region32_t *res) {
|
||||||
xcb_generic_error_t *e = NULL;
|
xcb_generic_error_t *e = NULL;
|
||||||
xcb_xfixes_fetch_region_reply_t *xr =
|
xcb_xfixes_fetch_region_reply_t *xr =
|
||||||
xcb_xfixes_fetch_region_reply(c, xcb_xfixes_fetch_region(c, r), &e);
|
xcb_xfixes_fetch_region_reply(c->c, xcb_xfixes_fetch_region(c->c, r), &e);
|
||||||
if (!xr) {
|
if (!xr) {
|
||||||
log_error_x_error(e, "Failed to fetch rectangles");
|
log_error_x_error(e, "Failed to fetch rectangles");
|
||||||
return false;
|
return false;
|
||||||
@@ -389,7 +451,35 @@ bool x_fetch_region(xcb_connection_t *c, xcb_xfixes_region_t r, pixman_region32_
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t x_create_region(xcb_connection_t *c, const region_t *reg) {
|
bool x_set_region(struct x_connection *c, xcb_xfixes_region_t dst, const region_t *src) {
|
||||||
|
if (!src || dst == XCB_NONE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t nrects = 0;
|
||||||
|
const rect_t *rects = pixman_region32_rectangles((region_t *)src, &nrects);
|
||||||
|
if (!rects || nrects < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb_rectangle_t *xrects = ccalloc(nrects, xcb_rectangle_t);
|
||||||
|
for (int32_t i = 0; i < nrects; i++) {
|
||||||
|
xrects[i] =
|
||||||
|
(xcb_rectangle_t){.x = to_i16_checked(rects[i].x1),
|
||||||
|
.y = to_i16_checked(rects[i].y1),
|
||||||
|
.width = to_u16_checked(rects[i].x2 - rects[i].x1),
|
||||||
|
.height = to_u16_checked(rects[i].y2 - rects[i].y1)};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success =
|
||||||
|
XCB_AWAIT_VOID(xcb_xfixes_set_region, c->c, dst, to_u32_checked(nrects), xrects);
|
||||||
|
|
||||||
|
free(xrects);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t x_create_region(struct x_connection *c, const region_t *reg) {
|
||||||
if (!reg) {
|
if (!reg) {
|
||||||
return XCB_NONE;
|
return XCB_NONE;
|
||||||
}
|
}
|
||||||
@@ -409,8 +499,8 @@ uint32_t x_create_region(xcb_connection_t *c, const region_t *reg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
xcb_xfixes_region_t ret = x_new_id(c);
|
xcb_xfixes_region_t ret = x_new_id(c);
|
||||||
bool success =
|
bool success = XCB_AWAIT_VOID(xcb_xfixes_create_region, c->c, ret,
|
||||||
XCB_AWAIT_VOID(xcb_xfixes_create_region, c, ret, to_u32_checked(nrects), xrects);
|
to_u32_checked(nrects), xrects);
|
||||||
free(xrects);
|
free(xrects);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
return XCB_NONE;
|
return XCB_NONE;
|
||||||
@@ -418,13 +508,13 @@ uint32_t x_create_region(xcb_connection_t *c, const region_t *reg) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void x_destroy_region(xcb_connection_t *c, xcb_xfixes_region_t r) {
|
void x_destroy_region(struct x_connection *c, xcb_xfixes_region_t r) {
|
||||||
if (r != XCB_NONE) {
|
if (r != XCB_NONE) {
|
||||||
xcb_xfixes_destroy_region(c, r);
|
set_debug_cant_fail_cookie(c, xcb_xfixes_destroy_region(c->c, r));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void x_set_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict,
|
void x_set_picture_clip_region(struct x_connection *c, xcb_render_picture_t pict,
|
||||||
int16_t clip_x_origin, int16_t clip_y_origin,
|
int16_t clip_x_origin, int16_t clip_y_origin,
|
||||||
const region_t *reg) {
|
const region_t *reg) {
|
||||||
int nrects;
|
int nrects;
|
||||||
@@ -439,9 +529,10 @@ void x_set_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict,
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_generic_error_t *e = xcb_request_check(
|
xcb_generic_error_t *e =
|
||||||
c, xcb_render_set_picture_clip_rectangles_checked(
|
xcb_request_check(c->c, xcb_render_set_picture_clip_rectangles_checked(
|
||||||
c, pict, clip_x_origin, clip_y_origin, to_u32_checked(nrects), xrects));
|
c->c, pict, clip_x_origin, clip_y_origin,
|
||||||
|
to_u32_checked(nrects), xrects));
|
||||||
if (e) {
|
if (e) {
|
||||||
log_error_x_error(e, "Failed to set clip region");
|
log_error_x_error(e, "Failed to set clip region");
|
||||||
free(e);
|
free(e);
|
||||||
@@ -449,17 +540,28 @@ void x_set_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict,
|
|||||||
free(xrects);
|
free(xrects);
|
||||||
}
|
}
|
||||||
|
|
||||||
void x_clear_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict) {
|
void x_clear_picture_clip_region(struct x_connection *c, xcb_render_picture_t pict) {
|
||||||
assert(pict != XCB_NONE);
|
assert(pict != XCB_NONE);
|
||||||
xcb_render_change_picture_value_list_t v = {.clipmask = XCB_NONE};
|
xcb_render_change_picture_value_list_t v = {.clipmask = XCB_NONE};
|
||||||
xcb_generic_error_t *e = xcb_request_check(
|
xcb_generic_error_t *e = xcb_request_check(
|
||||||
c, xcb_render_change_picture_checked(c, pict, XCB_RENDER_CP_CLIP_MASK, &v));
|
c->c, xcb_render_change_picture_checked(c->c, pict, XCB_RENDER_CP_CLIP_MASK, &v));
|
||||||
if (e) {
|
if (e) {
|
||||||
log_error_x_error(e, "failed to clear clip region");
|
log_error_x_error(e, "failed to clear clip region");
|
||||||
free(e);
|
free(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy a <code>Picture</code>.
|
||||||
|
*
|
||||||
|
* Picture must be valid.
|
||||||
|
*/
|
||||||
|
void x_free_picture(struct x_connection *c, xcb_render_picture_t p) {
|
||||||
|
assert(p != XCB_NONE);
|
||||||
|
auto cookie = xcb_render_free_picture(c->c, p);
|
||||||
|
set_debug_cant_fail_cookie(c, cookie);
|
||||||
|
}
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
XSyncBadCounter = 0,
|
XSyncBadCounter = 0,
|
||||||
XSyncBadAlarm = 1,
|
XSyncBadAlarm = 1,
|
||||||
@@ -480,9 +582,7 @@ _x_strerror(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_c
|
|||||||
const char *name = "Unknown";
|
const char *name = "Unknown";
|
||||||
|
|
||||||
#define CASESTRRET(s) \
|
#define CASESTRRET(s) \
|
||||||
case s: \
|
case s: name = #s; break
|
||||||
name = #s; \
|
|
||||||
break
|
|
||||||
|
|
||||||
#define CASESTRRET2(s) \
|
#define CASESTRRET2(s) \
|
||||||
case XCB_##s: name = #s; break
|
case XCB_##s: name = #s; break
|
||||||
@@ -592,12 +692,12 @@ const char *x_strerror(xcb_generic_error_t *e) {
|
|||||||
/**
|
/**
|
||||||
* Create a pixmap and check that creation succeeded.
|
* Create a pixmap and check that creation succeeded.
|
||||||
*/
|
*/
|
||||||
xcb_pixmap_t x_create_pixmap(xcb_connection_t *c, uint8_t depth, xcb_drawable_t drawable,
|
xcb_pixmap_t x_create_pixmap(struct x_connection *c, uint8_t depth, int width, int height) {
|
||||||
int width, int height) {
|
|
||||||
xcb_pixmap_t pix = x_new_id(c);
|
xcb_pixmap_t pix = x_new_id(c);
|
||||||
xcb_void_cookie_t cookie = xcb_create_pixmap_checked(
|
xcb_void_cookie_t cookie =
|
||||||
c, depth, pix, drawable, to_u16_checked(width), to_u16_checked(height));
|
xcb_create_pixmap_checked(c->c, depth, pix, c->screen_info->root,
|
||||||
xcb_generic_error_t *err = xcb_request_check(c, cookie);
|
to_u16_checked(width), to_u16_checked(height));
|
||||||
|
xcb_generic_error_t *err = xcb_request_check(c->c, cookie);
|
||||||
if (err == NULL) {
|
if (err == NULL) {
|
||||||
return pix;
|
return pix;
|
||||||
}
|
}
|
||||||
@@ -607,42 +707,26 @@ xcb_pixmap_t x_create_pixmap(xcb_connection_t *c, uint8_t depth, xcb_drawable_t
|
|||||||
return XCB_NONE;
|
return XCB_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// We don't use the _XSETROOT_ID root window property as a source of the background
|
||||||
* Validate a pixmap.
|
/// pixmap because it most likely points to a dummy pixmap used to keep the colormap
|
||||||
*
|
/// associated with the background pixmap alive but we listen for it's changes and update
|
||||||
* Detect whether the pixmap is valid with XGetGeometry. Well, maybe there
|
/// the background pixmap accordingly.
|
||||||
* are better ways.
|
///
|
||||||
*/
|
/// For details on the _XSETROOT_ID root window property and it's usage see:
|
||||||
bool x_validate_pixmap(xcb_connection_t *c, xcb_pixmap_t pixmap) {
|
/// https://metacpan.org/pod/X11::Protocol::XSetRoot#_XSETROOT_ID
|
||||||
if (pixmap == XCB_NONE) {
|
/// https://gitlab.freedesktop.org/xorg/app/xsetroot/-/blob/435d35409768de7cbc2c47a6322192dd4b480545/xsetroot.c#L318-352
|
||||||
return false;
|
/// https://github.com/ImageMagick/ImageMagick/blob/d04a47227637dbb3af9231b0107ccf9677bf985e/MagickCore/xwindow.c#L9203-L9260
|
||||||
}
|
/// https://github.com/ImageMagick/ImageMagick/blob/d04a47227637dbb3af9231b0107ccf9677bf985e/MagickCore/xwindow.c#L1853-L1922
|
||||||
|
/// https://www.fvwm.org/Archive/Manpages/fvwm-root.html
|
||||||
|
|
||||||
auto r = xcb_get_geometry_reply(c, xcb_get_geometry(c, pixmap), NULL);
|
xcb_pixmap_t x_get_root_back_pixmap(struct x_connection *c, struct atom *atoms) {
|
||||||
if (!r) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ret = r->width && r->height;
|
|
||||||
free(r);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
/// Names of root window properties that could point to a pixmap of
|
|
||||||
/// background.
|
|
||||||
static const char *background_props_str[] = {
|
|
||||||
"_XROOTPMAP_ID",
|
|
||||||
"_XSETROOT_ID",
|
|
||||||
0,
|
|
||||||
};
|
|
||||||
|
|
||||||
xcb_pixmap_t
|
|
||||||
x_get_root_back_pixmap(xcb_connection_t *c, xcb_window_t root, struct atom *atoms) {
|
|
||||||
xcb_pixmap_t pixmap = XCB_NONE;
|
xcb_pixmap_t pixmap = XCB_NONE;
|
||||||
|
|
||||||
// Get the values of background attributes
|
xcb_atom_t root_back_pixmap_atoms[] = {atoms->a_XROOTPMAP_ID, atoms->aESETROOT_PMAP_ID};
|
||||||
for (int p = 0; background_props_str[p]; p++) {
|
for (size_t i = 0; i < ARR_SIZE(root_back_pixmap_atoms); i++) {
|
||||||
xcb_atom_t prop_atom = get_atom(atoms, background_props_str[p]);
|
winprop_t prop =
|
||||||
winprop_t prop = x_get_prop(c, root, prop_atom, 1, XCB_ATOM_PIXMAP, 32);
|
x_get_prop(c, c->screen_info->root, root_back_pixmap_atoms[i], 1,
|
||||||
|
XCB_ATOM_PIXMAP, 32);
|
||||||
if (prop.nitems) {
|
if (prop.nitems) {
|
||||||
pixmap = (xcb_pixmap_t)*prop.p32;
|
pixmap = (xcb_pixmap_t)*prop.p32;
|
||||||
free_winprop(&prop);
|
free_winprop(&prop);
|
||||||
@@ -655,37 +739,32 @@ x_get_root_back_pixmap(xcb_connection_t *c, xcb_window_t root, struct atom *atom
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool x_is_root_back_pixmap_atom(struct atom *atoms, xcb_atom_t atom) {
|
bool x_is_root_back_pixmap_atom(struct atom *atoms, xcb_atom_t atom) {
|
||||||
for (int p = 0; background_props_str[p]; p++) {
|
return atom == atoms->a_XROOTPMAP_ID || atom == atoms->aESETROOT_PMAP_ID ||
|
||||||
xcb_atom_t prop_atom = get_atom(atoms, background_props_str[p]);
|
atom == atoms->a_XSETROOT_ID;
|
||||||
if (prop_atom == atom) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronizes a X Render drawable to ensure all pending painting requests
|
* Synchronizes a X Render drawable to ensure all pending painting requests
|
||||||
* are completed.
|
* are completed.
|
||||||
*/
|
*/
|
||||||
bool x_fence_sync(xcb_connection_t *c, xcb_sync_fence_t f) {
|
bool x_fence_sync(struct x_connection *c, xcb_sync_fence_t f) {
|
||||||
// TODO(richardgv): If everybody just follows the rules stated in X Sync
|
// TODO(richardgv): If everybody just follows the rules stated in X Sync
|
||||||
// prototype, we need only one fence per screen, but let's stay a bit
|
// prototype, we need only one fence per screen, but let's stay a bit
|
||||||
// cautious right now
|
// cautious right now
|
||||||
|
|
||||||
auto e = xcb_request_check(c, xcb_sync_trigger_fence_checked(c, f));
|
auto e = xcb_request_check(c->c, xcb_sync_trigger_fence_checked(c->c, f));
|
||||||
if (e) {
|
if (e) {
|
||||||
log_error_x_error(e, "Failed to trigger the fence");
|
log_error_x_error(e, "Failed to trigger the fence");
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
e = xcb_request_check(c, xcb_sync_await_fence_checked(c, 1, &f));
|
e = xcb_request_check(c->c, xcb_sync_await_fence_checked(c->c, 1, &f));
|
||||||
if (e) {
|
if (e) {
|
||||||
log_error_x_error(e, "Failed to await on a fence");
|
log_error_x_error(e, "Failed to await on a fence");
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
e = xcb_request_check(c, xcb_sync_reset_fence_checked(c, f));
|
e = xcb_request_check(c->c, xcb_sync_reset_fence_checked(c->c, f));
|
||||||
if (e) {
|
if (e) {
|
||||||
log_error_x_error(e, "Failed to reset the fence");
|
log_error_x_error(e, "Failed to reset the fence");
|
||||||
goto err;
|
goto err;
|
||||||
@@ -697,6 +776,31 @@ err:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void x_request_vblank_event(struct x_connection *c, xcb_window_t window, uint64_t msc) {
|
||||||
|
auto cookie = xcb_present_notify_msc(c->c, window, 0, msc, 1, 0);
|
||||||
|
set_cant_fail_cookie(c, cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool dpms_screen_is_off(xcb_dpms_info_reply_t *info) {
|
||||||
|
// state is a bool indicating whether dpms is enabled
|
||||||
|
return info->state && (info->power_level != XCB_DPMS_DPMS_MODE_ON);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool x_check_dpms_status(struct x_connection *c, bool *screen_is_off) {
|
||||||
|
auto r = xcb_dpms_info_reply(c->c, xcb_dpms_info(c->c), NULL);
|
||||||
|
if (!r) {
|
||||||
|
log_error("Failed to query DPMS status.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto now_screen_is_off = dpms_screen_is_off(r);
|
||||||
|
if (*screen_is_off != now_screen_is_off) {
|
||||||
|
log_debug("Screen is now %s", now_screen_is_off ? "off" : "on");
|
||||||
|
*screen_is_off = now_screen_is_off;
|
||||||
|
}
|
||||||
|
free(r);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a struct conv to a X picture convolution filter, normalizing the kernel
|
* Convert a struct conv to a X picture convolution filter, normalizing the kernel
|
||||||
* in the process. Allow the caller to specify the element at the center of the kernel,
|
* in the process. Allow the caller to specify the element at the center of the kernel,
|
||||||
@@ -747,10 +851,10 @@ void x_create_convolution_kernel(const conv *kernel, double center,
|
|||||||
|
|
||||||
/// Generate a search criteria for fbconfig from a X visual.
|
/// Generate a search criteria for fbconfig from a X visual.
|
||||||
/// Returns {-1, -1, -1, -1, -1, 0} on failure
|
/// Returns {-1, -1, -1, -1, -1, 0} on failure
|
||||||
struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual) {
|
struct xvisual_info x_get_visual_info(struct x_connection *c, xcb_visualid_t visual) {
|
||||||
auto pictfmt = x_get_pictform_for_visual(c, visual);
|
auto pictfmt = x_get_pictform_for_visual(c, visual);
|
||||||
auto depth = x_get_visual_depth(c, visual);
|
auto depth = xcb_aux_get_depth_of_visual(c->screen_info, visual);
|
||||||
if (!pictfmt || depth == -1) {
|
if (!pictfmt || depth == 0) {
|
||||||
log_error("Invalid visual %#03x", visual);
|
log_error("Invalid visual %#03x", visual);
|
||||||
return (struct xvisual_info){-1, -1, -1, -1, -1, 0};
|
return (struct xvisual_info){-1, -1, -1, -1, -1, 0};
|
||||||
}
|
}
|
||||||
@@ -775,15 +879,34 @@ struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen) {
|
void x_update_monitors(struct x_connection *c, struct x_monitors *m) {
|
||||||
xcb_screen_iterator_t iter;
|
x_free_monitor_info(m);
|
||||||
|
|
||||||
iter = xcb_setup_roots_iterator(xcb_get_setup(c));
|
xcb_randr_get_monitors_reply_t *r = xcb_randr_get_monitors_reply(
|
||||||
for (; iter.rem; --screen, xcb_screen_next(&iter)) {
|
c->c, xcb_randr_get_monitors(c->c, c->screen_info->root, true), NULL);
|
||||||
if (screen == 0) {
|
if (!r) {
|
||||||
return iter.data;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
m->count = xcb_randr_get_monitors_monitors_length(r);
|
||||||
|
m->regions = ccalloc(m->count, region_t);
|
||||||
|
xcb_randr_monitor_info_iterator_t monitor_info_it =
|
||||||
|
xcb_randr_get_monitors_monitors_iterator(r);
|
||||||
|
for (int i = 0; monitor_info_it.rem; xcb_randr_monitor_info_next(&monitor_info_it)) {
|
||||||
|
xcb_randr_monitor_info_t *mi = monitor_info_it.data;
|
||||||
|
pixman_region32_init_rect(&m->regions[i++], mi->x, mi->y, mi->width, mi->height);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
void x_free_monitor_info(struct x_monitors *m) {
|
||||||
|
if (m->regions) {
|
||||||
|
for (int i = 0; i < m->count; i++) {
|
||||||
|
pixman_region32_fini(&m->regions[i]);
|
||||||
|
}
|
||||||
|
free(m->regions);
|
||||||
|
m->regions = NULL;
|
||||||
|
}
|
||||||
|
m->count = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
210
src/x.h
210
src/x.h
@@ -1,6 +1,7 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
// Copyright (c) 2018 Yuxuan Shui <yshuiv7@gmail.com>
|
// Copyright (c) 2018 Yuxuan Shui <yshuiv7@gmail.com>
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <X11/Xlib.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -26,6 +27,7 @@ typedef struct winprop {
|
|||||||
int16_t *p16;
|
int16_t *p16;
|
||||||
int32_t *p32;
|
int32_t *p32;
|
||||||
uint32_t *c32; // 32bit cardinal
|
uint32_t *c32; // 32bit cardinal
|
||||||
|
xcb_atom_t *atom;
|
||||||
};
|
};
|
||||||
unsigned long nitems;
|
unsigned long nitems;
|
||||||
xcb_atom_t type;
|
xcb_atom_t type;
|
||||||
@@ -55,6 +57,42 @@ struct xvisual_info {
|
|||||||
xcb_visualid_t visual;
|
xcb_visualid_t visual;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum pending_reply_action {
|
||||||
|
PENDING_REPLY_ACTION_IGNORE,
|
||||||
|
PENDING_REPLY_ACTION_ABORT,
|
||||||
|
PENDING_REPLY_ACTION_DEBUG_ABORT,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct pending_reply {
|
||||||
|
struct pending_reply *next;
|
||||||
|
unsigned long sequence;
|
||||||
|
enum pending_reply_action action;
|
||||||
|
} pending_reply_t;
|
||||||
|
|
||||||
|
struct x_connection {
|
||||||
|
/// XCB connection.
|
||||||
|
xcb_connection_t *c;
|
||||||
|
/// Display in use.
|
||||||
|
Display *dpy;
|
||||||
|
/// Head pointer of the error ignore linked list.
|
||||||
|
pending_reply_t *pending_reply_head;
|
||||||
|
/// Pointer to the <code>next</code> member of tail element of the error
|
||||||
|
/// ignore linked list.
|
||||||
|
pending_reply_t **pending_reply_tail;
|
||||||
|
/// Previous handler of X errors
|
||||||
|
XErrorHandler previous_xerror_handler;
|
||||||
|
/// Default screen
|
||||||
|
int screen;
|
||||||
|
/// Information about the default screen
|
||||||
|
xcb_screen_t *screen_info;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Monitor info
|
||||||
|
struct x_monitors {
|
||||||
|
int count;
|
||||||
|
region_t *regions;
|
||||||
|
};
|
||||||
|
|
||||||
#define XCB_AWAIT_VOID(func, c, ...) \
|
#define XCB_AWAIT_VOID(func, c, ...) \
|
||||||
({ \
|
({ \
|
||||||
bool __success = true; \
|
bool __success = true; \
|
||||||
@@ -92,8 +130,8 @@ struct xvisual_info {
|
|||||||
#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536))
|
#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536))
|
||||||
|
|
||||||
/// Wraps x_new_id. abort the program if x_new_id returns error
|
/// Wraps x_new_id. abort the program if x_new_id returns error
|
||||||
static inline uint32_t x_new_id(xcb_connection_t *c) {
|
static inline uint32_t x_new_id(struct x_connection *c) {
|
||||||
auto ret = xcb_generate_id(c);
|
auto ret = xcb_generate_id(c->c);
|
||||||
if (ret == (uint32_t)-1) {
|
if (ret == (uint32_t)-1) {
|
||||||
log_fatal("We seems to have run of XIDs. This is either a bug in the X "
|
log_fatal("We seems to have run of XIDs. This is either a bug in the X "
|
||||||
"server, or a resource leakage in the compositor. Please open "
|
"server, or a resource leakage in the compositor. Please open "
|
||||||
@@ -103,17 +141,73 @@ static inline uint32_t x_new_id(xcb_connection_t *c) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static void set_reply_action(struct x_connection *c, uint32_t sequence,
|
||||||
* Send a request to X server and get the reply to make sure all previous
|
enum pending_reply_action action) {
|
||||||
* requests are processed, and their replies received
|
auto i = cmalloc(pending_reply_t);
|
||||||
*
|
|
||||||
* xcb_get_input_focus is used here because it is the same request used by
|
i->sequence = sequence;
|
||||||
* libX11
|
i->next = 0;
|
||||||
*/
|
i->action = action;
|
||||||
static inline void x_sync(xcb_connection_t *c) {
|
*c->pending_reply_tail = i;
|
||||||
free(xcb_get_input_focus_reply(c, xcb_get_input_focus(c), NULL));
|
c->pending_reply_tail = &i->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ignore X errors caused by given X request.
|
||||||
|
*/
|
||||||
|
static inline void attr_unused set_ignore_cookie(struct x_connection *c,
|
||||||
|
xcb_void_cookie_t cookie) {
|
||||||
|
set_reply_action(c, cookie.sequence, PENDING_REPLY_ACTION_IGNORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void attr_unused set_cant_fail_cookie(struct x_connection *c,
|
||||||
|
xcb_void_cookie_t cookie) {
|
||||||
|
set_reply_action(c, cookie.sequence, PENDING_REPLY_ACTION_ABORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void attr_unused set_debug_cant_fail_cookie(struct x_connection *c,
|
||||||
|
xcb_void_cookie_t cookie) {
|
||||||
|
#ifndef NDEBUG
|
||||||
|
set_reply_action(c, cookie.sequence, PENDING_REPLY_ACTION_DEBUG_ABORT);
|
||||||
|
#else
|
||||||
|
(void)c;
|
||||||
|
(void)cookie;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void attr_unused free_x_connection(struct x_connection *c) {
|
||||||
|
pending_reply_t *next = NULL;
|
||||||
|
for (auto ign = c->pending_reply_head; ign; ign = next) {
|
||||||
|
next = ign->next;
|
||||||
|
|
||||||
|
free(ign);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset head and tail
|
||||||
|
c->pending_reply_head = NULL;
|
||||||
|
c->pending_reply_tail = &c->pending_reply_head;
|
||||||
|
|
||||||
|
XSetErrorHandler(c->previous_xerror_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize x_connection struct from an Xlib Display.
|
||||||
|
///
|
||||||
|
/// Note this function doesn't take ownership of the Display, the caller is still
|
||||||
|
/// responsible for closing it after `free_x_connection` is called.
|
||||||
|
void x_connection_init(struct x_connection *c, Display *dpy);
|
||||||
|
|
||||||
|
/// Discard queued pending replies.
|
||||||
|
///
|
||||||
|
/// We have received reply with sequence number `sequence`, which means all pending
|
||||||
|
/// replies with sequence number less than `sequence` will never be received. So discard
|
||||||
|
/// them.
|
||||||
|
void x_discard_pending(struct x_connection *c, uint32_t sequence);
|
||||||
|
|
||||||
|
/// Handle X errors.
|
||||||
|
///
|
||||||
|
/// This function logs X errors, or aborts the program based on severity of the error.
|
||||||
|
void x_handle_error(struct x_connection *c, xcb_generic_error_t *ev);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a specific attribute of a window.
|
* Get a specific attribute of a window.
|
||||||
*
|
*
|
||||||
@@ -129,25 +223,26 @@ static inline void x_sync(xcb_connection_t *c) {
|
|||||||
* @return a <code>winprop_t</code> structure containing the attribute
|
* @return a <code>winprop_t</code> structure containing the attribute
|
||||||
* and number of items. A blank one on failure.
|
* and number of items. A blank one on failure.
|
||||||
*/
|
*/
|
||||||
winprop_t x_get_prop_with_offset(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom,
|
winprop_t x_get_prop_with_offset(const struct x_connection *c, xcb_window_t w, xcb_atom_t atom,
|
||||||
int offset, int length, xcb_atom_t rtype, int rformat);
|
int offset, int length, xcb_atom_t rtype, int rformat);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper of wid_get_prop_adv().
|
* Wrapper of wid_get_prop_adv().
|
||||||
*/
|
*/
|
||||||
static inline winprop_t x_get_prop(xcb_connection_t *c, xcb_window_t wid, xcb_atom_t atom,
|
static inline winprop_t
|
||||||
int length, xcb_atom_t rtype, int rformat) {
|
x_get_prop(const struct x_connection *c, xcb_window_t wid, xcb_atom_t atom, int length,
|
||||||
|
xcb_atom_t rtype, int rformat) {
|
||||||
return x_get_prop_with_offset(c, wid, atom, 0L, length, rtype, rformat);
|
return x_get_prop_with_offset(c, wid, atom, 0L, length, rtype, rformat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the type, format and size in bytes of a window's specific attribute.
|
/// Get the type, format and size in bytes of a window's specific attribute.
|
||||||
winprop_info_t x_get_prop_info(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom);
|
winprop_info_t x_get_prop_info(const struct x_connection *c, xcb_window_t w, xcb_atom_t atom);
|
||||||
|
|
||||||
/// Discard all X events in queue or in flight. Should only be used when the server is
|
/// Discard all X events in queue or in flight. Should only be used when the server is
|
||||||
/// grabbed
|
/// grabbed
|
||||||
static inline void x_discard_events(xcb_connection_t *c) {
|
static inline void x_discard_events(struct x_connection *c) {
|
||||||
xcb_generic_event_t *e;
|
xcb_generic_event_t *e;
|
||||||
while ((e = xcb_poll_for_event(c))) {
|
while ((e = xcb_poll_for_event(c->c))) {
|
||||||
free(e);
|
free(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,7 +252,7 @@ static inline void x_discard_events(xcb_connection_t *c) {
|
|||||||
*
|
*
|
||||||
* @return the value if successful, 0 otherwise
|
* @return the value if successful, 0 otherwise
|
||||||
*/
|
*/
|
||||||
xcb_window_t wid_get_prop_window(xcb_connection_t *c, xcb_window_t wid, xcb_atom_t aprop);
|
xcb_window_t wid_get_prop_window(struct x_connection *c, xcb_window_t wid, xcb_atom_t aprop);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value of a text property of a window.
|
* Get the value of a text property of a window.
|
||||||
@@ -166,34 +261,33 @@ xcb_window_t wid_get_prop_window(xcb_connection_t *c, xcb_window_t wid, xcb_atom
|
|||||||
* array
|
* array
|
||||||
* @param[out] pnstr Number of strings in the array
|
* @param[out] pnstr Number of strings in the array
|
||||||
*/
|
*/
|
||||||
bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ***pstrlst,
|
bool wid_get_text_prop(struct x_connection *c, struct atom *atoms, xcb_window_t wid,
|
||||||
int *pnstr);
|
xcb_atom_t prop, char ***pstrlst, int *pnstr);
|
||||||
|
|
||||||
const xcb_render_pictforminfo_t *
|
const xcb_render_pictforminfo_t *
|
||||||
x_get_pictform_for_visual(xcb_connection_t *, xcb_visualid_t);
|
x_get_pictform_for_visual(struct x_connection *, xcb_visualid_t);
|
||||||
int x_get_visual_depth(xcb_connection_t *, xcb_visualid_t);
|
|
||||||
|
|
||||||
xcb_render_picture_t
|
xcb_render_picture_t
|
||||||
x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *,
|
x_create_picture_with_pictfmt_and_pixmap(struct x_connection *,
|
||||||
const xcb_render_pictforminfo_t *pictfmt,
|
const xcb_render_pictforminfo_t *pictfmt,
|
||||||
xcb_pixmap_t pixmap, uint32_t valuemask,
|
xcb_pixmap_t pixmap, uint32_t valuemask,
|
||||||
const xcb_render_create_picture_value_list_t *attr)
|
const xcb_render_create_picture_value_list_t *attr)
|
||||||
attr_nonnull(1, 2);
|
attr_nonnull(1, 2);
|
||||||
|
|
||||||
xcb_render_picture_t
|
xcb_render_picture_t
|
||||||
x_create_picture_with_visual_and_pixmap(xcb_connection_t *, xcb_visualid_t visual,
|
x_create_picture_with_visual_and_pixmap(struct x_connection *, xcb_visualid_t visual,
|
||||||
xcb_pixmap_t pixmap, uint32_t valuemask,
|
xcb_pixmap_t pixmap, uint32_t valuemask,
|
||||||
const xcb_render_create_picture_value_list_t *attr)
|
const xcb_render_create_picture_value_list_t *attr)
|
||||||
attr_nonnull(1);
|
attr_nonnull(1);
|
||||||
|
|
||||||
xcb_render_picture_t
|
xcb_render_picture_t
|
||||||
x_create_picture_with_standard_and_pixmap(xcb_connection_t *, xcb_pict_standard_t standard,
|
x_create_picture_with_standard_and_pixmap(struct x_connection *, xcb_pict_standard_t standard,
|
||||||
xcb_pixmap_t pixmap, uint32_t valuemask,
|
xcb_pixmap_t pixmap, uint32_t valuemask,
|
||||||
const xcb_render_create_picture_value_list_t *attr)
|
const xcb_render_create_picture_value_list_t *attr)
|
||||||
attr_nonnull(1);
|
attr_nonnull(1);
|
||||||
|
|
||||||
xcb_render_picture_t
|
xcb_render_picture_t
|
||||||
x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int h,
|
x_create_picture_with_standard(struct x_connection *c, int w, int h,
|
||||||
xcb_pict_standard_t standard, uint32_t valuemask,
|
xcb_pict_standard_t standard, uint32_t valuemask,
|
||||||
const xcb_render_create_picture_value_list_t *attr)
|
const xcb_render_create_picture_value_list_t *attr)
|
||||||
attr_nonnull(1);
|
attr_nonnull(1);
|
||||||
@@ -202,30 +296,40 @@ x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int
|
|||||||
* Create an picture.
|
* Create an picture.
|
||||||
*/
|
*/
|
||||||
xcb_render_picture_t
|
xcb_render_picture_t
|
||||||
x_create_picture_with_pictfmt(xcb_connection_t *, xcb_drawable_t, int w, int h,
|
x_create_picture_with_pictfmt(struct x_connection *, int w, int h,
|
||||||
const xcb_render_pictforminfo_t *pictfmt, uint32_t valuemask,
|
const xcb_render_pictforminfo_t *pictfmt, uint32_t valuemask,
|
||||||
const xcb_render_create_picture_value_list_t *attr)
|
const xcb_render_create_picture_value_list_t *attr)
|
||||||
attr_nonnull(1, 5);
|
attr_nonnull(1, 4);
|
||||||
|
|
||||||
xcb_render_picture_t
|
xcb_render_picture_t
|
||||||
x_create_picture_with_visual(xcb_connection_t *, xcb_drawable_t, int w, int h,
|
x_create_picture_with_visual(struct x_connection *, int w, int h, xcb_visualid_t visual,
|
||||||
xcb_visualid_t visual, uint32_t valuemask,
|
uint32_t valuemask,
|
||||||
const xcb_render_create_picture_value_list_t *attr)
|
const xcb_render_create_picture_value_list_t *attr)
|
||||||
attr_nonnull(1);
|
attr_nonnull(1);
|
||||||
|
|
||||||
/// Fetch a X region and store it in a pixman region
|
/// Fetch a X region and store it in a pixman region
|
||||||
bool x_fetch_region(xcb_connection_t *, xcb_xfixes_region_t r, region_t *res);
|
bool x_fetch_region(struct x_connection *, xcb_xfixes_region_t r, region_t *res);
|
||||||
|
|
||||||
|
/// Set an X region to a pixman region
|
||||||
|
bool x_set_region(struct x_connection *c, xcb_xfixes_region_t dst, const region_t *src);
|
||||||
|
|
||||||
/// Create a X region from a pixman region
|
/// Create a X region from a pixman region
|
||||||
uint32_t x_create_region(xcb_connection_t *c, const region_t *reg);
|
uint32_t x_create_region(struct x_connection *c, const region_t *reg);
|
||||||
|
|
||||||
/// Destroy a X region
|
/// Destroy a X region
|
||||||
void x_destroy_region(xcb_connection_t *c, uint32_t region);
|
void x_destroy_region(struct x_connection *c, uint32_t region);
|
||||||
|
|
||||||
void x_set_picture_clip_region(xcb_connection_t *, xcb_render_picture_t, int16_t clip_x_origin,
|
void x_set_picture_clip_region(struct x_connection *, xcb_render_picture_t,
|
||||||
int16_t clip_y_origin, const region_t *);
|
int16_t clip_x_origin, int16_t clip_y_origin, const region_t *);
|
||||||
|
|
||||||
void x_clear_picture_clip_region(xcb_connection_t *, xcb_render_picture_t pict);
|
void x_clear_picture_clip_region(struct x_connection *, xcb_render_picture_t pict);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy a <code>Picture</code>.
|
||||||
|
*
|
||||||
|
* Picture must be valid.
|
||||||
|
*/
|
||||||
|
void x_free_picture(struct x_connection *c, xcb_render_picture_t p);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log a X11 error
|
* Log a X11 error
|
||||||
@@ -242,10 +346,7 @@ void x_log_error(enum log_level level, unsigned long serial, uint8_t major,
|
|||||||
*/
|
*/
|
||||||
const char *x_strerror(xcb_generic_error_t *e);
|
const char *x_strerror(xcb_generic_error_t *e);
|
||||||
|
|
||||||
xcb_pixmap_t x_create_pixmap(xcb_connection_t *, uint8_t depth, xcb_drawable_t drawable,
|
xcb_pixmap_t x_create_pixmap(struct x_connection *, uint8_t depth, int width, int height);
|
||||||
int width, int height);
|
|
||||||
|
|
||||||
bool x_validate_pixmap(xcb_connection_t *, xcb_pixmap_t pxmap);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Free a <code>winprop_t</code>.
|
* Free a <code>winprop_t</code>.
|
||||||
@@ -254,22 +355,22 @@ bool x_validate_pixmap(xcb_connection_t *, xcb_pixmap_t pxmap);
|
|||||||
*/
|
*/
|
||||||
static inline void free_winprop(winprop_t *pprop) {
|
static inline void free_winprop(winprop_t *pprop) {
|
||||||
// Empty the whole structure to avoid possible issues
|
// Empty the whole structure to avoid possible issues
|
||||||
if (pprop->r)
|
if (pprop->r) {
|
||||||
free(pprop->r);
|
free(pprop->r);
|
||||||
|
}
|
||||||
pprop->ptr = NULL;
|
pprop->ptr = NULL;
|
||||||
pprop->r = NULL;
|
pprop->r = NULL;
|
||||||
pprop->nitems = 0;
|
pprop->nitems = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the back pixmap of the root window
|
/// Get the back pixmap of the root window
|
||||||
xcb_pixmap_t
|
xcb_pixmap_t x_get_root_back_pixmap(struct x_connection *c, struct atom *atoms);
|
||||||
x_get_root_back_pixmap(xcb_connection_t *c, xcb_window_t root, struct atom *atoms);
|
|
||||||
|
|
||||||
/// Return true if the atom refers to a property name that is used for the
|
/// Return true if the atom refers to a property name that is used for the
|
||||||
/// root window background pixmap
|
/// root window background pixmap
|
||||||
bool x_is_root_back_pixmap_atom(struct atom *atoms, xcb_atom_t atom);
|
bool x_is_root_back_pixmap_atom(struct atom *atoms, xcb_atom_t atom);
|
||||||
|
|
||||||
bool x_fence_sync(xcb_connection_t *, xcb_sync_fence_t);
|
bool x_fence_sync(struct x_connection *, xcb_sync_fence_t);
|
||||||
|
|
||||||
struct x_convolution_kernel {
|
struct x_convolution_kernel {
|
||||||
int size;
|
int size;
|
||||||
@@ -293,13 +394,26 @@ void attr_nonnull(1, 3) x_create_convolution_kernel(const conv *kernel, double c
|
|||||||
|
|
||||||
/// Generate a search criteria for fbconfig from a X visual.
|
/// Generate a search criteria for fbconfig from a X visual.
|
||||||
/// Returns {-1, -1, -1, -1, -1, -1} on failure
|
/// Returns {-1, -1, -1, -1, -1, -1} on failure
|
||||||
struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual);
|
struct xvisual_info x_get_visual_info(struct x_connection *c, xcb_visualid_t visual);
|
||||||
|
|
||||||
xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_t std);
|
xcb_visualid_t x_get_visual_for_standard(struct x_connection *c, xcb_pict_standard_t std);
|
||||||
|
|
||||||
|
xcb_visualid_t x_get_visual_for_depth(xcb_screen_t *screen, uint8_t depth);
|
||||||
|
|
||||||
xcb_render_pictformat_t
|
xcb_render_pictformat_t
|
||||||
x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std);
|
x_get_pictfmt_for_standard(struct x_connection *c, xcb_pict_standard_t std);
|
||||||
|
|
||||||
xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen);
|
/// Populates a `struct x_monitors` with the current monitor configuration.
|
||||||
|
void x_update_monitors(struct x_connection *, struct x_monitors *);
|
||||||
|
/// Free memory allocated for a `struct x_monitors`.
|
||||||
|
void x_free_monitor_info(struct x_monitors *);
|
||||||
|
|
||||||
uint32_t attr_deprecated xcb_generate_id(xcb_connection_t *c);
|
uint32_t attr_deprecated xcb_generate_id(xcb_connection_t *c);
|
||||||
|
|
||||||
|
/// Ask X server to send us a notification for the next end of vblank.
|
||||||
|
void x_request_vblank_event(struct x_connection *c, xcb_window_t window, uint64_t msc);
|
||||||
|
|
||||||
|
/// Update screen_is_off to reflect the current DPMS state.
|
||||||
|
///
|
||||||
|
/// Returns true if the DPMS state was successfully queried, false otherwise.
|
||||||
|
bool x_check_dpms_status(struct x_connection *c, bool *screen_is_off);
|
||||||
|
|||||||
@@ -58,6 +58,14 @@ struct test_file_metadata __attribute__((weak)) * test_file_head;
|
|||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
#define TEST_NOTEQUAL(a, b) \
|
||||||
|
do { \
|
||||||
|
if ((a) == (b)) { \
|
||||||
|
SET_FAILURE(#a " == " #b, false); \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
#define TEST_TRUE(a) \
|
#define TEST_TRUE(a) \
|
||||||
do { \
|
do { \
|
||||||
if (!(a)) { \
|
if (!(a)) { \
|
||||||
@@ -69,11 +77,13 @@ struct test_file_metadata __attribute__((weak)) * test_file_head;
|
|||||||
#define TEST_STREQUAL(a, b) \
|
#define TEST_STREQUAL(a, b) \
|
||||||
do { \
|
do { \
|
||||||
if (strcmp(a, b) != 0) { \
|
if (strcmp(a, b) != 0) { \
|
||||||
const char *part2 = " != " #b; \
|
const char *test_strequal__part2 = " != " #b; \
|
||||||
size_t len = strlen(a) + strlen(part2) + 3; \
|
size_t test_strequal__len = \
|
||||||
char *buf = malloc(len); \
|
strlen(a) + strlen(test_strequal__part2) + 3; \
|
||||||
snprintf(buf, len, "\"%s\"%s", a, part2); \
|
char *test_strequal__buf = malloc(test_strequal__len); \
|
||||||
SET_FAILURE(buf, true); \
|
snprintf(test_strequal__buf, test_strequal__len, "\"%s\"%s", a, \
|
||||||
|
test_strequal__part2); \
|
||||||
|
SET_FAILURE(test_strequal__buf, true); \
|
||||||
return; \
|
return; \
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
@@ -81,11 +91,27 @@ struct test_file_metadata __attribute__((weak)) * test_file_head;
|
|||||||
#define TEST_STRNEQUAL(a, b, len) \
|
#define TEST_STRNEQUAL(a, b, len) \
|
||||||
do { \
|
do { \
|
||||||
if (strncmp(a, b, len) != 0) { \
|
if (strncmp(a, b, len) != 0) { \
|
||||||
const char *part2 = " != " #b; \
|
const char *test_strnequal__part2 = " != " #b; \
|
||||||
size_t len2 = len + strlen(part2) + 3; \
|
size_t test_strnequal__len2 = \
|
||||||
char *buf = malloc(len2); \
|
len + strlen(test_strnequal__part2) + 3; \
|
||||||
snprintf(buf, len2, "\"%.*s\"%s", (int)len, a, part2); \
|
char *test_strnequal__buf = malloc(test_strnequal__len2); \
|
||||||
SET_FAILURE(buf, true); \
|
snprintf(test_strnequal__buf, test_strnequal__len2, \
|
||||||
|
"\"%.*s\"%s", (int)len, a, test_strnequal__part2); \
|
||||||
|
SET_FAILURE(test_strnequal__buf, true); \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define TEST_STREQUAL3(str, expected, len) \
|
||||||
|
do { \
|
||||||
|
if (len != strlen(expected) || strncmp(str, expected, len) != 0) { \
|
||||||
|
const char *test_strequal3__part2 = " != " #expected; \
|
||||||
|
size_t test_strequal3__len2 = \
|
||||||
|
len + strlen(test_strequal3__part2) + 3; \
|
||||||
|
char *test_strequal3__buf = malloc(test_strequal3__len2); \
|
||||||
|
snprintf(test_strequal3__buf, test_strequal3__len2, \
|
||||||
|
"\"%.*s\"%s", (int)len, str, test_strequal3__part2); \
|
||||||
|
SET_FAILURE(test_strequal3__buf, true); \
|
||||||
return; \
|
return; \
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
@@ -199,6 +225,9 @@ static inline void __attribute__((constructor(102))) run_tests(void) {
|
|||||||
#define TEST_EQUAL(a, b) \
|
#define TEST_EQUAL(a, b) \
|
||||||
(void)(a); \
|
(void)(a); \
|
||||||
(void)(b)
|
(void)(b)
|
||||||
|
#define TEST_NOTEQUAL(a, b) \
|
||||||
|
(void)(a); \
|
||||||
|
(void)(b)
|
||||||
#define TEST_TRUE(a) (void)(a)
|
#define TEST_TRUE(a) (void)(a)
|
||||||
#define TEST_STREQUAL(a, b) \
|
#define TEST_STREQUAL(a, b) \
|
||||||
(void)(a); \
|
(void)(a); \
|
||||||
@@ -207,5 +236,8 @@ static inline void __attribute__((constructor(102))) run_tests(void) {
|
|||||||
(void)(a); \
|
(void)(a); \
|
||||||
(void)(b); \
|
(void)(b); \
|
||||||
(void)(len)
|
(void)(len)
|
||||||
|
#define TEST_STREQUAL3(str, expected, len) \
|
||||||
|
(void)(str); \
|
||||||
|
(void)(expected); \
|
||||||
|
(void)(len)
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -64,8 +64,9 @@ shadow-exclude = [
|
|||||||
#
|
#
|
||||||
# shadow-exclude-reg = ""
|
# shadow-exclude-reg = ""
|
||||||
|
|
||||||
# Crop shadow of a window fully on a particular Xinerama screen to the screen.
|
# Crop shadow of a window fully on a particular monitor to that monitor. This is
|
||||||
# xinerama-shadow-crop = false
|
# currently implemented using the X RandR extension.
|
||||||
|
# crop-shadow-to-monitor = false
|
||||||
|
|
||||||
|
|
||||||
#################################
|
#################################
|
||||||
@@ -154,6 +155,11 @@ rounded-corners-exclude = [
|
|||||||
"window_type = 'desktop'"
|
"window_type = 'desktop'"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
corner-radius-rules = [
|
||||||
|
"10:window_type = 'dock'",
|
||||||
|
"0x32:window_type = 'desktop'"
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
#################################
|
#################################
|
||||||
# Background-Blurring #
|
# Background-Blurring #
|
||||||
|
|||||||
Reference in New Issue
Block a user