File indexing completed on 2024-04-28 05:27:06

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 = std::make_shared<KDecoration2::DecorationSettings>(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 (button == nullptr) {
0098         return;
0099     }
0100 
0101     button->setGeometry(DecorationPainter::ButtonGeometry);
0102 
0103     if (buttonType == QStringLiteral("maximized")) {
0104         // Different decorations use different ways to know if the window is maximized
0105         // For example Breeze uses 'checked' property, but Oxygen uses client's 'isMaximized' method
0106         button->setChecked(true);
0107         if (m_client) {
0108             dynamic_cast<DummyDecoratedClient *>(m_client)->setMaximized(true);
0109         }
0110     }
0111 
0112     if (buttonState.contains(QStringLiteral("active"))) {
0113         passMousePressEventToButton(button.get());
0114     } else if (buttonState.contains(QStringLiteral("hover"))) {
0115         passMouseHoverEventToButton(button.get());
0116     }
0117 
0118     if (buttonState.contains(QStringLiteral("backdrop"))) {
0119         if (m_client) {
0120             dynamic_cast<DummyDecoratedClient *>(m_client)->setActive(false);
0121         }
0122     } else {
0123         if (m_client) {
0124             dynamic_cast<DummyDecoratedClient *>(m_client)->setActive(true);
0125         }
0126     }
0127 
0128     button->paint(&painter, DecorationPainter::ButtonGeometry);
0129     enableAnimations();
0130 }
0131 
0132 void DummyDecorationBridge::disableAnimations()
0133 {
0134     KSharedConfig::Ptr decorationConfig = KSharedConfig::openConfig(m_decorationsConfigFileName, KConfig::NoGlobals);
0135     if (decorationConfig) {
0136         KConfigGroup group = decorationConfig->group(QStringLiteral("Windeco"));
0137         group.writeEntry(QStringLiteral("AnimationsEnabled"), false, KConfig::WriteConfigFlags());
0138     }
0139 
0140     // In case decoration is using global animation settings
0141     KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig();
0142     if (globalConfig) {
0143         auto group = globalConfig->group(QStringLiteral("KDE"));
0144         globalAnimationEntryValue = group.readEntry(QStringLiteral("AnimationDurationFactor"), 1.0);
0145         group.writeEntry(QStringLiteral("AnimationDurationFactor"), 0, KConfig::WriteConfigFlags());
0146     }
0147 }
0148 
0149 void DummyDecorationBridge::enableAnimations()
0150 {
0151     KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig();
0152     if (globalConfig) {
0153         auto group = globalConfig->group(QStringLiteral("KDE"));
0154         group.writeEntry(QStringLiteral("AnimationDurationFactor"), globalAnimationEntryValue, KConfig::WriteConfigFlags());
0155     }
0156 }
0157 
0158 QString DummyDecorationBridge::windowDecorationPluginPath(const QString &decorationTheme) const
0159 {
0160     const auto decorationPlugins = KPluginMetaData::findPlugins(QStringLiteral("org.kde.kdecoration2"));
0161 
0162     QString defaultPluginPath;
0163 
0164     for (const auto &pluginMetaData : decorationPlugins) {
0165         if (pluginMetaData.pluginId() == QLatin1String("org.kde.breeze")) {
0166             defaultPluginPath = pluginMetaData.fileName();
0167         }
0168 
0169         if (pluginMetaData.name() == decorationTheme) {
0170             return pluginMetaData.fileName();
0171         }
0172     }
0173     return defaultPluginPath;
0174 }
0175 
0176 void DummyDecorationBridge::passMouseHoverEventToButton(KDecoration2::DecorationButton *button) const
0177 {
0178     QHoverEvent event{QEvent::HoverEnter,
0179                       {
0180                           DecorationPainter::ButtonGeometry.width() / 2.0,
0181                           DecorationPainter::ButtonGeometry.height() / 2.0,
0182                       },
0183                       {
0184                           (DecorationPainter::ButtonGeometry.width() / 2.0) - 1,
0185                           (DecorationPainter::ButtonGeometry.height() / 2.0) - 1,
0186                       },
0187                       Qt::NoModifier};
0188     QCoreApplication::instance()->sendEvent(button, &event);
0189 }
0190 
0191 void DummyDecorationBridge::passMousePressEventToButton(KDecoration2::DecorationButton *button) const
0192 {
0193     QMouseEvent event{QEvent::MouseButtonPress,
0194                       {
0195                           DecorationPainter::ButtonGeometry.width() / 2.0,
0196                           DecorationPainter::ButtonGeometry.height() / 2.0,
0197                       },
0198                       Qt::LeftButton,
0199                       Qt::LeftButton,
0200                       Qt::NoModifier};
0201     QCoreApplication::instance()->sendEvent(button, &event);
0202 }
0203 
0204 KDecoration2::DecorationButtonType DummyDecorationBridge::strToButtonType(const QString &type) const
0205 {
0206     if (type == QStringLiteral("minimize")) {
0207         return KDecoration2::DecorationButtonType::Minimize;
0208     } else if (type == QStringLiteral("close")) {
0209         return KDecoration2::DecorationButtonType::Close;
0210     } else {
0211         return KDecoration2::DecorationButtonType::Maximize;
0212     }
0213 }
0214 
0215 }
0216 
0217 #include "moc_dummydecorationbridge.cpp"