File indexing completed on 2024-05-05 12:24:28
0001 /* 0002 SPDX-FileCopyrightText: 2009 Marco Martin <notmart@gmail.com> 0003 SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.1-or-later 0006 */ 0007 0008 #include "kwindoweffects_x11.h" 0009 0010 #include <QGuiApplication> 0011 #include <QVarLengthArray> 0012 0013 #include "kwindowsystem.h" 0014 #include "kx11extras.h" 0015 #include <config-kwindowsystem.h> 0016 0017 #include <QMatrix4x4> 0018 #include <QWindow> 0019 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0020 #include <private/qtx11extras_p.h> 0021 #else 0022 #include <QX11Info> 0023 #endif 0024 0025 #include <xcb/xcb.h> 0026 0027 #include "cptr_p.h" 0028 #include <cmath> 0029 0030 using namespace KWindowEffects; 0031 0032 KWindowEffectsPrivateX11::KWindowEffectsPrivateX11() 0033 { 0034 } 0035 0036 KWindowEffectsPrivateX11::~KWindowEffectsPrivateX11() 0037 { 0038 } 0039 0040 bool KWindowEffectsPrivateX11::isEffectAvailable(Effect effect) 0041 { 0042 if (!KX11Extras::self()->compositingActive()) { 0043 return false; 0044 } 0045 QByteArray effectName; 0046 0047 switch (effect) { 0048 case Slide: 0049 effectName = QByteArrayLiteral("_KDE_SLIDE"); 0050 break; 0051 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82) 0052 case PresentWindows: 0053 effectName = QByteArrayLiteral("_KDE_PRESENT_WINDOWS_DESKTOP"); 0054 break; 0055 case PresentWindowsGroup: 0056 effectName = QByteArrayLiteral("_KDE_PRESENT_WINDOWS_GROUP"); 0057 break; 0058 case HighlightWindows: 0059 effectName = QByteArrayLiteral("_KDE_WINDOW_HIGHLIGHT"); 0060 break; 0061 #endif 0062 case BlurBehind: 0063 effectName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION"); 0064 break; 0065 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 67) 0066 case Dashboard: 0067 // TODO: Better namespacing for atoms 0068 effectName = QByteArrayLiteral("_WM_EFFECT_KDE_DASHBOARD"); 0069 break; 0070 #endif 0071 case BackgroundContrast: 0072 effectName = QByteArrayLiteral("_KDE_NET_WM_BACKGROUND_CONTRAST_REGION"); 0073 break; 0074 default: 0075 return false; 0076 } 0077 0078 // hackish way to find out if KWin has the effect enabled, 0079 // TODO provide proper support 0080 xcb_connection_t *c = QX11Info::connection(); 0081 xcb_list_properties_cookie_t propsCookie = xcb_list_properties_unchecked(c, QX11Info::appRootWindow()); 0082 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData()); 0083 0084 UniqueCPointer<xcb_list_properties_reply_t> props(xcb_list_properties_reply(c, propsCookie, nullptr)); 0085 UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr)); 0086 if (!atom || !props) { 0087 return false; 0088 } 0089 xcb_atom_t *atoms = xcb_list_properties_atoms(props.get()); 0090 for (int i = 0; i < props->atoms_len; ++i) { 0091 if (atoms[i] == atom->atom) { 0092 return true; 0093 } 0094 } 0095 return false; 0096 } 0097 0098 void KWindowEffectsPrivateX11::slideWindow(WId id, SlideFromLocation location, int offset) 0099 { 0100 xcb_connection_t *c = QX11Info::connection(); 0101 if (!c) { 0102 return; 0103 } 0104 0105 const QByteArray effectName = QByteArrayLiteral("_KDE_SLIDE"); 0106 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData()); 0107 0108 const int size = 2; 0109 int32_t data[size]; 0110 data[0] = offset; 0111 0112 switch (location) { 0113 case LeftEdge: 0114 data[1] = 0; 0115 break; 0116 case TopEdge: 0117 data[1] = 1; 0118 break; 0119 case RightEdge: 0120 data[1] = 2; 0121 break; 0122 case BottomEdge: 0123 data[1] = 3; 0124 default: 0125 break; 0126 } 0127 0128 UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr)); 0129 if (!atom) { 0130 return; 0131 } 0132 if (location == NoEdge) { 0133 xcb_delete_property(c, id, atom->atom); 0134 } else { 0135 xcb_change_property(c, XCB_PROP_MODE_REPLACE, id, atom->atom, atom->atom, 32, size, data); 0136 } 0137 } 0138 0139 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 81) 0140 QList<QSize> KWindowEffectsPrivateX11::windowSizes(const QList<WId> &ids) 0141 { 0142 QList<QSize> windowSizes; 0143 for (WId id : ids) { 0144 if (id > 0) { 0145 KWindowInfo info(id, NET::WMGeometry | NET::WMFrameExtents); 0146 windowSizes.append(info.frameGeometry().size()); 0147 } else { 0148 windowSizes.append(QSize()); 0149 } 0150 } 0151 return windowSizes; 0152 } 0153 #endif 0154 0155 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82) 0156 void KWindowEffectsPrivateX11::presentWindows(WId controller, const QList<WId> &ids) 0157 { 0158 xcb_connection_t *c = QX11Info::connection(); 0159 if (!c) { 0160 return; 0161 } 0162 0163 const int numWindows = ids.count(); 0164 QVarLengthArray<int32_t, 32> data(numWindows); 0165 int actualCount = 0; 0166 0167 for (int i = 0; i < numWindows; ++i) { 0168 data[i] = ids.at(i); 0169 ++actualCount; 0170 } 0171 0172 if (actualCount != numWindows) { 0173 data.resize(actualCount); 0174 } 0175 0176 if (data.isEmpty()) { 0177 return; 0178 } 0179 0180 const QByteArray effectName = QByteArrayLiteral("_KDE_PRESENT_WINDOWS_GROUP"); 0181 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData()); 0182 UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr)); 0183 if (!atom) { 0184 return; 0185 } 0186 xcb_change_property(c, XCB_PROP_MODE_REPLACE, controller, atom->atom, atom->atom, 32, data.size(), data.constData()); 0187 } 0188 #endif 0189 0190 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82) 0191 void KWindowEffectsPrivateX11::presentWindows(WId controller, int desktop) 0192 { 0193 xcb_connection_t *c = QX11Info::connection(); 0194 if (!c) { 0195 return; 0196 } 0197 const QByteArray effectName = QByteArrayLiteral("_KDE_PRESENT_WINDOWS_DESKTOP"); 0198 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData()); 0199 UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr)); 0200 if (!atom) { 0201 return; 0202 } 0203 0204 int32_t data = desktop; 0205 xcb_change_property(c, XCB_PROP_MODE_REPLACE, controller, atom->atom, atom->atom, 32, 1, &data); 0206 } 0207 #endif 0208 0209 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82) 0210 void KWindowEffectsPrivateX11::highlightWindows(WId controller, const QList<WId> &ids) 0211 { 0212 xcb_connection_t *c = QX11Info::connection(); 0213 if (!c) { 0214 return; 0215 } 0216 const QByteArray effectName = QByteArrayLiteral("_KDE_WINDOW_HIGHLIGHT"); 0217 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData()); 0218 UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr)); 0219 if (!atom) { 0220 return; 0221 } 0222 0223 const int numWindows = ids.count(); 0224 if (numWindows == 0) { 0225 xcb_delete_property(c, controller, atom->atom); 0226 return; 0227 } 0228 0229 QVarLengthArray<int32_t, 32> data(numWindows); 0230 int actualCount = 0; 0231 0232 for (int i = 0; i < numWindows; ++i) { 0233 data[i] = ids.at(i); 0234 ++actualCount; 0235 } 0236 0237 if (actualCount != numWindows) { 0238 data.resize(actualCount); 0239 } 0240 0241 if (data.isEmpty()) { 0242 return; 0243 } 0244 xcb_change_property(c, XCB_PROP_MODE_REPLACE, controller, atom->atom, atom->atom, 32, data.size(), data.constData()); 0245 } 0246 #endif 0247 0248 void KWindowEffectsPrivateX11::enableBlurBehind(WId window, bool enable, const QRegion ®ion) 0249 { 0250 xcb_connection_t *c = QX11Info::connection(); 0251 if (!c) { 0252 return; 0253 } 0254 const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION"); 0255 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData()); 0256 UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr)); 0257 if (!atom) { 0258 return; 0259 } 0260 0261 if (enable) { 0262 QVector<uint32_t> data; 0263 data.reserve(region.rectCount() * 4); 0264 for (const QRect &r : region) { 0265 // kwin on X uses device pixels, convert from logical 0266 auto dpr = qApp->devicePixelRatio(); 0267 data << std::floor(r.x() * dpr) << std::floor(r.y() * dpr) << std::ceil(r.width() * dpr) << std::ceil(r.height() * dpr); 0268 } 0269 0270 xcb_change_property(c, XCB_PROP_MODE_REPLACE, window, atom->atom, XCB_ATOM_CARDINAL, 32, data.size(), data.constData()); 0271 } else { 0272 xcb_delete_property(c, window, atom->atom); 0273 } 0274 } 0275 0276 void KWindowEffectsPrivateX11::setBackgroundFrost(QWindow *window, QColor color, const QRegion ®ion) 0277 { 0278 auto id = window->winId(); 0279 0280 xcb_connection_t *c = QX11Info::connection(); 0281 const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_BACKGROUND_FROST_REGION"); 0282 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData()); 0283 UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr)); 0284 if (!atom) { 0285 return; 0286 } 0287 0288 if (!color.isValid()) { 0289 xcb_delete_property(c, id, atom->atom); 0290 return; 0291 } 0292 0293 enableBackgroundContrast(id, false); 0294 0295 QVector<uint32_t> data; 0296 data.reserve(region.rectCount() * 4 + 4); 0297 for (const QRect &r : region) { 0298 auto dpr = qApp->devicePixelRatio(); 0299 data << std::floor(r.x() * dpr) << std::floor(r.y() * dpr) << std::ceil(r.width() * dpr) << std::ceil(r.height() * dpr); 0300 } 0301 0302 data << color.red(); 0303 data << color.green(); 0304 data << color.blue(); 0305 data << color.alpha(); 0306 0307 xcb_change_property(c, XCB_PROP_MODE_REPLACE, id, atom->atom, atom->atom, 32, data.size(), data.constData()); 0308 } 0309 0310 static QWindow *getWindowFromWinId(WId winId) 0311 { 0312 const QWindowList windows = qGuiApp->topLevelWindows(); 0313 for (auto window : windows) { 0314 if (window->handle() && window->winId() == winId) { 0315 return window; 0316 } 0317 } 0318 return nullptr; 0319 } 0320 0321 void KWindowEffectsPrivateX11::enableBackgroundContrast(WId window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion ®ion) 0322 { 0323 xcb_connection_t *c = QX11Info::connection(); 0324 const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_BACKGROUND_CONTRAST_REGION"); 0325 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData()); 0326 UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr)); 0327 if (!atom) { 0328 return; 0329 } 0330 0331 if (enable) { 0332 if (QWindow *windowObject = getWindowFromWinId(window)) { 0333 setBackgroundFrost(windowObject, {}); 0334 } 0335 QVector<uint32_t> data; 0336 data.reserve(region.rectCount() * 4 + 16); 0337 for (const QRect &r : region) { 0338 auto dpr = qApp->devicePixelRatio(); 0339 data << std::floor(r.x() * dpr) << std::floor(r.y() * dpr) << std::ceil(r.width() * dpr) << std::ceil(r.height() * dpr); 0340 } 0341 0342 QMatrix4x4 satMatrix; // saturation 0343 QMatrix4x4 intMatrix; // intensity 0344 QMatrix4x4 contMatrix; // contrast 0345 0346 // clang-format off 0347 0348 //Saturation matrix 0349 if (!qFuzzyCompare(saturation, 1.0)) { 0350 const qreal rval = (1.0 - saturation) * .2126; 0351 const qreal gval = (1.0 - saturation) * .7152; 0352 const qreal bval = (1.0 - saturation) * .0722; 0353 0354 satMatrix = QMatrix4x4(rval + saturation, rval, rval, 0.0, 0355 gval, gval + saturation, gval, 0.0, 0356 bval, bval, bval + saturation, 0.0, 0357 0, 0, 0, 1.0); 0358 } 0359 0360 //IntensityMatrix 0361 if (!qFuzzyCompare(intensity, 1.0)) { 0362 intMatrix.scale(intensity, intensity, intensity); 0363 } 0364 0365 //Contrast Matrix 0366 if (!qFuzzyCompare(contrast, 1.0)) { 0367 const float transl = (1.0 - contrast) / 2.0; 0368 0369 contMatrix = QMatrix4x4(contrast, 0, 0, 0.0, 0370 0, contrast, 0, 0.0, 0371 0, 0, contrast, 0.0, 0372 transl, transl, transl, 1.0); 0373 } 0374 0375 // clang-format on 0376 0377 QMatrix4x4 colorMatrix = contMatrix * satMatrix * intMatrix; 0378 colorMatrix = colorMatrix.transposed(); 0379 0380 uint32_t *rawData = reinterpret_cast<uint32_t *>(colorMatrix.data()); 0381 0382 for (int i = 0; i < 16; ++i) { 0383 data << rawData[i]; 0384 } 0385 0386 xcb_change_property(c, XCB_PROP_MODE_REPLACE, window, atom->atom, atom->atom, 32, data.size(), data.constData()); 0387 } else { 0388 xcb_delete_property(c, window, atom->atom); 0389 } 0390 } 0391 0392 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 67) 0393 void KWindowEffectsPrivateX11::markAsDashboard(WId window) 0394 { 0395 static const char DASHBOARD_WIN_CLASS[] = "dashboard\0dashboard"; 0396 xcb_connection_t *c = QX11Info::connection(); 0397 if (!c) { 0398 return; 0399 } 0400 xcb_change_property(c, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, 19, DASHBOARD_WIN_CLASS); 0401 } 0402 #endif