File indexing completed on 2024-05-12 13:31:12

0001 /*
0002  * SPDX-FileCopyrightText: 2020 Mikhail Zolotukhin <zomial@protonmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include <QCoreApplication>
0008 #include <QMouseEvent>
0009 
0010 #include <KConfigGroup>
0011 #include <KDecoration2/DecoratedClient>
0012 #include <KDecoration2/DecorationSettings>
0013 #include <KDecoration2/Private/DecoratedClientPrivate>
0014 #include <KDecoration2/Private/DecorationSettingsPrivate>
0015 #include <KPluginFactory>
0016 #include <KPluginMetaData>
0017 #include <KSharedConfig>
0018 
0019 #include "decorationpainter.h"
0020 #include "dummydecoratedclient.h"
0021 #include "dummydecorationbridge.h"
0022 #include "dummydecorationsettings.h"
0023 
0024 namespace KDecoration2
0025 {
0026 DummyDecorationBridge::DummyDecorationBridge(const QString &decorationTheme, QObject *parent)
0027     : DecorationBridge(parent)
0028     , m_decorationsConfigFileName()
0029     , m_factory()
0030     , m_decoration()
0031     , m_client()
0032 {
0033     // HACK:
0034     // Some window decorations use button fade-in and fade-out animations.
0035     // These animations are very slow, and they are preventing this bridge
0036     // to correctly draw hover states of the buttons, when we pass a "hover"
0037     // event to them. To avoid this harmful side effect we use a hack:
0038     // We disable the animations via user configuration file temporary if
0039     // they were enabled, draw a buttons and then enable them again.
0040     if (decorationTheme == QStringLiteral("Oxygen")) {
0041         m_decorationsConfigFileName = QStringLiteral("oxygenrc");
0042     } else { // for Breeze window decorations and its forks
0043         m_decorationsConfigFileName = QStringLiteral("breezerc");
0044     }
0045 
0046     disableAnimations();
0047 
0048     const QString pluginPath = windowDecorationPluginPath(decorationTheme);
0049     m_pluginLoader.setFileName(pluginPath);
0050     m_factory = qobject_cast<KPluginFactory *>(m_pluginLoader.instance());
0051     if (m_factory) {
0052         const QVariantMap args({{QStringLiteral("bridge"), QVariant::fromValue(this)}});
0053         m_decoration = m_factory->create<KDecoration2::Decoration>(m_factory, QVariantList({args}));
0054     }
0055 
0056     auto decorationSettings = QSharedPointer<KDecoration2::DecorationSettings>::create(this);
0057     m_decoration->setSettings(decorationSettings);
0058     m_decoration->init();
0059 
0060     // Update decoration settings, e.g. Breeze's "Draw a circle around close button"
0061     if (m_settings) {
0062         Q_EMIT m_settings->decorationSettings()->reconfigured();
0063     }
0064     enableAnimations();
0065 }
0066 
0067 DummyDecorationBridge::~DummyDecorationBridge()
0068 {
0069     m_pluginLoader.unload();
0070 }
0071 
0072 std::unique_ptr<KDecoration2::DecorationSettingsPrivate> DummyDecorationBridge::settings(KDecoration2::DecorationSettings *parent)
0073 {
0074     auto newSettings = std::unique_ptr<DummyDecorationSettings>(new DummyDecorationSettings(parent));
0075     m_settings = newSettings.get();
0076     return newSettings;
0077 }
0078 
0079 std::unique_ptr<KDecoration2::DecoratedClientPrivate> DummyDecorationBridge::createClient(KDecoration2::DecoratedClient *client,
0080                                                                                           KDecoration2::Decoration *decoration)
0081 {
0082     auto ptr = std::unique_ptr<DummyDecoratedClient>(new DummyDecoratedClient(client, decoration));
0083     m_client = ptr.get();
0084     return ptr;
0085 }
0086 
0087 void DummyDecorationBridge::paintButton(QPainter &painter, const QString &buttonType, const QString &buttonState)
0088 {
0089     disableAnimations();
0090     std::unique_ptr<KDecoration2::DecorationButton> button{
0091         m_factory->create<KDecoration2::DecorationButton>(m_decoration,
0092                                                           QVariantList({
0093                                                               QVariant::fromValue(strToButtonType(buttonType)),
0094                                                               QVariant::fromValue(m_decoration),
0095                                                           }))};
0096 
0097 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 87)
0098     if (button == nullptr) {
0099         button.reset(m_factory->create<KDecoration2::DecorationButton>(QStringLiteral("button"),
0100                                                                        m_decoration,
0101                                                                        QVariantList({
0102                                                                            QVariant::fromValue(strToButtonType(buttonType)),
0103                                                                            QVariant::fromValue(m_decoration),
0104                                                                        })));
0105     }
0106 #endif
0107 
0108     if (button == nullptr) {
0109         return;
0110     }
0111 
0112     button->setGeometry(DecorationPainter::ButtonGeometry);
0113 
0114     if (buttonType == QStringLiteral("maximized")) {
0115         // Different decorations use different ways to know if the window is maximized
0116         // For example Breeze uses 'checked' property, but Oxygen uses client's 'isMaximized' method
0117         button->setChecked(true);
0118         if (m_client) {
0119             dynamic_cast<DummyDecoratedClient *>(m_client)->setMaximized(true);
0120         }
0121     }
0122 
0123     if (buttonState.contains(QStringLiteral("active"))) {
0124         passMousePressEventToButton(button.get());
0125     } else if (buttonState.contains(QStringLiteral("hover"))) {
0126         passMouseHoverEventToButton(button.get());
0127     }
0128 
0129     if (buttonState.contains(QStringLiteral("backdrop"))) {
0130         if (m_client) {
0131             dynamic_cast<DummyDecoratedClient *>(m_client)->setActive(false);
0132         }
0133     } else {
0134         if (m_client) {
0135             dynamic_cast<DummyDecoratedClient *>(m_client)->setActive(true);
0136         }
0137     }
0138 
0139     button->paint(&painter, DecorationPainter::ButtonGeometry);
0140     enableAnimations();
0141 }
0142 
0143 void DummyDecorationBridge::disableAnimations()
0144 {
0145     KSharedConfig::Ptr decorationConfig = KSharedConfig::openConfig(m_decorationsConfigFileName, KConfig::NoGlobals);
0146     if (decorationConfig) {
0147         KConfigGroup group = decorationConfig->group(QStringLiteral("Windeco"));
0148         group.writeEntry(QStringLiteral("AnimationsEnabled"), false, KConfig::WriteConfigFlags());
0149     }
0150 
0151     // In case decoration is using global animation settings
0152     KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig();
0153     if (globalConfig) {
0154         auto group = globalConfig->group(QStringLiteral("KDE"));
0155         globalAnimationEntryValue = group.readEntry(QStringLiteral("AnimationDurationFactor"), 1.0);
0156         group.writeEntry(QStringLiteral("AnimationDurationFactor"), 0, KConfig::WriteConfigFlags());
0157     }
0158 }
0159 
0160 void DummyDecorationBridge::enableAnimations()
0161 {
0162     KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig();
0163     if (globalConfig) {
0164         auto group = globalConfig->group(QStringLiteral("KDE"));
0165         group.writeEntry(QStringLiteral("AnimationDurationFactor"), globalAnimationEntryValue, KConfig::WriteConfigFlags());
0166     }
0167 }
0168 
0169 QString DummyDecorationBridge::windowDecorationPluginPath(const QString &decorationTheme) const
0170 {
0171     const auto decorationPlugins = KPluginMetaData::findPlugins(QStringLiteral("org.kde.kdecoration2"));
0172 
0173     QString defaultPluginPath;
0174 
0175     for (const auto &pluginMetaData : decorationPlugins) {
0176         if (pluginMetaData.pluginId() == QLatin1String("org.kde.breeze")) {
0177             defaultPluginPath = pluginMetaData.fileName();
0178         }
0179 
0180         if (pluginMetaData.name() == decorationTheme) {
0181             return pluginMetaData.fileName();
0182         }
0183     }
0184     return defaultPluginPath;
0185 }
0186 
0187 void DummyDecorationBridge::passMouseHoverEventToButton(KDecoration2::DecorationButton *button) const
0188 {
0189     QHoverEvent event{QEvent::HoverEnter,
0190                       {
0191                           DecorationPainter::ButtonGeometry.width() / 2.0,
0192                           DecorationPainter::ButtonGeometry.height() / 2.0,
0193                       },
0194                       {
0195                           (DecorationPainter::ButtonGeometry.width() / 2.0) - 1,
0196                           (DecorationPainter::ButtonGeometry.height() / 2.0) - 1,
0197                       },
0198                       Qt::NoModifier};
0199     QCoreApplication::instance()->sendEvent(button, &event);
0200 }
0201 
0202 void DummyDecorationBridge::passMousePressEventToButton(KDecoration2::DecorationButton *button) const
0203 {
0204     QMouseEvent event{QEvent::MouseButtonPress,
0205                       {
0206                           DecorationPainter::ButtonGeometry.width() / 2.0,
0207                           DecorationPainter::ButtonGeometry.height() / 2.0,
0208                       },
0209                       Qt::LeftButton,
0210                       Qt::LeftButton,
0211                       Qt::NoModifier};
0212     QCoreApplication::instance()->sendEvent(button, &event);
0213 }
0214 
0215 KDecoration2::DecorationButtonType DummyDecorationBridge::strToButtonType(const QString &type) const
0216 {
0217     if (type == QStringLiteral("minimize")) {
0218         return KDecoration2::DecorationButtonType::Minimize;
0219     } else if (type == QStringLiteral("close")) {
0220         return KDecoration2::DecorationButtonType::Close;
0221     } else {
0222         return KDecoration2::DecorationButtonType::Maximize;
0223     }
0224 }
0225 
0226 }