File indexing completed on 2024-04-28 16:55:13

0001 /***************************************************************************
0002  *   Copyright (C) 2015 Kai Uwe Broulik <kde@privat.broulik.de>            *
0003  *                                                                         *
0004  *   This program is free software; you can redistribute it and/or modify  *
0005  *   it under the terms of the GNU General Public License as published by  *
0006  *   the Free Software Foundation; either version 2 of the License, or     *
0007  *   (at your option) any later version.                                   *
0008  *                                                                         *
0009  *   This program is distributed in the hope that it will be useful,       *
0010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0012  *   GNU General Public License for more details.                          *
0013  *                                                                         *
0014  *   You should have received a copy of the GNU General Public License     *
0015  *   along with this program; if not, write to the                         *
0016  *   Free Software Foundation, Inc.,                                       *
0017  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
0018  ***************************************************************************/
0019 
0020 #include "kwinkscreenhelpereffect.h"
0021 
0022 #include <QCoreApplication>
0023 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0024 #include <QX11Info>
0025 #else
0026 #include <private/qtx11extras_p.h>
0027 #endif
0028 #include <chrono>
0029 
0030 using namespace std::chrono_literals;
0031 
0032 namespace PowerDevil
0033 {
0034 
0035 KWinKScreenHelperEffect::KWinKScreenHelperEffect(QObject *parent) : QObject(parent)
0036 {
0037     m_abortTimer.setSingleShot(true);
0038     m_abortTimer.setInterval(11s);
0039     connect(&m_abortTimer, &QTimer::timeout, this, &KWinKScreenHelperEffect::stop);
0040 
0041     qApp->installNativeEventFilter(this);
0042 }
0043 
0044 KWinKScreenHelperEffect::~KWinKScreenHelperEffect()
0045 {
0046     stop();
0047 }
0048 
0049 bool KWinKScreenHelperEffect::start()
0050 {
0051     m_isValid = checkValid();
0052     if (!m_isValid) {
0053         // emit fade out right away since the effect is not available
0054         Q_EMIT fadedOut();
0055         return false;
0056     }
0057 
0058     m_running = true;
0059     setEffectProperty(1);
0060     m_abortTimer.start();
0061     return true;
0062 }
0063 
0064 void KWinKScreenHelperEffect::stop()
0065 {
0066     // Maybe somebody got confused, just reset the property then
0067     if (m_state == NormalState) {
0068         setEffectProperty(0);
0069     } else {
0070         setEffectProperty(3);
0071     }
0072     m_running = false;
0073     m_abortTimer.stop();
0074 }
0075 
0076 bool KWinKScreenHelperEffect::checkValid()
0077 {
0078 #if HAVE_XCB
0079     if (QX11Info::isPlatformX11()) {
0080         QScopedPointer<xcb_list_properties_reply_t, QScopedPointerPodDeleter> propsReply(xcb_list_properties_reply(QX11Info::connection(),
0081             xcb_list_properties_unchecked(QX11Info::connection(), QX11Info::appRootWindow()),
0082         nullptr));
0083         QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> atomReply(xcb_intern_atom_reply(QX11Info::connection(),
0084             xcb_intern_atom_unchecked(QX11Info::connection(), false, 25, "_KDE_KWIN_KSCREEN_SUPPORT"),
0085         nullptr));
0086 
0087         if (propsReply.isNull() || atomReply.isNull()) {
0088             return false;
0089         }
0090 
0091         auto *atoms = xcb_list_properties_atoms(propsReply.data());
0092         for (int i = 0; i < propsReply->atoms_len; ++i) {
0093             if (atoms[i] == atomReply->atom) {
0094                 m_atom = atomReply->atom;
0095                 return true;
0096             }
0097         }
0098 
0099         m_atom = 0;
0100         return false;
0101     }
0102 #endif
0103     return false;
0104 }
0105 
0106 void KWinKScreenHelperEffect::setEffectProperty(long value)
0107 {
0108 #if HAVE_XCB
0109     if (m_isValid && QX11Info::isPlatformX11()) {
0110         xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, QX11Info::appRootWindow(), m_atom, XCB_ATOM_CARDINAL, 32, 1, &value);
0111     }
0112 #else
0113     Q_UNUSED(value);
0114 #endif
0115 }
0116 
0117 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0118 bool KWinKScreenHelperEffect::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
0119 #else
0120 bool KWinKScreenHelperEffect::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result)
0121 #endif
0122 {
0123     Q_UNUSED(result);
0124 
0125     if (eventType != "xcb_generic_event_t") {
0126         return false;
0127     }
0128 
0129 #if HAVE_XCB
0130     if (m_isValid && QX11Info::isPlatformX11()) {
0131         auto e = static_cast<xcb_generic_event_t *>(message);
0132         const uint8_t type = e->response_type & ~0x80;
0133         if (type == XCB_PROPERTY_NOTIFY) {
0134             auto *event = reinterpret_cast<xcb_property_notify_event_t *>(e);
0135             if (event->window == QX11Info::appRootWindow()) {
0136                 if (event->atom != m_atom) {
0137                     return false;
0138                 }
0139 
0140                 auto cookie = xcb_get_property(QX11Info::connection(), false, QX11Info::appRootWindow(), m_atom, XCB_ATOM_CARDINAL, 0, 1);
0141                 QScopedPointer<xcb_get_property_reply_t, QScopedPointerPodDeleter> reply(xcb_get_property_reply(QX11Info::connection(), cookie, nullptr));
0142                 if (reply.isNull() || reply.data()->value_len != 1 || reply.data()->format != uint8_t(32)) {
0143                     return false;
0144                 }
0145 
0146                 auto *data = reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.data()));
0147                 if (!data) {
0148                     return false;
0149                 }
0150 
0151                 switch(*data) {
0152                 case 1:
0153                     m_state = FadingOutState;
0154                     break;
0155                 case 2:
0156                     m_state = FadedOutState;
0157                     if (m_running) {
0158                         Q_EMIT fadedOut();
0159                     }
0160                     break;
0161                 case 3:
0162                     m_state = FadingInState;
0163                     m_running = false;
0164                     m_abortTimer.stop();
0165                     break;
0166                 default:
0167                     m_state = NormalState;
0168                     m_running = false;
0169                 }
0170 
0171                 Q_EMIT stateChanged(m_state);
0172             }
0173         }
0174     }
0175 #else
0176     Q_UNUSED(message);
0177 #endif
0178 
0179     return false;
0180 }
0181 
0182 } // namespace PowerDevil