File indexing completed on 2024-04-28 03:59:24

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 "kx11extras.h"
0014 #include <config-kwindowsystem.h>
0015 
0016 #include <QMatrix4x4>
0017 #include <QWindow>
0018 #include <private/qtx11extras_p.h>
0019 
0020 #include <xcb/xcb.h>
0021 
0022 #include "cptr_p.h"
0023 #include <cmath>
0024 
0025 using namespace KWindowEffects;
0026 
0027 KWindowEffectsPrivateX11::KWindowEffectsPrivateX11()
0028 {
0029 }
0030 
0031 KWindowEffectsPrivateX11::~KWindowEffectsPrivateX11()
0032 {
0033 }
0034 
0035 bool KWindowEffectsPrivateX11::isEffectAvailable(Effect effect)
0036 {
0037     if (!KX11Extras::self()->compositingActive()) {
0038         return false;
0039     }
0040     QByteArray effectName;
0041 
0042     switch (effect) {
0043     case Slide:
0044         effectName = QByteArrayLiteral("_KDE_SLIDE");
0045         break;
0046     case BlurBehind:
0047         effectName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION");
0048         break;
0049     case BackgroundContrast:
0050         effectName = QByteArrayLiteral("_KDE_NET_WM_BACKGROUND_CONTRAST_REGION");
0051         break;
0052     default:
0053         return false;
0054     }
0055 
0056     // hackish way to find out if KWin has the effect enabled,
0057     // TODO provide proper support
0058     xcb_connection_t *c = QX11Info::connection();
0059     xcb_list_properties_cookie_t propsCookie = xcb_list_properties_unchecked(c, QX11Info::appRootWindow());
0060     xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData());
0061 
0062     UniqueCPointer<xcb_list_properties_reply_t> props(xcb_list_properties_reply(c, propsCookie, nullptr));
0063     UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr));
0064     if (!atom || !props) {
0065         return false;
0066     }
0067     xcb_atom_t *atoms = xcb_list_properties_atoms(props.get());
0068     for (int i = 0; i < props->atoms_len; ++i) {
0069         if (atoms[i] == atom->atom) {
0070             return true;
0071         }
0072     }
0073     return false;
0074 }
0075 
0076 void KWindowEffectsPrivateX11::slideWindow(QWindow *window, SlideFromLocation location, int offset)
0077 {
0078     xcb_connection_t *c = QX11Info::connection();
0079     if (!c) {
0080         return;
0081     }
0082 
0083     const QByteArray effectName = QByteArrayLiteral("_KDE_SLIDE");
0084     xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData());
0085 
0086     const int size = 2;
0087     int32_t data[size];
0088     data[0] = offset;
0089 
0090     switch (location) {
0091     case LeftEdge:
0092         data[1] = 0;
0093         break;
0094     case TopEdge:
0095         data[1] = 1;
0096         break;
0097     case RightEdge:
0098         data[1] = 2;
0099         break;
0100     case BottomEdge:
0101         data[1] = 3;
0102     default:
0103         break;
0104     }
0105 
0106     UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr));
0107     if (!atom) {
0108         return;
0109     }
0110     if (location == NoEdge) {
0111         xcb_delete_property(c, window->winId(), atom->atom);
0112     } else {
0113         xcb_change_property(c, XCB_PROP_MODE_REPLACE, window->winId(), atom->atom, atom->atom, 32, size, data);
0114     }
0115 }
0116 
0117 void KWindowEffectsPrivateX11::enableBlurBehind(QWindow *window, bool enable, const QRegion &region)
0118 {
0119     xcb_connection_t *c = QX11Info::connection();
0120     if (!c) {
0121         return;
0122     }
0123     const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION");
0124     xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData());
0125     UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr));
0126     if (!atom) {
0127         return;
0128     }
0129 
0130     if (enable) {
0131         QList<uint32_t> data;
0132         data.reserve(region.rectCount() * 4);
0133         for (const QRect &r : region) {
0134             // kwin on X uses device pixels, convert from logical
0135             auto dpr = qApp->devicePixelRatio();
0136             data << std::floor(r.x() * dpr) << std::floor(r.y() * dpr) << std::ceil(r.width() * dpr) << std::ceil(r.height() * dpr);
0137         }
0138 
0139         xcb_change_property(c, XCB_PROP_MODE_REPLACE, window->winId(), atom->atom, XCB_ATOM_CARDINAL, 32, data.size(), data.constData());
0140     } else {
0141         xcb_delete_property(c, window->winId(), atom->atom);
0142     }
0143 }
0144 
0145 void KWindowEffectsPrivateX11::enableBackgroundContrast(QWindow *window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion &region)
0146 {
0147     xcb_connection_t *c = QX11Info::connection();
0148     const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_BACKGROUND_CONTRAST_REGION");
0149     xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData());
0150     UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr));
0151     if (!atom) {
0152         return;
0153     }
0154 
0155     if (enable) {
0156         QList<uint32_t> data;
0157         data.reserve(region.rectCount() * 4 + 16);
0158         for (const QRect &r : region) {
0159             auto dpr = qApp->devicePixelRatio();
0160             data << std::floor(r.x() * dpr) << std::floor(r.y() * dpr) << std::ceil(r.width() * dpr) << std::ceil(r.height() * dpr);
0161         }
0162 
0163         QMatrix4x4 satMatrix; // saturation
0164         QMatrix4x4 intMatrix; // intensity
0165         QMatrix4x4 contMatrix; // contrast
0166 
0167         // clang-format off
0168 
0169         //Saturation matrix
0170         if (!qFuzzyCompare(saturation, 1.0)) {
0171             const qreal rval = (1.0 - saturation) * .2126;
0172             const qreal gval = (1.0 - saturation) * .7152;
0173             const qreal bval = (1.0 - saturation) * .0722;
0174 
0175             satMatrix = QMatrix4x4(rval + saturation, rval,     rval,     0.0,
0176                 gval,     gval + saturation, gval,     0.0,
0177                 bval,     bval,     bval + saturation, 0.0,
0178                 0,        0,        0,        1.0);
0179         }
0180 
0181         //IntensityMatrix
0182         if (!qFuzzyCompare(intensity, 1.0)) {
0183             intMatrix.scale(intensity, intensity, intensity);
0184         }
0185 
0186         //Contrast Matrix
0187         if (!qFuzzyCompare(contrast, 1.0)) {
0188             const float transl = (1.0 - contrast) / 2.0;
0189 
0190             contMatrix = QMatrix4x4(contrast, 0,        0,        0.0,
0191                 0,        contrast, 0,        0.0,
0192                 0,        0,        contrast, 0.0,
0193                 transl,   transl,   transl,   1.0);
0194         }
0195 
0196         // clang-format on
0197 
0198         QMatrix4x4 colorMatrix = contMatrix * satMatrix * intMatrix;
0199         colorMatrix = colorMatrix.transposed();
0200 
0201         uint32_t *rawData = reinterpret_cast<uint32_t *>(colorMatrix.data());
0202 
0203         for (int i = 0; i < 16; ++i) {
0204             data << rawData[i];
0205         }
0206 
0207         xcb_change_property(c, XCB_PROP_MODE_REPLACE, window->winId(), atom->atom, atom->atom, 32, data.size(), data.constData());
0208     } else {
0209         xcb_delete_property(c, window->winId(), atom->atom);
0210     }
0211 }