1/*
2 SPDX-FileCopyrightText: 2009 Marco Martin <notmart@gmail.com>
3 SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.1-or-later
6*/
7
8#include "kwindoweffects_x11.h"
9
10#include <QGuiApplication>
11#include <QVarLengthArray>
12
13#include "kx11extras.h"
14#include <config-kwindowsystem.h>
15
16#include <QMatrix4x4>
17#include <QWindow>
18#include <private/qtx11extras_p.h>
19
20#include <xcb/xcb.h>
21
22#include "cptr_p.h"
23#include <cmath>
24
25using namespace KWindowEffects;
26
27KWindowEffectsPrivateX11::KWindowEffectsPrivateX11()
28{
29}
30
31KWindowEffectsPrivateX11::~KWindowEffectsPrivateX11()
32{
33}
34
35bool KWindowEffectsPrivateX11::isEffectAvailable(Effect effect)
36{
37 if (!KX11Extras::self()->compositingActive()) {
38 return false;
39 }
40 QByteArray effectName;
41
42 switch (effect) {
43 case Slide:
44 effectName = QByteArrayLiteral("_KDE_SLIDE");
45 break;
46 case BlurBehind:
47 effectName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION");
48 break;
49 case BackgroundContrast:
50 effectName = QByteArrayLiteral("_KDE_NET_WM_BACKGROUND_CONTRAST_REGION");
51 break;
52 default:
53 return false;
54 }
55
56 // hackish way to find out if KWin has the effect enabled,
57 // TODO provide proper support
58 xcb_connection_t *c = QX11Info::connection();
59 xcb_list_properties_cookie_t propsCookie = xcb_list_properties_unchecked(c, window: QX11Info::appRootWindow());
60 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, only_if_exists: false, name_len: effectName.length(), name: effectName.constData());
61
62 UniqueCPointer<xcb_list_properties_reply_t> props(xcb_list_properties_reply(c, cookie: propsCookie, e: nullptr));
63 UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, cookie: atomCookie, e: nullptr));
64 if (!atom || !props) {
65 return false;
66 }
67 xcb_atom_t *atoms = xcb_list_properties_atoms(R: props.get());
68 for (int i = 0; i < props->atoms_len; ++i) {
69 if (atoms[i] == atom->atom) {
70 return true;
71 }
72 }
73 return false;
74}
75
76void KWindowEffectsPrivateX11::slideWindow(QWindow *window, SlideFromLocation location, int offset)
77{
78 xcb_connection_t *c = QX11Info::connection();
79 if (!c) {
80 return;
81 }
82
83 const QByteArray effectName = QByteArrayLiteral("_KDE_SLIDE");
84 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, only_if_exists: false, name_len: effectName.length(), name: effectName.constData());
85
86 const int size = 2;
87 int32_t data[size];
88 data[0] = offset;
89
90 switch (location) {
91 case LeftEdge:
92 data[1] = 0;
93 break;
94 case TopEdge:
95 data[1] = 1;
96 break;
97 case RightEdge:
98 data[1] = 2;
99 break;
100 case BottomEdge:
101 data[1] = 3;
102 default:
103 break;
104 }
105
106 UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, cookie: atomCookie, e: nullptr));
107 if (!atom) {
108 return;
109 }
110 if (location == NoEdge) {
111 xcb_delete_property(c, window: window->winId(), property: atom->atom);
112 } else {
113 xcb_change_property(c, mode: XCB_PROP_MODE_REPLACE, window: window->winId(), property: atom->atom, type: atom->atom, format: 32, data_len: size, data);
114 }
115}
116
117void KWindowEffectsPrivateX11::enableBlurBehind(QWindow *window, bool enable, const QRegion &region)
118{
119 xcb_connection_t *c = QX11Info::connection();
120 if (!c) {
121 return;
122 }
123 const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION");
124 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, only_if_exists: false, name_len: effectName.length(), name: effectName.constData());
125 UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, cookie: atomCookie, e: nullptr));
126 if (!atom) {
127 return;
128 }
129
130 if (enable) {
131 QList<uint32_t> data;
132 data.reserve(asize: region.rectCount() * 4);
133 for (const QRect &r : region) {
134 // kwin on X uses device pixels, convert from logical
135 auto dpr = qApp->devicePixelRatio();
136 data << std::floor(x: r.x() * dpr) << std::floor(x: r.y() * dpr) << std::ceil(x: r.width() * dpr) << std::ceil(x: r.height() * dpr);
137 }
138
139 xcb_change_property(c, mode: XCB_PROP_MODE_REPLACE, window: window->winId(), property: atom->atom, type: XCB_ATOM_CARDINAL, format: 32, data_len: data.size(), data: data.constData());
140 } else {
141 xcb_delete_property(c, window: window->winId(), property: atom->atom);
142 }
143}
144
145void KWindowEffectsPrivateX11::enableBackgroundContrast(QWindow *window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion &region)
146{
147 xcb_connection_t *c = QX11Info::connection();
148 const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_BACKGROUND_CONTRAST_REGION");
149 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, only_if_exists: false, name_len: effectName.length(), name: effectName.constData());
150 UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, cookie: atomCookie, e: nullptr));
151 if (!atom) {
152 return;
153 }
154
155 if (enable) {
156 QList<uint32_t> data;
157 data.reserve(asize: region.rectCount() * 4 + 16);
158 for (const QRect &r : region) {
159 auto dpr = qApp->devicePixelRatio();
160 data << std::floor(x: r.x() * dpr) << std::floor(x: r.y() * dpr) << std::ceil(x: r.width() * dpr) << std::ceil(x: r.height() * dpr);
161 }
162
163 QMatrix4x4 satMatrix; // saturation
164 QMatrix4x4 intMatrix; // intensity
165 QMatrix4x4 contMatrix; // contrast
166
167 // clang-format off
168
169 //Saturation matrix
170 if (!qFuzzyCompare(p1: saturation, p2: 1.0)) {
171 const qreal rval = (1.0 - saturation) * .2126;
172 const qreal gval = (1.0 - saturation) * .7152;
173 const qreal bval = (1.0 - saturation) * .0722;
174
175 satMatrix = QMatrix4x4(rval + saturation, rval, rval, 0.0,
176 gval, gval + saturation, gval, 0.0,
177 bval, bval, bval + saturation, 0.0,
178 0, 0, 0, 1.0);
179 }
180
181 //IntensityMatrix
182 if (!qFuzzyCompare(p1: intensity, p2: 1.0)) {
183 intMatrix.scale(x: intensity, y: intensity, z: intensity);
184 }
185
186 //Contrast Matrix
187 if (!qFuzzyCompare(p1: contrast, p2: 1.0)) {
188 const float transl = (1.0 - contrast) / 2.0;
189
190 contMatrix = QMatrix4x4(contrast, 0, 0, 0.0,
191 0, contrast, 0, 0.0,
192 0, 0, contrast, 0.0,
193 transl, transl, transl, 1.0);
194 }
195
196 // clang-format on
197
198 QMatrix4x4 colorMatrix = contMatrix * satMatrix * intMatrix;
199 colorMatrix = colorMatrix.transposed();
200
201 uint32_t *rawData = reinterpret_cast<uint32_t *>(colorMatrix.data());
202
203 for (int i = 0; i < 16; ++i) {
204 data << rawData[i];
205 }
206
207 xcb_change_property(c, mode: XCB_PROP_MODE_REPLACE, window: window->winId(), property: atom->atom, type: atom->atom, format: 32, data_len: data.size(), data: data.constData());
208 } else {
209 xcb_delete_property(c, window: window->winId(), property: atom->atom);
210 }
211}
212

source code of kwindowsystem/src/platforms/xcb/kwindoweffects.cpp