File indexing completed on 2024-05-05 16:19:50

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 &region)
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 &region)
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 &region)
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