From cd371a398d8487a03b9f57fdf1d90dff9ff3f03b Mon Sep 17 00:00:00 2001 From: Reion Wong Date: Sun, 30 Aug 2026 00:30:06 -0400 Subject: [PATCH] feat: port the rounded corners effect to KWin 6 and drop the KWin 5 plugins --- README.md | 14 +- config/kwinrc | 1 + debian/control | 1 + plugins/CMakeLists.txt | 47 +- plugins/cutefish_multitasking/CMakeLists.txt | 0 plugins/decoration/CMakeLists.txt | 65 +- plugins/decoration/button.cpp | 131 ++-- plugins/decoration/button.h | 38 +- plugins/decoration/cutefishos.json | 15 +- plugins/decoration/decoration.cpp | 614 ++++++++----------- plugins/decoration/decoration.h | 103 +--- plugins/decoration/x11shadow.cpp | 31 - plugins/decoration/x11shadow.h | 18 - plugins/decoration3/CMakeLists.txt | 52 -- plugins/decoration3/button.cpp | 91 --- plugins/decoration3/button.h | 24 - plugins/decoration3/cutefishos.json | 19 - plugins/decoration3/decoration.cpp | 369 ----------- plugins/decoration3/decoration.h | 71 --- plugins/roundedwindow/CMakeLists.txt | 86 ++- plugins/roundedwindow/main.cpp | 42 -- plugins/roundedwindow/metadata.json | 19 + plugins/roundedwindow/resources.qrc | 6 - plugins/roundedwindow/roundedwindow.cpp | 555 ++++++++--------- plugins/roundedwindow/roundedwindow.h | 90 ++- plugins/roundedwindow/roundedwindow.json | 31 - plugins/roundedwindow/shaders.frag.110 | 14 - plugins/roundedwindow/shaders.frag.140 | 15 - 28 files changed, 764 insertions(+), 1798 deletions(-) delete mode 100644 plugins/cutefish_multitasking/CMakeLists.txt delete mode 100644 plugins/decoration/x11shadow.cpp delete mode 100644 plugins/decoration/x11shadow.h delete mode 100644 plugins/decoration3/CMakeLists.txt delete mode 100644 plugins/decoration3/button.cpp delete mode 100644 plugins/decoration3/button.h delete mode 100644 plugins/decoration3/cutefishos.json delete mode 100644 plugins/decoration3/decoration.cpp delete mode 100644 plugins/decoration3/decoration.h delete mode 100644 plugins/roundedwindow/main.cpp create mode 100644 plugins/roundedwindow/metadata.json delete mode 100644 plugins/roundedwindow/resources.qrc delete mode 100644 plugins/roundedwindow/roundedwindow.json delete mode 100644 plugins/roundedwindow/shaders.frag.110 delete mode 100644 plugins/roundedwindow/shaders.frag.140 diff --git a/README.md b/README.md index 0468d18..aa3660e 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,8 @@ CutefishOS. The plugins target KWin 6 (Plasma 6 / KDecoration3). | Path | Description | | --- | --- | -| `plugins/decoration3` | KDecoration3 window decoration (installed as `org.cutefish.decoration`) | -| `plugins/decoration` | Legacy KDecoration2 decoration for KWin 5, not built by default | -| `plugins/roundedwindow` | Legacy KWin 5 C++ effect, not built (uses private KWin 5 APIs) | +| `plugins/decoration` | KDecoration3 window decoration (installed as `org.cutefish.decoration`) | +| `plugins/roundedwindow` | Rounded corners effect (`cutefish_roundedwindow`) | | `scripts/` | JavaScript effects and scripts (`cutefish_scale`, `cutefish_squash`, `cutefish_popups`, `cutefishlauncher`) | | `tabbox/` | `cutefish_thumbnail` window switcher layout | | `config/` | System-wide KWin defaults installed into `/etc/xdg` | @@ -18,7 +17,7 @@ CutefishOS. The plugins target KWin 6 (Plasma 6 / KDecoration3). Debian/Ubuntu: -`sudo apt install cmake extra-cmake-modules qt6-base-dev libkf6coreaddons-dev libkdecorations3-dev kwin-common` +`sudo apt install cmake extra-cmake-modules qt6-base-dev libkf6coreaddons-dev libkdecorations3-dev kwin-dev kwin-common` `kwin-common` is required at runtime: it provides the KPackage structures and the `org.kde.kwin` QML module that KWin uses to load scripted effects, scripts @@ -34,8 +33,11 @@ make sudo make install ``` -Build options: `CUTEFISH_BUILD_KWIN6_DECORATION` (ON), `CUTEFISH_BUILD_KWIN5_DECORATION` -(OFF) and `CUTEFISH_BUILD_KWIN5_EFFECT` (OFF). +Build options: `CUTEFISH_BUILD_KWIN6_DECORATION` (ON) and `CUTEFISH_BUILD_KWIN6_EFFECT` (ON). + +The rounded corners effect is a binary KWin plugin: KWin provides no stable effect +ABI, so it has to be rebuilt against the KWin version it runs on (KWin refuses to +load a plugin built against a different one). ## Enabling diff --git a/config/kwinrc b/config/kwinrc index 1f2290c..83c76cd 100644 --- a/config/kwinrc +++ b/config/kwinrc @@ -7,6 +7,7 @@ dialogparentEnabled=true # The CutefishOS animations replace the KWin ones with the same purpose. cutefish_popupsEnabled=true +cutefish_roundedwindowEnabled=true cutefish_scaleEnabled=true cutefish_squashEnabled=true diff --git a/debian/control b/debian/control index c9f594b..36c0cbe 100644 --- a/debian/control +++ b/debian/control @@ -5,6 +5,7 @@ Maintainer: CutefishOS Build-Depends: cmake, debhelper (>= 9), extra-cmake-modules, + kwin-dev, libkdecorations3-dev, libkf6coreaddons-dev, qt6-base-dev diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index e379599..26a5a9f 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,40 +1,35 @@ option(CUTEFISH_BUILD_KWIN6_DECORATION "Build the KDecoration3 plugin used by KWin6" ON) -option(CUTEFISH_BUILD_KWIN5_DECORATION "Build the legacy KDecoration2 plugin" OFF) -option(CUTEFISH_BUILD_KWIN5_EFFECT "Build the legacy KWin5 C++ effect" OFF) +option(CUTEFISH_BUILD_KWIN6_EFFECT "Build the rounded corners effect for KWin6" ON) if(CUTEFISH_BUILD_KWIN6_DECORATION) find_package(KDecoration3 QUIET) if(KDecoration3_FOUND) - add_subdirectory(decoration3) + add_subdirectory(decoration) message(STATUS "KDecoration3 found: building Cutefish KWin6 decoration") else() message(WARNING "KDecoration3 was not found; the KWin6 decoration will not be built. Install libkdecorations3-dev or disable CUTEFISH_BUILD_KWIN6_DECORATION.") endif() endif() -if(CUTEFISH_BUILD_KWIN5_DECORATION) - find_package(Qt5 CONFIG QUIET COMPONENTS Core Gui Widgets X11Extras) - find_package(KF5CoreAddons QUIET) - find_package(KF5Config QUIET) - find_package(KF5WindowSystem QUIET) - find_package(KDecoration2 QUIET) - if(Qt5_FOUND AND KF5CoreAddons_FOUND AND KF5Config_FOUND AND KF5WindowSystem_FOUND AND KDecoration2_FOUND) - find_program(QT_QMAKE_EXECUTABLE qmake) - if(QT_QMAKE_EXECUTABLE) - execute_process(COMMAND "${QT_QMAKE_EXECUTABLE}" -query QT_INSTALL_PLUGINS - OUTPUT_VARIABLE QT_PLUGINS_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) - endif() - if(QT_PLUGINS_DIR) - add_subdirectory(decoration) - else() - message(WARNING "Qt5 plugin directory could not be detected; skipping KDecoration2") - endif() - else() - message(WARNING "KDecoration2 dependencies were not found; skipping the legacy KWin5 decoration") +if(CUTEFISH_BUILD_KWIN6_EFFECT) + # KWinConfig.cmake looks its dependencies up with find modules shipped by ECM, + # and the imported KWin::kwin target expects them all to be known here. + find_package(ECM QUIET NO_MODULE) + if(ECM_FOUND) + list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) endif() -endif() -if(CUTEFISH_BUILD_KWIN5_EFFECT) - message(WARNING "The roundedwindow C++ effect still uses private KWin5 APIs and is disabled by default on current KDE. The JavaScript effects are installed separately.") - add_subdirectory(roundedwindow) + find_package(Qt6 CONFIG QUIET COMPONENTS Core Gui DBus Quick Widgets) + find_package(KF6Config QUIET) + find_package(KF6CoreAddons QUIET) + find_package(KF6WindowSystem QUIET) + find_package(Wayland QUIET COMPONENTS Server) + find_package(epoxy QUIET) + find_package(KWin QUIET) + if(KWin_FOUND) + add_subdirectory(roundedwindow) + message(STATUS "KWin development files found: building the Cutefish rounded corners effect") + else() + message(WARNING "KWin development files were not found; the rounded corners effect will not be built. Install kwin-dev or disable CUTEFISH_BUILD_KWIN6_EFFECT.") + endif() endif() diff --git a/plugins/cutefish_multitasking/CMakeLists.txt b/plugins/cutefish_multitasking/CMakeLists.txt deleted file mode 100644 index e69de29..0000000 diff --git a/plugins/decoration/CMakeLists.txt b/plugins/decoration/CMakeLists.txt index ec720ec..4e147e1 100644 --- a/plugins/decoration/CMakeLists.txt +++ b/plugins/decoration/CMakeLists.txt @@ -1,37 +1,52 @@ -project (cutefishdecoration) -set(CMAKE_CXX_STANDARD 11) +cmake_minimum_required(VERSION 3.16) +project(cutefishdecoration LANGUAGES CXX) -find_package(KF5CoreAddons REQUIRED) -find_package(KF5Config REQUIRED) -find_package(KF5WindowSystem REQUIRED) -find_package(KDecoration2 REQUIRED) -find_package(Qt5 CONFIG REQUIRED COMPONENTS Gui Widgets Core X11Extras) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) -set (decoration_SRCS +include(GNUInstallDirs) + +find_package(Qt6 CONFIG REQUIRED COMPONENTS Core Gui Widgets) +find_package(KF6CoreAddons REQUIRED) +find_package(KDecoration3 REQUIRED) + +set(decoration_SRCS decoration.cpp - x11shadow.cpp button.cpp resources.qrc ) -add_library (cutefishdecoration MODULE - ${decoration_SRCS} -) +add_library(cutefishdecoration MODULE ${decoration_SRCS}) -target_link_libraries (cutefishdecoration - PUBLIC - Qt5::Core - Qt5::Gui - Qt5::Widgets - Qt5::X11Extras - KF5::ConfigCore - KF5::ConfigGui - KF5::CoreAddons - KF5::WindowSystem +# KWin looks the decoration plugin up by file name (the "library" key of the +# [org.kde.kdecoration2] group in kwinrc), so the file has to be named after the +# plugin id. +set_target_properties(cutefishdecoration PROPERTIES + PREFIX "" + OUTPUT_NAME "org.cutefish.decoration") +target_link_libraries(cutefishdecoration PRIVATE - KDecoration2::KDecoration + Qt6::Core + Qt6::Gui + Qt6::Widgets + KF6::CoreAddons + KDecoration3::KDecoration ) -install (TARGETS cutefishdecoration - DESTINATION ${QT_PLUGINS_DIR}/org.kde.kdecoration2) +if(NOT KDE_INSTALL_PLUGINDIR) + find_program(QT6_QTPATHS_EXECUTABLE qtpaths6) + if(QT6_QTPATHS_EXECUTABLE) + execute_process(COMMAND "${QT6_QTPATHS_EXECUTABLE}" --query QT_INSTALL_PLUGINS + OUTPUT_VARIABLE KDE_INSTALL_PLUGINDIR OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() +endif() + +if(NOT KDE_INSTALL_PLUGINDIR) + set(KDE_INSTALL_PLUGINDIR "${CMAKE_INSTALL_LIBDIR}/qt6/plugins") +endif() + +install(TARGETS cutefishdecoration + DESTINATION "${KDE_INSTALL_PLUGINDIR}/org.kde.kdecoration3") diff --git a/plugins/decoration/button.cpp b/plugins/decoration/button.cpp index e144790..cd1cc0f 100644 --- a/plugins/decoration/button.cpp +++ b/plugins/decoration/button.cpp @@ -1,50 +1,35 @@ -/* - * Copyright (C) 2020 PandaOS Team. - * - * Author: rekols - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - #include "button.h" #include "decoration.h" -#include -#include +#include #include -#include -Button::Button(KDecoration2::DecorationButtonType type, const QPointer &decoration, QObject *parent) - : KDecoration2::DecorationButton(type, decoration, parent) +Button::Button(KDecoration3::DecorationButtonType type, + KDecoration3::Decoration *decoration, + QObject *parent) + : KDecoration3::DecorationButton(type, decoration, parent) { - auto c = decoration->client().toStrongRef().data(); + auto *window = decoration ? decoration->window() : nullptr; + if (!window) { + setVisible(false); + return; + } switch (type) { - case KDecoration2::DecorationButtonType::Menu: + case KDecoration3::DecorationButtonType::Minimize: + setVisible(window->isMinimizeable()); + connect(window, &KDecoration3::DecoratedWindow::minimizeableChanged, this, &Button::setVisible); break; - case KDecoration2::DecorationButtonType::Minimize: - setVisible(c->isMinimizeable()); - connect(c, &KDecoration2::DecoratedClient::minimizeableChanged, this, &Button::setVisible); + case KDecoration3::DecorationButtonType::Maximize: + setVisible(window->isMaximizeable()); + connect(window, &KDecoration3::DecoratedWindow::maximizeableChanged, this, &Button::setVisible); break; - case KDecoration2::DecorationButtonType::Maximize: - setVisible(c->isMaximizeable()); - connect(c, &KDecoration2::DecoratedClient::maximizeableChanged, this, &Button::setVisible); + case KDecoration3::DecorationButtonType::Close: + setVisible(window->isCloseable()); + connect(window, &KDecoration3::DecoratedWindow::closeableChanged, this, &Button::setVisible); break; - case KDecoration2::DecorationButtonType::Close: - setVisible(c->isCloseable()); - connect(c, &KDecoration2::DecoratedClient::closeableChanged, this, &Button::setVisible); + case KDecoration3::DecorationButtonType::Menu: break; default: setVisible(false); @@ -52,69 +37,49 @@ Button::Button(KDecoration2::DecorationButtonType type, const QPointer(this->decoration()); + Q_UNUSED(repaintArea) - if (!decoration) + auto *decoration = qobject_cast(this->decoration()); + if (!decoration) { return; + } - auto c = decoration->client().toStrongRef().data(); - const bool isDarkMode = decoration->darkMode(); - const QRect &rect = geometry().toRect(); + const QRectF buttonRect = geometry(); + const qreal scale = decoration->devicePixelRatio(); + const QRectF hoverRect = buttonRect.adjusted(2 * scale, 2 * scale, -2 * scale, -2 * scale); + const QRectF imageRect = QRectF(0, 0, 24 * scale, 24 * scale); painter->save(); + painter->setRenderHint(QPainter::Antialiasing); painter->setRenderHint(QPainter::SmoothPixmapTransform, false); - painter->setRenderHints(QPainter::Antialiasing, true); - - QRect btnRect(0, 0, 26 * decoration->devicePixelRatio(), - 26 * decoration->devicePixelRatio()); - btnRect.moveCenter(rect.center()); - - QRect imgRect(0, 0, 24 * decoration->devicePixelRatio(), - 24 * decoration->devicePixelRatio()); - imgRect.moveCenter(rect.center()); - if (isHovered() || isPressed()) { painter->setPen(Qt::NoPen); - painter->setBrush(isDarkMode ? isPressed() ? QColor(255, 255, 255, 255 * 0.1) : QColor(255, 255, 255, 255 * 0.15) - : isPressed() ? QColor(0, 0, 0, 255 * 0.15) : QColor(0, 0, 0, 255 * 0.1)); - painter->drawRoundedRect(btnRect.adjusted(2, 2, -2, -2), btnRect.height() / 2, btnRect.height() / 2); + painter->setBrush(decoration->darkMode() + ? (isPressed() ? QColor(255, 255, 255, 26) : QColor(255, 255, 255, 38)) + : (isPressed() ? QColor(0, 0, 0, 38) : QColor(0, 0, 0, 26))); + painter->drawRoundedRect(hoverRect, hoverRect.height() / 2, hoverRect.height() / 2); } - switch (type()) { - case KDecoration2::DecorationButtonType::Menu: { - c->icon().paint(painter, rect); - break; - } - case KDecoration2::DecorationButtonType::ApplicationMenu: { - break; - } - case KDecoration2::DecorationButtonType::Minimize: { - painter->drawPixmap(imgRect, decoration->minimizeBtnPixmap()); - break; + if (type() == KDecoration3::DecorationButtonType::Menu) { + if (auto *window = decoration->window()) { + window->icon().paint(painter, buttonRect.toRect()); + } + } else { + const QPixmap pixmap = decoration->buttonPixmap(type(), isChecked()); + QRectF centered = imageRect; + centered.moveCenter(buttonRect.center()); + if (!pixmap.isNull()) { + painter->drawPixmap(centered, pixmap, pixmap.rect()); + } } - case KDecoration2::DecorationButtonType::Maximize: { - if (isChecked()) - painter->drawPixmap(imgRect, decoration->restoreBtnPixmap()); - else - painter->drawPixmap(imgRect, decoration->maximizeBtnPixmap()); - break; - } - case KDecoration2::DecorationButtonType::Close: { - painter->drawPixmap(imgRect, decoration->closeBtnPixmap()); - break; - } - default: - break; - } - painter->restore(); } diff --git a/plugins/decoration/button.h b/plugins/decoration/button.h index eb979f3..bfd503d 100644 --- a/plugins/decoration/button.h +++ b/plugins/decoration/button.h @@ -1,36 +1,18 @@ -/* - * Copyright (C) 2020 PandaOS Team. - * - * Author: rekols - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ +#pragma once -#ifndef BUTTON_H -#define BUTTON_H +#include -#include - -class Button : KDecoration2::DecorationButton +class Button : public KDecoration3::DecorationButton { public: - explicit Button(KDecoration2::DecorationButtonType type, const QPointer &decoration, QObject *parent = nullptr); + explicit Button(KDecoration3::DecorationButtonType type, + KDecoration3::Decoration *decoration, + QObject *parent = nullptr); - static DecorationButton *create(KDecoration2::DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent); + static KDecoration3::DecorationButton *create(KDecoration3::DecorationButtonType type, + KDecoration3::Decoration *decoration, + QObject *parent); protected: - void paint(QPainter *painter, const QRect &repaintRegion) override; + void paint(QPainter *painter, const QRectF &repaintArea) override; }; - -#endif // BUTTON_H \ No newline at end of file diff --git a/plugins/decoration/cutefishos.json b/plugins/decoration/cutefishos.json index 8fa82fe..a694784 100644 --- a/plugins/decoration/cutefishos.json +++ b/plugins/decoration/cutefishos.json @@ -1,14 +1,19 @@ { "KPlugin": { - "Description": "Window decoration", + "Description": "CutefishOS window decoration for KDE Plasma 6", "EnabledByDefault": true, "Id": "org.cutefish.decoration", "Name": "CutefishOS Window Frame", "ServiceTypes": [ - "org.kde.kdecoration2" - ] + "org.kde.kdecoration3" + ], + "Version": "1.0" }, - "org.kde.kdecoration2": { - "blur": false + "org.kde.kdecoration3": { + "blur": false, + "recommended": true, + "styles": [ + "titled" + ] } } diff --git a/plugins/decoration/decoration.cpp b/plugins/decoration/decoration.cpp index 5681410..00cb8f9 100644 --- a/plugins/decoration/decoration.cpp +++ b/plugins/decoration/decoration.cpp @@ -1,393 +1,316 @@ -/* - * Copyright (C) 2020 PandaOS Team. - * - * Author: rekols - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -// own #include "decoration.h" #include "button.h" -// KDecoration -#include -#include -#include +#include +#include +#include +#include -// Qt -#include -#include -#include -#include +#include #include -#include - -#include +#include +#include #include -K_PLUGIN_FACTORY_WITH_JSON( - CutefishDecorationFactory, - "cutefishos.json", - registerPlugin();); +K_PLUGIN_FACTORY_WITH_JSON(CutefishDecorationFactory, "cutefishos.json", + registerPlugin();) namespace Cutefish { -static int g_sDecoCount = 0; -static int g_shadowSize = 0; -static int g_shadowStrength = 0; -static QColor g_shadowColor = Qt::black; -static QSharedPointer g_sShadow; + +static int s_decorationCount = 0; +static int s_shadowRadius = -1; +static std::shared_ptr s_shadow; Decoration::Decoration(QObject *parent, const QVariantList &args) - : KDecoration2::Decoration(parent, args) - , m_settings(new QSettings(QSettings::UserScope, "cutefishos", "theme")) - , m_settingsFile(m_settings->fileName()) - , m_fileWatcher(new QFileSystemWatcher) - , m_x11Shadow(new X11Shadow) + : KDecoration3::Decoration(parent, args) + , m_themeSettings(QSettings::UserScope, QStringLiteral("cutefishos"), QStringLiteral("theme")) { - ++g_sDecoCount; + ++s_decorationCount; } Decoration::~Decoration() { - if (--g_sDecoCount == 0) { - g_sShadow.clear(); + if (--s_decorationCount == 0) { + s_shadow.reset(); + s_shadowRadius = -1; } } -void Decoration::paint(QPainter *painter, const QRect &repaintRegion) +bool Decoration::init() { - auto *decoratedClient = client().toStrongRef().data(); - auto s = settings(); - - painter->fillRect(rect(), Qt::transparent); - - if (!decoratedClient->isShaded()) { - // paintFrameBackground(painter, repaintRegion); - - painter->fillRect(rect(), Qt::transparent); - painter->save(); - painter->setRenderHint(QPainter::Antialiasing); - painter->setPen(Qt::NoPen); - painter->setBrush(titleBarBackgroundColor()); - - if (s->isAlphaChannelSupported() && radiusAvailable()) { - painter->drawRoundedRect(rect(), m_frameRadius, m_frameRadius); - } else { - painter->drawRect(rect()); - } - painter->restore(); - - // draw buttons. - m_leftButtons->paint(painter, repaintRegion); - m_rightButtons->paint(painter, repaintRegion); + if (!window()) { + return false; } - paintCaption(painter, repaintRegion); - paintButtons(painter, repaintRegion); -} - -void Decoration::init() -{ - auto c = client().toStrongRef().data(); - auto s = settings(); - - m_devicePixelRatio = m_settings->value("PixelRatio", 1.0).toReal(); - m_frameRadius = 11 * m_devicePixelRatio; - - reconfigure(); - updateTitleBar(); - - connect(s.data(), &KDecoration2::DecorationSettings::borderSizeChanged, this, &Decoration::recalculateBorders); + m_themeSettingsFile = m_themeSettings.fileName(); + reloadTheme(); - // a change in font might cause the borders to change - connect(s.data(), &KDecoration2::DecorationSettings::fontChanged, this, &Decoration::recalculateBorders); - connect(s.data(), &KDecoration2::DecorationSettings::spacingChanged, this, &Decoration::recalculateBorders); + m_leftButtons = new KDecoration3::DecorationButtonGroup( + KDecoration3::DecorationButtonGroup::Position::Left, this, &Button::create); + m_rightButtons = new KDecoration3::DecorationButtonGroup( + KDecoration3::DecorationButtonGroup::Position::Right, this, &Button::create); - // full reconfiguration - connect(s.data(), &KDecoration2::DecorationSettings::reconfigured, this, &Decoration::reconfigure); - connect(s.data(), &KDecoration2::DecorationSettings::reconfigured, this, &Decoration::updateButtonsGeometryDelayed); - - // buttons - connect(s.data(), &KDecoration2::DecorationSettings::spacingChanged, this, &Decoration::updateButtonsGeometryDelayed); - connect(s.data(), &KDecoration2::DecorationSettings::decorationButtonsLeftChanged, this, &Decoration::updateButtonsGeometryDelayed); - connect(s.data(), &KDecoration2::DecorationSettings::decorationButtonsRightChanged, this, &Decoration::updateButtonsGeometryDelayed); - - connect(c, &KDecoration2::DecoratedClient::adjacentScreenEdgesChanged, this, &Decoration::recalculateBorders); - connect(c, &KDecoration2::DecoratedClient::maximizedHorizontallyChanged, this, &Decoration::recalculateBorders); - connect(c, &KDecoration2::DecoratedClient::maximizedVerticallyChanged, this, &Decoration::recalculateBorders); - connect(c, &KDecoration2::DecoratedClient::shadedChanged, this, &Decoration::recalculateBorders); - connect(c, &KDecoration2::DecoratedClient::captionChanged, this, [this]() { - // update the caption area - update(titleBar()); + auto window = this->window(); + connect(window, &KDecoration3::DecoratedWindow::captionChanged, this, [this] { update(); }); + connect(window, &KDecoration3::DecoratedWindow::activeChanged, this, [this] { update(); }); + connect(window, &KDecoration3::DecoratedWindow::shadedChanged, this, [this] { + updateGeometry(); + updateButtonsGeometry(); + update(); }); - - connect(c, &KDecoration2::DecoratedClient::activeChanged, this, [this] { - update(titleBar()); + connect(window, &KDecoration3::DecoratedWindow::maximizedChanged, this, [this] { + updateGeometry(); + updateButtonsGeometry(); + update(); }); - - connect(c, &KDecoration2::DecoratedClient::widthChanged, this, &Decoration::updateTitleBar); - - connect(c, &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::updateTitleBar); - connect(c, &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::updateButtonsGeometry); - - connect(c, &KDecoration2::DecoratedClient::widthChanged, this, &Decoration::updateButtonsGeometry); - connect(c, &KDecoration2::DecoratedClient::adjacentScreenEdgesChanged, this, &Decoration::updateButtonsGeometry); - connect(c, &KDecoration2::DecoratedClient::shadedChanged, this, &Decoration::updateButtonsGeometry); - - // cutefishos settings - m_fileWatcher->addPath(m_settingsFile); - connect(m_fileWatcher, &QFileSystemWatcher::fileChanged, this, [=] { - m_settings->sync(); - m_devicePixelRatio = m_settings->value("PixelRatio", 1.0).toReal(); - - updateBtnPixmap(); - update(titleBar()); - updateTitleBar(); + connect(window, &KDecoration3::DecoratedWindow::widthChanged, this, [this] { + updateGeometry(); updateButtonsGeometry(); - reconfigure(); - - bool fileDeleted = !m_fileWatcher->files().contains(m_settingsFile); - if (fileDeleted) - m_fileWatcher->addPath(m_settingsFile); }); + connect(window, &KDecoration3::DecoratedWindow::heightChanged, this, [this] { updateGeometry(); }); + + if (settings()) { + connect(settings().get(), &KDecoration3::DecorationSettings::fontChanged, this, [this] { + updateGeometry(); + update(); + }); + connect(settings().get(), &KDecoration3::DecorationSettings::spacingChanged, this, [this] { + updateButtonsGeometry(); + update(); + }); + connect(settings().get(), &KDecoration3::DecorationSettings::decorationButtonsLeftChanged, this, + [this] { updateButtonsGeometry(); }); + connect(settings().get(), &KDecoration3::DecorationSettings::decorationButtonsRightChanged, this, + [this] { updateButtonsGeometry(); }); + connect(settings().get(), &KDecoration3::DecorationSettings::reconfigured, this, [this] { + reloadTheme(); + updateGeometry(); + updateButtonsGeometry(); + update(); + }); + } - updateBtnPixmap(); - createButtons(); - - // // For some reason, the shadow should be installed the last. Otherwise, - // // the Window Decorations KCM crashes. - updateShadow(); -} - -void Decoration::reconfigure() -{ - recalculateBorders(); - updateResizeBorders(); - updateShadow(); -} + // Follow the CutefishOS theme settings (dark mode, scaling) while the window is open. + if (!m_themeSettingsFile.isEmpty()) { + m_themeWatcher.addPath(m_themeSettingsFile); + connect(&m_themeWatcher, &QFileSystemWatcher::fileChanged, this, [this] { + reloadTheme(); + updateGeometry(); + updateButtonsGeometry(); + update(); + + // Editors replace the file instead of writing in place, which drops the watch. + if (!m_themeWatcher.files().contains(m_themeSettingsFile)) { + m_themeWatcher.addPath(m_themeSettingsFile); + } + }); + } -void Decoration::createButtons() -{ - m_leftButtons = new KDecoration2::DecorationButtonGroup(KDecoration2::DecorationButtonGroup::Position::Left, this, &Button::create); - m_rightButtons = new KDecoration2::DecorationButtonGroup(KDecoration2::DecorationButtonGroup::Position::Right, this, &Button::create); + updateGeometry(); updateButtonsGeometry(); + return true; } -void Decoration::recalculateBorders() +void Decoration::paint(QPainter *painter, const QRectF &repaintArea) { - QMargins borders; - -// if (!isMaximized()) { -// borders.setLeft(m_frameRadius / 2); -// borders.setRight(m_frameRadius / 2); -// borders.setBottom(m_frameRadius / 2); -// } - - borders.setTop(titleBarHeight()); + if (!window()) { + return; + } - setBorders(borders); -} + if (!window()->isShaded()) { + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(Qt::NoPen); + painter->setBrush(titleBarBackgroundColor()); -void Decoration::updateResizeBorders() -{ - QMargins borders; + const bool rounded = !window()->isMaximized() + && (!settings() || settings()->isAlphaChannelSupported()); + if (rounded) { + painter->drawRoundedRect(rect(), m_frameRadius, m_frameRadius); + } else { + painter->drawRect(rect()); + } + painter->restore(); - borders.setLeft(5); - borders.setTop(5); - borders.setRight(5); - borders.setBottom(5); + if (m_leftButtons) { + m_leftButtons->paint(painter, repaintArea); + } + if (m_rightButtons) { + m_rightButtons->paint(painter, repaintArea); + } + } - setResizeOnlyBorders(borders); + paintCaption(painter); } -void Decoration::updateTitleBar() +void Decoration::updateGeometry() { - auto *decoratedClient = client().toStrongRef().data(); - setTitleBar(QRect(0, 0, decoratedClient->width(), titleBarHeight())); - update(titleBar()); -} + if (!window()) { + return; + } -void Decoration::updateButtonsGeometryDelayed() -{ - QTimer::singleShot(0, this, &Decoration::updateButtonsGeometry); + const qreal height = titleBarHeight(); + setBorders(QMarginsF(0, height, 0, 0)); + setResizeOnlyBorders(QMarginsF(5, 5, 5, 5)); + setTitleBar(QRectF(0, 0, window()->width(), height)); } void Decoration::updateButtonsGeometry() { - auto s = settings(); - // auto c = client().toStrongRef().data(); - int rightMargin = 2; - int btnSpacing = 8; - - foreach (const QPointer &button, m_leftButtons->buttons() + m_rightButtons->buttons()) { - button.data()->setGeometry(QRectF(QPoint(0, 0), QSizeF(titleBarHeight(), titleBarHeight()))); - } - - if (!m_leftButtons->buttons().isEmpty()) { - m_leftButtons->setPos(QPointF(0, 0)); - m_leftButtons->setSpacing(btnSpacing); + if (!window() || !m_leftButtons || !m_rightButtons) { + return; } - if (!m_rightButtons->buttons().isEmpty()) { - m_rightButtons->setSpacing(btnSpacing); - m_rightButtons->setPos(QPointF(size().width() - m_rightButtons->geometry().width() - rightMargin, 0)); + const QSizeF buttonSize(titleBarHeight(), titleBarHeight()); + const auto buttons = m_leftButtons->buttons() + m_rightButtons->buttons(); + for (auto *button : buttons) { + button->setGeometry(QRectF(QPointF(0, 0), buttonSize)); } - update(); + constexpr qreal spacing = 8.0; + m_leftButtons->setSpacing(spacing); + m_leftButtons->setPos(QPointF(0, 0)); + m_rightButtons->setSpacing(spacing); + m_rightButtons->setPos(QPointF(size().width() - m_rightButtons->geometry().width() - 2, 0)); } void Decoration::updateShadow() { - // assign global shadow if exists and parameters match - if (!g_sShadow) { - // assign parameters - g_shadowSize = 90; - g_shadowStrength = 35; - g_shadowColor = Qt::black; - const int shadowOverlap = m_frameRadius; - // const int shadowOffset = qMax(6 * g_shadowSize / 16, shadowOverlap * 2); - const int shadowOffset = shadowOverlap / 2; - - // create image - QImage image(2 * g_shadowSize, 2 * g_shadowSize, QImage::Format_ARGB32_Premultiplied); - image.fill(Qt::transparent); - - // create gradient - // gaussian delta function - auto alpha = [](qreal x) { return std::exp( -x*x/0.15 ); }; - - // color calculation delta function - auto gradientStopColor = [](QColor color, int alpha) { - color.setAlpha(alpha); - return color; - }; - - QRadialGradient radialGradient(g_shadowSize, g_shadowSize, g_shadowSize); - for (int i = 0; i < 10; ++i) { - const qreal x(qreal( i ) / 9); - radialGradient.setColorAt(x, gradientStopColor(g_shadowColor, alpha(x) * g_shadowStrength)); - } - - radialGradient.setColorAt(1, gradientStopColor(g_shadowColor, 0 )); - - QPainter painter; - // fill - painter.begin(&image); - //TODO review these - //QPainter painter(&image); - painter.setRenderHint( QPainter::Antialiasing, true ); - painter.fillRect( image.rect(), radialGradient); - - // contrast pixel - QRectF innerRect = QRectF( - g_shadowSize - shadowOverlap, g_shadowSize - shadowOffset - shadowOverlap, - 2 * shadowOverlap, shadowOffset + 2 * shadowOverlap ); - // g_shadowSize - shadowOffset - shadowOverlap, g_shadowSize - shadowOffset - shadowOverlap, - // shadowOffset + 2*shadowOverlap, shadowOffset + 2*shadowOverlap ); - - painter.setPen( gradientStopColor(g_shadowColor, g_shadowStrength * 0.5)); - painter.setBrush(Qt::NoBrush); - painter.drawRoundedRect(innerRect, -0.5 + m_frameRadius, -0.5 + m_frameRadius); - - // mask out inner rect - painter.setPen(Qt::NoPen); - painter.setBrush(Qt::black); - painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); - painter.drawRoundedRect(innerRect, 0.5 + m_frameRadius, 0.5 + m_frameRadius); - painter.end(); - - g_sShadow = QSharedPointer::create(); - g_sShadow->setPadding( QMargins( - // g_shadowSize - shadowOffset - shadowOverlap, - g_shadowSize - shadowOverlap, - g_shadowSize - shadowOffset - shadowOverlap, - g_shadowSize - shadowOverlap, - g_shadowSize - shadowOverlap)); - - g_sShadow->setInnerShadowRect(QRect(g_shadowSize, g_shadowSize, 1, 1)); - - // assign image - g_sShadow->setShadow(image); + if (s_shadow && s_shadowRadius == m_frameRadius) { + setShadow(s_shadow); + return; } - setShadow(g_sShadow); -} - -void Decoration::updateBtnPixmap() -{ - int size = 24; - QString dirName = darkMode() ? "dark" : "light"; - - m_closeBtnPixmap = fromSvgToPixmap(QString(":/images/%1/close_normal.svg").arg(dirName), QSize(size, size)); - m_maximizeBtnPixmap = fromSvgToPixmap(QString(":/images/%1/maximize_normal.svg").arg(dirName), QSize(size, size)); - m_minimizeBtnPixmap = fromSvgToPixmap(QString(":/images/%1/minimize_normal.svg").arg(dirName), QSize(size, size)); - m_restoreBtnPixmap = fromSvgToPixmap(QString(":/images/%1/restore_normal.svg").arg(dirName), QSize(size, size)); + const int shadowSize = 90; + const int shadowStrength = 35; + const QColor shadowColor = Qt::black; + const int shadowOverlap = m_frameRadius; + const int shadowOffset = shadowOverlap / 2; + + QImage image(2 * shadowSize, 2 * shadowSize, QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::transparent); + + // Gaussian delta function used to fade the shadow out. + auto alpha = [](qreal x) { return std::exp(-x * x / 0.15); }; + auto gradientStopColor = [](QColor color, int alpha) { + color.setAlpha(alpha); + return color; + }; + + QRadialGradient radialGradient(shadowSize, shadowSize, shadowSize); + for (int i = 0; i < 10; ++i) { + const qreal x(qreal(i) / 9); + radialGradient.setColorAt(x, gradientStopColor(shadowColor, alpha(x) * shadowStrength)); + } + radialGradient.setColorAt(1, gradientStopColor(shadowColor, 0)); + + QPainter painter(&image); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.fillRect(image.rect(), radialGradient); + + const QRectF innerRect(shadowSize - shadowOverlap, + shadowSize - shadowOffset - shadowOverlap, + 2 * shadowOverlap, + shadowOffset + 2 * shadowOverlap); + + // Contrast pixel around the window. + painter.setPen(gradientStopColor(shadowColor, shadowStrength * 0.5)); + painter.setBrush(Qt::NoBrush); + painter.drawRoundedRect(innerRect, -0.5 + m_frameRadius, -0.5 + m_frameRadius); + + // Mask out the area covered by the window itself. + painter.setPen(Qt::NoPen); + painter.setBrush(Qt::black); + painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); + painter.drawRoundedRect(innerRect, 0.5 + m_frameRadius, 0.5 + m_frameRadius); + painter.end(); + + s_shadow = std::make_shared(); + s_shadow->setPadding(QMarginsF(shadowSize - shadowOverlap, + shadowSize - shadowOffset - shadowOverlap, + shadowSize - shadowOverlap, + shadowSize - shadowOverlap)); + s_shadow->setInnerShadowRect(QRectF(shadowSize, shadowSize, 1, 1)); + s_shadow->setShadow(image); + s_shadowRadius = m_frameRadius; + + setShadow(s_shadow); } -QPixmap Decoration::fromSvgToPixmap(const QString &file, const QSize &size) +void Decoration::reloadTheme() { - QImageReader reader(file); + m_themeSettings.sync(); - if (reader.canRead()) { - reader.setScaledSize(size * m_devicePixelRatio); - return QPixmap::fromImage(reader.read()); + m_devicePixelRatio = m_themeSettings.value(QStringLiteral("PixelRatio"), 1.0).toReal(); + if (m_devicePixelRatio <= 0) { + m_devicePixelRatio = 1.0; } + m_darkMode = m_themeSettings.value(QStringLiteral("DarkMode"), false).toBool(); + m_frameRadius = qRound(11 * m_devicePixelRatio); - return QPixmap(); + updateButtonPixmaps(); + updateShadow(); } -int Decoration::titleBarHeight() const +void Decoration::updateButtonPixmaps() { - return m_titleBarHeight * m_devicePixelRatio; - - // const QFontMetrics fontMetrics(settings()->font()); - // const int baseUnit = settings()->gridUnit(); - // return qRound(1.5 * baseUnit) + fontMetrics.height(); + m_closeBtnPixmap = loadPixmap(pixmapPath(KDecoration3::DecorationButtonType::Close, false)); + m_maximizeBtnPixmap = loadPixmap(pixmapPath(KDecoration3::DecorationButtonType::Maximize, false)); + m_minimizeBtnPixmap = loadPixmap(pixmapPath(KDecoration3::DecorationButtonType::Minimize, false)); + m_restoreBtnPixmap = loadPixmap(pixmapPath(KDecoration3::DecorationButtonType::Maximize, true)); } -bool Decoration::darkMode() const +QPixmap Decoration::loadPixmap(const QString &path) const { - QSettings settings(QSettings::UserScope, "cutefishos", "theme"); - return settings.value("DarkMode", false).toBool(); + QImageReader reader(path); + if (!reader.canRead()) { + return {}; + } + + // The icons are SVGs, render them at the size the buttons are painted with. + reader.setScaledSize(QSize(24, 24) * m_devicePixelRatio); + return QPixmap::fromImage(reader.read()); } -bool Decoration::radiusAvailable() const +QPixmap Decoration::buttonPixmap(KDecoration3::DecorationButtonType type, bool checked) const { - return !isMaximized(); - // return client().toStrongRef().data()->adjacentScreenEdges() == Qt::Edges(); + switch (type) { + case KDecoration3::DecorationButtonType::Close: + return m_closeBtnPixmap; + case KDecoration3::DecorationButtonType::Maximize: + return checked ? m_restoreBtnPixmap : m_maximizeBtnPixmap; + case KDecoration3::DecorationButtonType::Minimize: + return m_minimizeBtnPixmap; + default: + return {}; + } } -bool Decoration::isMaximized() const +QString Decoration::pixmapPath(KDecoration3::DecorationButtonType type, bool checked) const { - return client().toStrongRef().data()->isMaximized(); + const QString mode = darkMode() ? QStringLiteral("dark") : QStringLiteral("light"); + QString name; + switch (type) { + case KDecoration3::DecorationButtonType::Close: + name = QStringLiteral("close_normal.svg"); + break; + case KDecoration3::DecorationButtonType::Maximize: + name = checked ? QStringLiteral("restore_normal.svg") : QStringLiteral("maximize_normal.svg"); + break; + case KDecoration3::DecorationButtonType::Minimize: + name = QStringLiteral("minimize_normal.svg"); + break; + default: + return {}; + } + return QStringLiteral(":/images/%1/%2").arg(mode, name); } -void Decoration::paintFrameBackground(QPainter *painter, const QRect &repaintRegion) const +int Decoration::titleBarHeight() const { - Q_UNUSED(repaintRegion) - - // const auto *decoratedClient = client().toStrongRef().data(); - - painter->save(); - - painter->fillRect(rect(), Qt::transparent); - painter->setRenderHint(QPainter::Antialiasing); - painter->setPen(Qt::NoPen); - painter->restore(); + return qMax(1, qRound(m_titleBarHeight * m_devicePixelRatio)); } QColor Decoration::titleBarBackgroundColor() const @@ -397,65 +320,44 @@ QColor Decoration::titleBarBackgroundColor() const QColor Decoration::titleBarForegroundColor() const { - const auto *decoratedClient = client().toStrongRef().data(); - const bool isActive = decoratedClient->isActive(); - QColor color; - - if (isActive) { - color = darkMode() ? m_titleBarFgDarkColor : m_titleBarFgColor; - } else { - color = darkMode() ? m_unfocusedFgDarkColor : m_unfocusedFgColor; + if (window() && window()->isActive()) { + return darkMode() ? m_titleBarFgDarkColor : m_titleBarFgColor; } - - return color; + return darkMode() ? m_unfocusedFgDarkColor : m_unfocusedFgColor; } -void Decoration::paintCaption(QPainter *painter, const QRect &repaintRegion) const +void Decoration::paintCaption(QPainter *painter) const { - Q_UNUSED(repaintRegion) - - const auto *decoratedClient = client().toStrongRef().data(); - - const int textWidth = settings()->fontMetrics().boundingRect(decoratedClient->caption()).width(); - const QRect textRect((size().width() - textWidth) / 2, 0, textWidth, titleBarHeight()); - - const QRect titleBarRect(0, 0, size().width(), titleBarHeight()); - - const QRect availableRect = titleBarRect.adjusted( - m_leftButtons->geometry().width() + 20, 0, - -(m_rightButtons->geometry().width() + 20), 0 - ); - - QRect captionRect; - Qt::Alignment alignment; + if (!window()) { + return; + } - if (textRect.left() < availableRect.left()) { - captionRect = availableRect; + QFont font = settings() ? settings()->font() : QFont(); + QFontMetricsF metrics(font); + const QRectF titleRect(0, 0, size().width(), titleBarHeight()); + const qreal left = m_leftButtons ? m_leftButtons->geometry().width() + 20 : 20; + const qreal right = m_rightButtons ? m_rightButtons->geometry().width() + 20 : 20; + const QRectF available = titleRect.adjusted(left, 0, -right, 0); + const qreal textWidth = metrics.horizontalAdvance(window()->caption()); + const QRectF natural((size().width() - textWidth) / 2, 0, textWidth, titleBarHeight()); + + Qt::Alignment alignment = Qt::AlignCenter; + QRectF captionRect = titleRect; + if (natural.left() < available.left()) { + captionRect = available; alignment = Qt::AlignLeft | Qt::AlignVCenter; - } else if (availableRect.right() < textRect.right()) { - captionRect = availableRect; + } else if (natural.right() > available.right()) { + captionRect = available; alignment = Qt::AlignRight | Qt::AlignVCenter; - } else { - captionRect = titleBarRect; - alignment = Qt::AlignCenter; } - const QString caption = painter->fontMetrics() - .elidedText(decoratedClient->caption(), Qt::ElideMiddle, captionRect.width()); - painter->save(); - painter->setFont(settings()->font()); + painter->setFont(font); painter->setPen(titleBarForegroundColor()); - painter->drawText(captionRect, alignment, caption); + painter->drawText(captionRect, alignment, metrics.elidedText(window()->caption(), Qt::ElideMiddle, qRound(captionRect.width()))); painter->restore(); } -void Decoration::paintButtons(QPainter *painter, const QRect &repaintRegion) const -{ - m_leftButtons->paint(painter, repaintRegion); - m_rightButtons->paint(painter, repaintRegion); -} - } #include "decoration.moc" diff --git a/plugins/decoration/decoration.h b/plugins/decoration/decoration.h index 099f5c8..a1ec161 100644 --- a/plugins/decoration/decoration.h +++ b/plugins/decoration/decoration.h @@ -1,118 +1,65 @@ -/* - * Copyright (C) 2020 PandaOS Team. - * - * Author: rekols - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - #pragma once -// KDecoration -#include -#include +#include +#include -// Qt #include +#include #include -#include -#include - -#include "x11shadow.h" namespace Cutefish { -class CloseButton; -class MaximizeButton; -class MinimizeButton; - -class Decoration : public KDecoration2::Decoration +class Decoration : public KDecoration3::Decoration { Q_OBJECT public: - Decoration(QObject *parent = nullptr, const QVariantList &args = QVariantList()); + explicit Decoration(QObject *parent = nullptr, const QVariantList &args = {}); ~Decoration() override; - void paint(QPainter *painter, const QRect &repaintRegion) override; - - QPixmap closeBtnPixmap() { return m_closeBtnPixmap; } - QPixmap maximizeBtnPixmap() { return m_maximizeBtnPixmap; } - QPixmap minimizeBtnPixmap() { return m_minimizeBtnPixmap; } - QPixmap restoreBtnPixmap() { return m_restoreBtnPixmap; } + bool init() override; + void paint(QPainter *painter, const QRectF &repaintArea) override; - bool darkMode() const; + bool darkMode() const { return m_darkMode; } qreal devicePixelRatio() const { return m_devicePixelRatio; } - -public slots: - void init() override; + QPixmap buttonPixmap(KDecoration3::DecorationButtonType type, bool checked = false) const; private: - void reconfigure(); - void createButtons(); - void recalculateBorders(); - void updateResizeBorders(); - void updateTitleBar(); - void updateButtonsGeometryDelayed(); + void updateGeometry(); void updateButtonsGeometry(); void updateShadow(); - - void updateBtnPixmap(); - QPixmap fromSvgToPixmap(const QString &file, const QSize &size); + void reloadTheme(); + void updateButtonPixmaps(); + void paintCaption(QPainter *painter) const; int titleBarHeight() const; - QColor titleBarBackgroundColor() const; QColor titleBarForegroundColor() const; + QString pixmapPath(KDecoration3::DecorationButtonType type, bool checked) const; + QPixmap loadPixmap(const QString &path) const; - bool radiusAvailable() const; - bool isMaximized() const; - - void paintFrameBackground(QPainter *painter, const QRect &repaintRegion) const; - void paintCaption(QPainter *painter, const QRect &repaintRegion) const; - void paintButtons(QPainter *painter, const QRect &repaintRegion) const; + KDecoration3::DecorationButtonGroup *m_leftButtons = nullptr; + KDecoration3::DecorationButtonGroup *m_rightButtons = nullptr; - KDecoration2::DecorationButtonGroup *m_leftButtons; - KDecoration2::DecorationButtonGroup *m_rightButtons; - - friend class CloseButton; - friend class MaximizeButton; - friend class MinimizeButton; - -private: + qreal m_devicePixelRatio = 1.0; + bool m_darkMode = false; int m_titleBarHeight = 30; int m_frameRadius = 11; - qreal m_devicePixelRatio = 1.0; - QColor m_titleBarBgColor = QColor(255, 255, 255, 255); - QColor m_titleBarFgColor = QColor(56, 56, 56, 255); - QColor m_unfocusedFgColor = QColor(127, 127, 127, 255); - + QColor m_titleBarBgColor = QColor(255, 255, 255); + QColor m_titleBarFgColor = QColor(56, 56, 56); + QColor m_unfocusedFgColor = QColor(127, 127, 127); QColor m_titleBarBgDarkColor = QColor(44, 44, 45); QColor m_titleBarFgDarkColor = QColor(202, 203, 206); QColor m_unfocusedFgDarkColor = QColor(112, 112, 112); - QSettings *m_settings; - QString m_settingsFile; - QFileSystemWatcher *m_fileWatcher; - + QSettings m_themeSettings; + QString m_themeSettingsFile; + QFileSystemWatcher m_themeWatcher; QPixmap m_closeBtnPixmap; QPixmap m_maximizeBtnPixmap; QPixmap m_minimizeBtnPixmap; QPixmap m_restoreBtnPixmap; - - X11Shadow *m_x11Shadow; }; } diff --git a/plugins/decoration/x11shadow.cpp b/plugins/decoration/x11shadow.cpp deleted file mode 100644 index 70ad4a7..0000000 --- a/plugins/decoration/x11shadow.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "x11shadow.h" - -#include -#include - -static xcb_atom_t internAtom(const char *name, bool only_if_exists) -{ - if (!name || *name == 0) - return XCB_NONE; - - if (!QX11Info::isPlatformX11()) - return XCB_NONE; - - xcb_intern_atom_cookie_t cookie = xcb_intern_atom(QX11Info::connection(), only_if_exists, strlen(name), name); - xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(QX11Info::connection(), cookie, 0); - - if (!reply) - return XCB_NONE; - - xcb_atom_t atom = reply->atom; - free(reply); - - return atom; -} - -X11Shadow::X11Shadow(QObject *parent) - : QObject(parent) -{ - m_atom_net_wm_shadow = internAtom("_KDE_NET_WM_SHADOW", false); - m_atom_net_wm_window_type = internAtom("_NET_WM_WINDOW_TYPE", false); -} diff --git a/plugins/decoration/x11shadow.h b/plugins/decoration/x11shadow.h deleted file mode 100644 index a9cae6c..0000000 --- a/plugins/decoration/x11shadow.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef X11SHADOW_H -#define X11SHADOW_H - -#include - -class X11Shadow : public QObject -{ - Q_OBJECT - -public: - explicit X11Shadow(QObject *parent = nullptr); - -private: - quint32 m_atom_net_wm_shadow; - quint32 m_atom_net_wm_window_type; -}; - -#endif // X11SHADOW_H diff --git a/plugins/decoration3/CMakeLists.txt b/plugins/decoration3/CMakeLists.txt deleted file mode 100644 index 642c00d..0000000 --- a/plugins/decoration3/CMakeLists.txt +++ /dev/null @@ -1,52 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(cutefishdecoration3 LANGUAGES CXX) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) - -include(GNUInstallDirs) - -find_package(Qt6 CONFIG REQUIRED COMPONENTS Core Gui Widgets) -find_package(KF6CoreAddons REQUIRED) -find_package(KDecoration3 REQUIRED) - -set(decoration3_SRCS - decoration.cpp - button.cpp - "${CMAKE_CURRENT_SOURCE_DIR}/../decoration/resources.qrc" -) - -add_library(cutefishdecoration3 MODULE ${decoration3_SRCS}) - -# KWin looks the decoration plugin up by file name (the "library" key of the -# [org.kde.kdecoration2] group in kwinrc), so the file has to be named after the -# plugin id. -set_target_properties(cutefishdecoration3 PROPERTIES - PREFIX "" - OUTPUT_NAME "org.cutefish.decoration") - -target_link_libraries(cutefishdecoration3 - PRIVATE - Qt6::Core - Qt6::Gui - Qt6::Widgets - KF6::CoreAddons - KDecoration3::KDecoration -) - -if(NOT KDE_INSTALL_PLUGINDIR) - find_program(QT6_QTPATHS_EXECUTABLE qtpaths6) - if(QT6_QTPATHS_EXECUTABLE) - execute_process(COMMAND "${QT6_QTPATHS_EXECUTABLE}" --query QT_INSTALL_PLUGINS - OUTPUT_VARIABLE KDE_INSTALL_PLUGINDIR OUTPUT_STRIP_TRAILING_WHITESPACE) - endif() -endif() - -if(NOT KDE_INSTALL_PLUGINDIR) - set(KDE_INSTALL_PLUGINDIR "${CMAKE_INSTALL_LIBDIR}/qt6/plugins") -endif() - -install(TARGETS cutefishdecoration3 - DESTINATION "${KDE_INSTALL_PLUGINDIR}/org.kde.kdecoration3") diff --git a/plugins/decoration3/button.cpp b/plugins/decoration3/button.cpp deleted file mode 100644 index b45bf9d..0000000 --- a/plugins/decoration3/button.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2020 PandaOS Team. - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "button.h" -#include "decoration.h" - -#include - -#include - -Button::Button(KDecoration3::DecorationButtonType type, - KDecoration3::Decoration *decoration, - QObject *parent) - : KDecoration3::DecorationButton(type, decoration, parent) -{ - auto *window = decoration ? decoration->window() : nullptr; - if (!window) { - setVisible(false); - return; - } - - switch (type) { - case KDecoration3::DecorationButtonType::Minimize: - setVisible(window->isMinimizeable()); - connect(window, &KDecoration3::DecoratedWindow::minimizeableChanged, this, &Button::setVisible); - break; - case KDecoration3::DecorationButtonType::Maximize: - setVisible(window->isMaximizeable()); - connect(window, &KDecoration3::DecoratedWindow::maximizeableChanged, this, &Button::setVisible); - break; - case KDecoration3::DecorationButtonType::Close: - setVisible(window->isCloseable()); - connect(window, &KDecoration3::DecoratedWindow::closeableChanged, this, &Button::setVisible); - break; - case KDecoration3::DecorationButtonType::Menu: - break; - default: - setVisible(false); - break; - } -} - -KDecoration3::DecorationButton *Button::create(KDecoration3::DecorationButtonType type, - KDecoration3::Decoration *decoration, - QObject *parent) -{ - return new Button(type, decoration, parent); -} - -void Button::paint(QPainter *painter, const QRectF &repaintArea) -{ - Q_UNUSED(repaintArea) - - auto *decoration = qobject_cast(this->decoration()); - if (!decoration) { - return; - } - - const QRectF buttonRect = geometry(); - const qreal scale = decoration->devicePixelRatio(); - const QRectF hoverRect = buttonRect.adjusted(2 * scale, 2 * scale, -2 * scale, -2 * scale); - const QRectF imageRect = QRectF(0, 0, 24 * scale, 24 * scale); - - painter->save(); - painter->setRenderHint(QPainter::Antialiasing); - painter->setRenderHint(QPainter::SmoothPixmapTransform, false); - if (isHovered() || isPressed()) { - painter->setPen(Qt::NoPen); - painter->setBrush(decoration->darkMode() - ? (isPressed() ? QColor(255, 255, 255, 26) : QColor(255, 255, 255, 38)) - : (isPressed() ? QColor(0, 0, 0, 38) : QColor(0, 0, 0, 26))); - painter->drawRoundedRect(hoverRect, hoverRect.height() / 2, hoverRect.height() / 2); - } - - if (type() == KDecoration3::DecorationButtonType::Menu) { - if (auto *window = decoration->window()) { - window->icon().paint(painter, buttonRect.toRect()); - } - } else { - const QPixmap pixmap = decoration->buttonPixmap(type(), isChecked()); - QRectF centered = imageRect; - centered.moveCenter(buttonRect.center()); - if (!pixmap.isNull()) { - painter->drawPixmap(centered, pixmap, pixmap.rect()); - } - } - painter->restore(); -} diff --git a/plugins/decoration3/button.h b/plugins/decoration3/button.h deleted file mode 100644 index 8ff34b9..0000000 --- a/plugins/decoration3/button.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2020 PandaOS Team. - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -class Button : public KDecoration3::DecorationButton -{ -public: - explicit Button(KDecoration3::DecorationButtonType type, - KDecoration3::Decoration *decoration, - QObject *parent = nullptr); - - static KDecoration3::DecorationButton *create(KDecoration3::DecorationButtonType type, - KDecoration3::Decoration *decoration, - QObject *parent); - -protected: - void paint(QPainter *painter, const QRectF &repaintArea) override; -}; diff --git a/plugins/decoration3/cutefishos.json b/plugins/decoration3/cutefishos.json deleted file mode 100644 index a694784..0000000 --- a/plugins/decoration3/cutefishos.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "KPlugin": { - "Description": "CutefishOS window decoration for KDE Plasma 6", - "EnabledByDefault": true, - "Id": "org.cutefish.decoration", - "Name": "CutefishOS Window Frame", - "ServiceTypes": [ - "org.kde.kdecoration3" - ], - "Version": "1.0" - }, - "org.kde.kdecoration3": { - "blur": false, - "recommended": true, - "styles": [ - "titled" - ] - } -} diff --git a/plugins/decoration3/decoration.cpp b/plugins/decoration3/decoration.cpp deleted file mode 100644 index 3dc4c3c..0000000 --- a/plugins/decoration3/decoration.cpp +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Copyright (C) 2020 PandaOS Team. - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "decoration.h" -#include "button.h" - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -K_PLUGIN_FACTORY_WITH_JSON(CutefishDecorationFactory, "cutefishos.json", - registerPlugin();) - -namespace Cutefish -{ - -static int s_decorationCount = 0; -static int s_shadowRadius = -1; -static std::shared_ptr s_shadow; - -Decoration::Decoration(QObject *parent, const QVariantList &args) - : KDecoration3::Decoration(parent, args) - , m_themeSettings(QSettings::UserScope, QStringLiteral("cutefishos"), QStringLiteral("theme")) -{ - ++s_decorationCount; -} - -Decoration::~Decoration() -{ - if (--s_decorationCount == 0) { - s_shadow.reset(); - s_shadowRadius = -1; - } -} - -bool Decoration::init() -{ - if (!window()) { - return false; - } - - m_themeSettingsFile = m_themeSettings.fileName(); - reloadTheme(); - - m_leftButtons = new KDecoration3::DecorationButtonGroup( - KDecoration3::DecorationButtonGroup::Position::Left, this, &Button::create); - m_rightButtons = new KDecoration3::DecorationButtonGroup( - KDecoration3::DecorationButtonGroup::Position::Right, this, &Button::create); - - auto window = this->window(); - connect(window, &KDecoration3::DecoratedWindow::captionChanged, this, [this] { update(); }); - connect(window, &KDecoration3::DecoratedWindow::activeChanged, this, [this] { update(); }); - connect(window, &KDecoration3::DecoratedWindow::shadedChanged, this, [this] { - updateGeometry(); - updateButtonsGeometry(); - update(); - }); - connect(window, &KDecoration3::DecoratedWindow::maximizedChanged, this, [this] { - updateGeometry(); - updateButtonsGeometry(); - update(); - }); - connect(window, &KDecoration3::DecoratedWindow::widthChanged, this, [this] { - updateGeometry(); - updateButtonsGeometry(); - }); - connect(window, &KDecoration3::DecoratedWindow::heightChanged, this, [this] { updateGeometry(); }); - - if (settings()) { - connect(settings().get(), &KDecoration3::DecorationSettings::fontChanged, this, [this] { - updateGeometry(); - update(); - }); - connect(settings().get(), &KDecoration3::DecorationSettings::spacingChanged, this, [this] { - updateButtonsGeometry(); - update(); - }); - connect(settings().get(), &KDecoration3::DecorationSettings::decorationButtonsLeftChanged, this, - [this] { updateButtonsGeometry(); }); - connect(settings().get(), &KDecoration3::DecorationSettings::decorationButtonsRightChanged, this, - [this] { updateButtonsGeometry(); }); - connect(settings().get(), &KDecoration3::DecorationSettings::reconfigured, this, [this] { - reloadTheme(); - updateGeometry(); - updateButtonsGeometry(); - update(); - }); - } - - // Follow the CutefishOS theme settings (dark mode, scaling) while the window is open. - if (!m_themeSettingsFile.isEmpty()) { - m_themeWatcher.addPath(m_themeSettingsFile); - connect(&m_themeWatcher, &QFileSystemWatcher::fileChanged, this, [this] { - reloadTheme(); - updateGeometry(); - updateButtonsGeometry(); - update(); - - // Editors replace the file instead of writing in place, which drops the watch. - if (!m_themeWatcher.files().contains(m_themeSettingsFile)) { - m_themeWatcher.addPath(m_themeSettingsFile); - } - }); - } - - updateGeometry(); - updateButtonsGeometry(); - return true; -} - -void Decoration::paint(QPainter *painter, const QRectF &repaintArea) -{ - if (!window()) { - return; - } - - if (!window()->isShaded()) { - painter->save(); - painter->setRenderHint(QPainter::Antialiasing); - painter->setPen(Qt::NoPen); - painter->setBrush(titleBarBackgroundColor()); - - const bool rounded = !window()->isMaximized() - && (!settings() || settings()->isAlphaChannelSupported()); - if (rounded) { - painter->drawRoundedRect(rect(), m_frameRadius, m_frameRadius); - } else { - painter->drawRect(rect()); - } - painter->restore(); - - if (m_leftButtons) { - m_leftButtons->paint(painter, repaintArea); - } - if (m_rightButtons) { - m_rightButtons->paint(painter, repaintArea); - } - } - - paintCaption(painter); -} - -void Decoration::updateGeometry() -{ - if (!window()) { - return; - } - - const qreal height = titleBarHeight(); - setBorders(QMarginsF(0, height, 0, 0)); - setResizeOnlyBorders(QMarginsF(5, 5, 5, 5)); - setTitleBar(QRectF(0, 0, window()->width(), height)); -} - -void Decoration::updateButtonsGeometry() -{ - if (!window() || !m_leftButtons || !m_rightButtons) { - return; - } - - const QSizeF buttonSize(titleBarHeight(), titleBarHeight()); - const auto buttons = m_leftButtons->buttons() + m_rightButtons->buttons(); - for (auto *button : buttons) { - button->setGeometry(QRectF(QPointF(0, 0), buttonSize)); - } - - constexpr qreal spacing = 8.0; - m_leftButtons->setSpacing(spacing); - m_leftButtons->setPos(QPointF(0, 0)); - m_rightButtons->setSpacing(spacing); - m_rightButtons->setPos(QPointF(size().width() - m_rightButtons->geometry().width() - 2, 0)); -} - -void Decoration::updateShadow() -{ - if (s_shadow && s_shadowRadius == m_frameRadius) { - setShadow(s_shadow); - return; - } - - const int shadowSize = 90; - const int shadowStrength = 35; - const QColor shadowColor = Qt::black; - const int shadowOverlap = m_frameRadius; - const int shadowOffset = shadowOverlap / 2; - - QImage image(2 * shadowSize, 2 * shadowSize, QImage::Format_ARGB32_Premultiplied); - image.fill(Qt::transparent); - - // Gaussian delta function used to fade the shadow out. - auto alpha = [](qreal x) { return std::exp(-x * x / 0.15); }; - auto gradientStopColor = [](QColor color, int alpha) { - color.setAlpha(alpha); - return color; - }; - - QRadialGradient radialGradient(shadowSize, shadowSize, shadowSize); - for (int i = 0; i < 10; ++i) { - const qreal x(qreal(i) / 9); - radialGradient.setColorAt(x, gradientStopColor(shadowColor, alpha(x) * shadowStrength)); - } - radialGradient.setColorAt(1, gradientStopColor(shadowColor, 0)); - - QPainter painter(&image); - painter.setRenderHint(QPainter::Antialiasing, true); - painter.fillRect(image.rect(), radialGradient); - - const QRectF innerRect(shadowSize - shadowOverlap, - shadowSize - shadowOffset - shadowOverlap, - 2 * shadowOverlap, - shadowOffset + 2 * shadowOverlap); - - // Contrast pixel around the window. - painter.setPen(gradientStopColor(shadowColor, shadowStrength * 0.5)); - painter.setBrush(Qt::NoBrush); - painter.drawRoundedRect(innerRect, -0.5 + m_frameRadius, -0.5 + m_frameRadius); - - // Mask out the area covered by the window itself. - painter.setPen(Qt::NoPen); - painter.setBrush(Qt::black); - painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); - painter.drawRoundedRect(innerRect, 0.5 + m_frameRadius, 0.5 + m_frameRadius); - painter.end(); - - s_shadow = std::make_shared(); - s_shadow->setPadding(QMarginsF(shadowSize - shadowOverlap, - shadowSize - shadowOffset - shadowOverlap, - shadowSize - shadowOverlap, - shadowSize - shadowOverlap)); - s_shadow->setInnerShadowRect(QRectF(shadowSize, shadowSize, 1, 1)); - s_shadow->setShadow(image); - s_shadowRadius = m_frameRadius; - - setShadow(s_shadow); -} - -void Decoration::reloadTheme() -{ - m_themeSettings.sync(); - - m_devicePixelRatio = m_themeSettings.value(QStringLiteral("PixelRatio"), 1.0).toReal(); - if (m_devicePixelRatio <= 0) { - m_devicePixelRatio = 1.0; - } - m_darkMode = m_themeSettings.value(QStringLiteral("DarkMode"), false).toBool(); - m_frameRadius = qRound(11 * m_devicePixelRatio); - - updateButtonPixmaps(); - updateShadow(); -} - -void Decoration::updateButtonPixmaps() -{ - m_closeBtnPixmap = loadPixmap(pixmapPath(KDecoration3::DecorationButtonType::Close, false)); - m_maximizeBtnPixmap = loadPixmap(pixmapPath(KDecoration3::DecorationButtonType::Maximize, false)); - m_minimizeBtnPixmap = loadPixmap(pixmapPath(KDecoration3::DecorationButtonType::Minimize, false)); - m_restoreBtnPixmap = loadPixmap(pixmapPath(KDecoration3::DecorationButtonType::Maximize, true)); -} - -QPixmap Decoration::loadPixmap(const QString &path) const -{ - QImageReader reader(path); - if (!reader.canRead()) { - return {}; - } - - // The icons are SVGs, render them at the size the buttons are painted with. - reader.setScaledSize(QSize(24, 24) * m_devicePixelRatio); - return QPixmap::fromImage(reader.read()); -} - -QPixmap Decoration::buttonPixmap(KDecoration3::DecorationButtonType type, bool checked) const -{ - switch (type) { - case KDecoration3::DecorationButtonType::Close: - return m_closeBtnPixmap; - case KDecoration3::DecorationButtonType::Maximize: - return checked ? m_restoreBtnPixmap : m_maximizeBtnPixmap; - case KDecoration3::DecorationButtonType::Minimize: - return m_minimizeBtnPixmap; - default: - return {}; - } -} - -QString Decoration::pixmapPath(KDecoration3::DecorationButtonType type, bool checked) const -{ - const QString mode = darkMode() ? QStringLiteral("dark") : QStringLiteral("light"); - QString name; - switch (type) { - case KDecoration3::DecorationButtonType::Close: - name = QStringLiteral("close_normal.svg"); - break; - case KDecoration3::DecorationButtonType::Maximize: - name = checked ? QStringLiteral("restore_normal.svg") : QStringLiteral("maximize_normal.svg"); - break; - case KDecoration3::DecorationButtonType::Minimize: - name = QStringLiteral("minimize_normal.svg"); - break; - default: - return {}; - } - return QStringLiteral(":/images/%1/%2").arg(mode, name); -} - -int Decoration::titleBarHeight() const -{ - return qMax(1, qRound(m_titleBarHeight * m_devicePixelRatio)); -} - -QColor Decoration::titleBarBackgroundColor() const -{ - return darkMode() ? m_titleBarBgDarkColor : m_titleBarBgColor; -} - -QColor Decoration::titleBarForegroundColor() const -{ - if (window() && window()->isActive()) { - return darkMode() ? m_titleBarFgDarkColor : m_titleBarFgColor; - } - return darkMode() ? m_unfocusedFgDarkColor : m_unfocusedFgColor; -} - -void Decoration::paintCaption(QPainter *painter) const -{ - if (!window()) { - return; - } - - QFont font = settings() ? settings()->font() : QFont(); - QFontMetricsF metrics(font); - const QRectF titleRect(0, 0, size().width(), titleBarHeight()); - const qreal left = m_leftButtons ? m_leftButtons->geometry().width() + 20 : 20; - const qreal right = m_rightButtons ? m_rightButtons->geometry().width() + 20 : 20; - const QRectF available = titleRect.adjusted(left, 0, -right, 0); - const qreal textWidth = metrics.horizontalAdvance(window()->caption()); - const QRectF natural((size().width() - textWidth) / 2, 0, textWidth, titleBarHeight()); - - Qt::Alignment alignment = Qt::AlignCenter; - QRectF captionRect = titleRect; - if (natural.left() < available.left()) { - captionRect = available; - alignment = Qt::AlignLeft | Qt::AlignVCenter; - } else if (natural.right() > available.right()) { - captionRect = available; - alignment = Qt::AlignRight | Qt::AlignVCenter; - } - - painter->save(); - painter->setFont(font); - painter->setPen(titleBarForegroundColor()); - painter->drawText(captionRect, alignment, metrics.elidedText(window()->caption(), Qt::ElideMiddle, qRound(captionRect.width()))); - painter->restore(); -} - -} - -#include "decoration.moc" diff --git a/plugins/decoration3/decoration.h b/plugins/decoration3/decoration.h deleted file mode 100644 index 3733f30..0000000 --- a/plugins/decoration3/decoration.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2020 PandaOS Team. - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include -#include - -#include -#include -#include - -namespace Cutefish -{ - -class Decoration : public KDecoration3::Decoration -{ - Q_OBJECT - -public: - explicit Decoration(QObject *parent = nullptr, const QVariantList &args = {}); - ~Decoration() override; - - bool init() override; - void paint(QPainter *painter, const QRectF &repaintArea) override; - - bool darkMode() const { return m_darkMode; } - qreal devicePixelRatio() const { return m_devicePixelRatio; } - QPixmap buttonPixmap(KDecoration3::DecorationButtonType type, bool checked = false) const; - -private: - void updateGeometry(); - void updateButtonsGeometry(); - void updateShadow(); - void reloadTheme(); - void updateButtonPixmaps(); - void paintCaption(QPainter *painter) const; - - int titleBarHeight() const; - QColor titleBarBackgroundColor() const; - QColor titleBarForegroundColor() const; - QString pixmapPath(KDecoration3::DecorationButtonType type, bool checked) const; - QPixmap loadPixmap(const QString &path) const; - - KDecoration3::DecorationButtonGroup *m_leftButtons = nullptr; - KDecoration3::DecorationButtonGroup *m_rightButtons = nullptr; - - qreal m_devicePixelRatio = 1.0; - bool m_darkMode = false; - int m_titleBarHeight = 30; - int m_frameRadius = 11; - QColor m_titleBarBgColor = QColor(255, 255, 255); - QColor m_titleBarFgColor = QColor(56, 56, 56); - QColor m_unfocusedFgColor = QColor(127, 127, 127); - QColor m_titleBarBgDarkColor = QColor(44, 44, 45); - QColor m_titleBarFgDarkColor = QColor(202, 203, 206); - QColor m_unfocusedFgDarkColor = QColor(112, 112, 112); - - QSettings m_themeSettings; - QString m_themeSettingsFile; - QFileSystemWatcher m_themeWatcher; - QPixmap m_closeBtnPixmap; - QPixmap m_maximizeBtnPixmap; - QPixmap m_minimizeBtnPixmap; - QPixmap m_restoreBtnPixmap; -}; - -} diff --git a/plugins/roundedwindow/CMakeLists.txt b/plugins/roundedwindow/CMakeLists.txt index 8472330..8cb2bfd 100644 --- a/plugins/roundedwindow/CMakeLists.txt +++ b/plugins/roundedwindow/CMakeLists.txt @@ -1,49 +1,45 @@ -find_package(KF5CoreAddons) -find_package(KF5Config) -find_package(KF5WindowSystem) - -find_path(EFFECTS_H kwineffects.h PATH_SUFFIXES kf5) - -if (EFFECTS_H) - include_directories(${EFFECTS_H}) -else (EFFECTS_H) - message(STATUS "didnt find kwineffects.h, not building effects") -endif (EFFECTS_H) - -find_library(KWIN_EFFECTS NAMES kwineffects PATH_SUFFIXES kf5) -find_library(KWIN_GLUTILS NAMES kwinglutils PATH_SUFFIXES kf5) -find_library(OPENGL NAMES GL) - -if (NOT KWIN_EFFECTS) - message(STATUS "didnt find kwineffects lib, not building effects") -endif (NOT KWIN_EFFECTS) - -if (NOT KWIN_GLUTILS) - message(STATUS "didnt find kwin glutils lib, not building effects") -endif (NOT KWIN_GLUTILS) - -if (NOT OPENGL) - message(STATUS "didnt find opengl, not building effects") -endif (NOT OPENGL) - -if (NOT EFFECTS_H OR NOT KWIN_GLUTILS OR NOT KWIN_EFFECTS OR NOT OPENGL) - message(FATAL_ERROR "cant continue") -endif (NOT EFFECTS_H OR NOT KWIN_GLUTILS OR NOT KWIN_EFFECTS OR NOT OPENGL) - -add_library(roundedwindow MODULE - main.cpp - roundedwindow.cpp - resources.qrc -) +cmake_minimum_required(VERSION 3.16) +project(cutefish_roundedwindow LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) + +# The plugin factory class is created by a macro, so automoc has to be taught +# about it. +list(APPEND CMAKE_AUTOMOC_MACRO_NAMES + KWIN_EFFECT_FACTORY + KWIN_EFFECT_FACTORY_ENABLED + KWIN_EFFECT_FACTORY_SUPPORTED + KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED) + +include(GNUInstallDirs) + +find_package(Qt6 CONFIG REQUIRED COMPONENTS Core Gui) +find_package(KWin REQUIRED) -target_link_libraries(roundedwindow - PUBLIC - Qt5::Core - Qt5::Gui +add_library(cutefish_roundedwindow MODULE roundedwindow.cpp) + +set_target_properties(cutefish_roundedwindow PROPERTIES PREFIX "") + +target_link_libraries(cutefish_roundedwindow PRIVATE - KF5::CoreAddons - KF5::ConfigCore - KF5::WindowSystem + Qt6::Core + Qt6::Gui + KWin::kwin ) -install (TARGETS roundedwindow DESTINATION ${QT_PLUGINS_DIR}/kwin/effects/plugins) +if(NOT KDE_INSTALL_PLUGINDIR) + find_program(QT6_QTPATHS_EXECUTABLE qtpaths6) + if(QT6_QTPATHS_EXECUTABLE) + execute_process(COMMAND "${QT6_QTPATHS_EXECUTABLE}" --query QT_INSTALL_PLUGINS + OUTPUT_VARIABLE KDE_INSTALL_PLUGINDIR OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() +endif() + +if(NOT KDE_INSTALL_PLUGINDIR) + set(KDE_INSTALL_PLUGINDIR "${CMAKE_INSTALL_LIBDIR}/qt6/plugins") +endif() + +install(TARGETS cutefish_roundedwindow + DESTINATION "${KDE_INSTALL_PLUGINDIR}/kwin/effects/plugins") diff --git a/plugins/roundedwindow/main.cpp b/plugins/roundedwindow/main.cpp deleted file mode 100644 index 1929bd0..0000000 --- a/plugins/roundedwindow/main.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright © 2021 Reion Wong - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. if not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include "roundedwindow.h" -#include - -class RoundedWindowPluginFactory : public KWin::EffectPluginFactory -{ - Q_OBJECT - Q_INTERFACES(KPluginFactory) - Q_PLUGIN_METADATA(IID KPluginFactory_iid FILE "roundedwindow.json") - -public: - explicit RoundedWindowPluginFactory(); - ~RoundedWindowPluginFactory(); - - KWin::Effect * createEffect() const override - { - return new RoundedWindow; - } -}; - -K_PLUGIN_FACTORY_DEFINITION(RoundedWindowPluginFactory, registerPlugin();) -K_EXPORT_PLUGIN_VERSION(KWIN_EFFECT_API_VERSION) - -#include "main.moc" diff --git a/plugins/roundedwindow/metadata.json b/plugins/roundedwindow/metadata.json new file mode 100644 index 0000000..3691560 --- /dev/null +++ b/plugins/roundedwindow/metadata.json @@ -0,0 +1,19 @@ +{ + "KPlugin": { + "Authors": [ + { + "Email": "reionwong@gmail.com", + "Name": "Reion Wong" + } + ], + "Category": "Appearance", + "Description": "Round the corners of windows", + "EnabledByDefault": true, + "Icon": "preferences-system-windows-effect-squash", + "Id": "cutefish_roundedwindow", + "License": "GPL", + "Name": "CutefishOS Rounded Corners", + "Version": "1" + }, + "X-KDE-Ordering": 5 +} diff --git a/plugins/roundedwindow/resources.qrc b/plugins/roundedwindow/resources.qrc deleted file mode 100644 index 15cc7cc..0000000 --- a/plugins/roundedwindow/resources.qrc +++ /dev/null @@ -1,6 +0,0 @@ - - - shaders.frag.110 - shaders.frag.140 - - \ No newline at end of file diff --git a/plugins/roundedwindow/roundedwindow.cpp b/plugins/roundedwindow/roundedwindow.cpp index 4523fda..a6ff81f 100644 --- a/plugins/roundedwindow/roundedwindow.cpp +++ b/plugins/roundedwindow/roundedwindow.cpp @@ -1,376 +1,303 @@ -/* - * Copyright © 2021 Reion Wong - * Copyright © 2021 Reven Martin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. if not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - #include "roundedwindow.h" -// Qt -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include - -Q_DECLARE_METATYPE(QPainterPath) - -typedef void (* SetDepth)(void *, int); -static SetDepth setDepthfunc = nullptr; - -static QStringList allowList = { "netease-cloud-music netease-cloud-music", - "com.alibabainc.dingtalk com.alibabainc.dingtalk", - "tenvideo_universal tenvideo_universal", - "com.eusoft.ting.en com.eusoft.ting.en", - "i4toolslinux i4tools", - "youku-app youku-app", - "qqmusic qqmusic", - "mytime mytime", - "feishu feishu", - "bytedance-feishu bytedance-feishu", - "xmind xmind", - "mtxx mtxx", - "ynote-desktop ynote-desktop", - - // Open source software - "code code", - "motrix motrix" - }; - -// From ubreffect -static KWin::GLShader *getShader() +#include +#include + +// Applications that draw their own window frame but still want rounded corners. +static const QStringList s_allowList = { + "netease-cloud-music netease-cloud-music", + "com.alibabainc.dingtalk com.alibabainc.dingtalk", + "tenvideo_universal tenvideo_universal", + "com.eusoft.ting.en com.eusoft.ting.en", + "i4toolslinux i4tools", + "youku-app youku-app", + "qqmusic qqmusic", + "mytime mytime", + "feishu feishu", + "bytedance-feishu bytedance-feishu", + "xmind xmind", + "mtxx mtxx", + "ynote-desktop ynote-desktop", + + // Open source software + "code code", + "motrix motrix" +}; + +static QByteArray fragmentShaderSource() { - // copy from kwinglutils.cpp - QByteArray source; - QTextStream stream(&source); - - KWin::GLPlatform * const gl = KWin::GLPlatform::instance(); - QByteArray varying, output, textureLookup; - - if (!gl->isGLES()) { - const bool glsl_140 = gl->glslVersion() >= KWin::kVersionNumber(1, 40); + const KWin::OpenGlContext *context = KWin::effects->openglContext(); + const bool gles = context && context->isOpenGLES(); + const KWin::Version glslVersion = context ? context->glslVersion() : KWin::Version(1, 40); - if (glsl_140) - stream << "#version 140\n\n"; - - varying = glsl_140 ? QByteArrayLiteral("in") : QByteArrayLiteral("varying"); - textureLookup = glsl_140 ? QByteArrayLiteral("texture") : QByteArrayLiteral("texture2D"); - output = glsl_140 ? QByteArrayLiteral("fragColor") : QByteArrayLiteral("gl_FragColor"); + QByteArray source; + QByteArray varying = QByteArrayLiteral("varying"); + QByteArray textureLookup = QByteArrayLiteral("texture2D"); + QByteArray output = QByteArrayLiteral("gl_FragColor"); + + if (!gles) { + if (glslVersion >= KWin::Version(1, 40)) { + source += "#version 140\n\n"; + varying = QByteArrayLiteral("in"); + textureLookup = QByteArrayLiteral("texture"); + output = QByteArrayLiteral("fragColor"); + } } else { - const bool glsl_es_300 = KWin::GLPlatform::instance()->glslVersion() >= KWin::kVersionNumber(3, 0); - - if (glsl_es_300) - stream << "#version 300 es\n\n"; - - // From the GLSL ES specification: - // - // "The fragment language has no default precision qualifier for floating point types." - stream << "precision highp float;\n\n"; - - varying = glsl_es_300 ? QByteArrayLiteral("in") : QByteArrayLiteral("varying"); - textureLookup = glsl_es_300 ? QByteArrayLiteral("texture") : QByteArrayLiteral("texture2D"); - output = glsl_es_300 ? QByteArrayLiteral("fragColor") : QByteArrayLiteral("gl_FragColor"); + if (glslVersion >= KWin::Version(3, 0)) { + source += "#version 300 es\n\n"; + varying = QByteArrayLiteral("in"); + textureLookup = QByteArrayLiteral("texture"); + output = QByteArrayLiteral("fragColor"); + } + // The fragment language has no default precision qualifier for floats. + source += "precision highp float;\n\n"; } - KWin::ShaderTraits traits; - - traits |= KWin::ShaderTrait::MapTexture; - traits |= KWin::ShaderTrait::Modulate; - traits |= KWin::ShaderTrait::AdjustSaturation; - - if (traits & KWin::ShaderTrait::MapTexture) { - stream << "uniform sampler2D sampler;\n"; - - // custom texture - stream << "uniform sampler2D topleft;\n"; - stream << "uniform sampler2D topright;\n"; - stream << "uniform sampler2D bottomleft;\n"; - stream << "uniform sampler2D bottomright;\n"; - - // scale - stream << "uniform vec2 scale;\n"; - stream << "uniform vec2 scale1;\n"; - stream << "uniform vec2 scale2;\n"; - stream << "uniform vec2 scale3;\n"; - - if (traits & KWin::ShaderTrait::Modulate) - stream << "uniform vec4 modulation;\n"; - if (traits & KWin::ShaderTrait::AdjustSaturation) - stream << "uniform float saturation;\n"; - - stream << "\n" << varying << " vec2 texcoord0;\n"; - - } else if (traits & KWin::ShaderTrait::UniformColor) - stream << "uniform vec4 geometryColor;\n"; - - #if KWIN_EFFECT_API_VERSION < 233 - if (traits & KWin::ShaderTrait::ClampTexture) { - stream << "uniform vec4 textureClamp;\n"; + source += "uniform sampler2D sampler;\n" + "uniform vec4 modulation;\n" + "uniform float saturation;\n" + "\n" + // Size of the offscreen texture, in logical pixels. + "uniform vec2 textureSize;\n" + // Frame of the window inside the texture (x, y, width, height). + "uniform vec4 frameRect;\n" + "uniform float radius;\n" + "\n"; + + source += varying + " vec2 texcoord0;\n"; + if (output != QByteArrayLiteral("gl_FragColor")) { + source += "out vec4 " + output + ";\n"; } - #endif - - if (output != QByteArrayLiteral("gl_FragColor")) - stream << "\nout vec4 " << output << ";\n"; - - stream << "\nvoid main(void)\n{\n"; - if (traits & KWin::ShaderTrait::MapTexture) { - stream << "vec2 texcoordC = texcoord0;\n"; - - - stream << " " << "vec4 var;\n"; - stream << "if (texcoordC.x < 0.5) {\n" - " if (texcoordC.y < 0.5) {\n" - " vec2 cornerCoord = vec2(texcoordC.x * scale.x, texcoordC.y * scale.y);\n" - " var = " << textureLookup << "(topleft, cornerCoord);\n" - " } else {\n" - " vec2 cornerCoordBL = vec2(texcoordC.x * scale2.x, (1.0 - texcoordC.y) * scale2.y);\n" - " var = " << textureLookup << "(bottomleft, cornerCoordBL);\n" - " }\n" - "} else {\n" - " if (texcoordC.y < 0.5) {\n" - " vec2 cornerCoordTR = vec2((1.0 - texcoordC.x) * scale1.x, texcoordC.y * scale1.y);\n" - " var = " << textureLookup << "(topright, cornerCoordTR);\n" - " } else {\n" - " vec2 cornerCoordBR = vec2((1.0 - texcoordC.x) * scale3.x, (1.0 - texcoordC.y) * scale3.y);\n" - " var = " << textureLookup << "(bottomright, cornerCoordBR);\n" - " }\n" - "}\n"; - - stream << " vec4 texel = " << textureLookup << "(sampler, texcoordC);\n"; - if (traits & KWin::ShaderTrait::Modulate) - stream << " texel *= modulation;\n"; - if (traits & KWin::ShaderTrait::AdjustSaturation) - stream << " texel.rgb = mix(vec3(dot(texel.rgb, vec3(0.2126, 0.7152, 0.0722))), texel.rgb, saturation);\n"; - - stream << " " << output << " = texel * var;\n"; - } else if (traits & KWin::ShaderTrait::UniformColor) - stream << " " << output << " = geometryColor;\n"; - - stream << "}"; - stream.flush(); - - auto shader = KWin::ShaderManager::instance()->generateCustomShader(traits, QByteArray(), source); - //shaders.insert(direction, shader); - return shader; -} -static KWin::GLTexture *getTexture(int borderRadius) -{ - QPixmap pix(QSize(borderRadius, borderRadius)); - pix.fill(Qt::transparent); - QPainter painter(&pix); - painter.setRenderHint(QPainter::Antialiasing); - QPainterPath path; - path.moveTo(borderRadius, 0); - path.arcTo(0, 0, 2 * borderRadius, 2 * borderRadius, 90, 90); - path.lineTo(borderRadius, borderRadius); - path.lineTo(borderRadius, 0); - painter.fillPath(path, Qt::white); - - auto texture = new KWin::GLTexture(pix); - texture->setFilter(GL_LINEAR); - texture->setWrapMode(GL_CLAMP_TO_BORDER); - - return texture; + source += "\n" + // Signed distance to a rounded rectangle centred on the origin. + "float roundedBoxDistance(vec2 point, vec2 halfSize, float r)\n" + "{\n" + " vec2 q = abs(point) - halfSize + vec2(r);\n" + " return length(max(q, vec2(0.0))) + min(max(q.x, q.y), 0.0) - r;\n" + "}\n" + "\n" + "void main(void)\n" + "{\n" + " vec4 texel = " + textureLookup + "(sampler, texcoord0);\n" + " texel *= modulation;\n" + " texel.rgb = mix(vec3(dot(texel.rgb, vec3(0.2126, 0.7152, 0.0722))), texel.rgb, saturation);\n" + "\n" + " vec2 point = texcoord0 * textureSize;\n" + " vec2 halfSize = frameRect.zw * 0.5;\n" + " vec2 centered = point - (frameRect.xy + halfSize);\n" + "\n" + // Everything outside of the window frame - the decoration shadow - + // has to be left alone. + " vec2 outside = abs(centered) - halfSize;\n" + " if (max(outside.x, outside.y) > 0.0) {\n" + " " + output + " = texel;\n" + " return;\n" + " }\n" + "\n" + " float dist = roundedBoxDistance(centered, halfSize, radius);\n" + " float aa = max(length(fwidth(point)) * 0.5, 0.5);\n" + " " + output + " = texel * (1.0 - smoothstep(-aa, aa, dist));\n" + "}\n"; + + return source; } -RoundedWindow::RoundedWindow(QObject *, const QVariantList &) - : KWin::Effect() +RoundedWindow::RoundedWindow() + : KWin::OffscreenEffect() { - QSettings settings(QSettings::UserScope, "cutefishos", "theme"); - qreal devicePixelRatio = settings.value("PixelRatio", 1.0).toReal(); - m_frameRadius = 11 * devicePixelRatio; - - setDepthfunc = (SetDepth) QLibrary::resolve("kwin.so." + qApp->applicationVersion(), "_ZN4KWin8Toplevel8setDepthEi"); + reconfigure(ReconfigureAll); - QString name = "_NET_WM_STATE"; - xcb_intern_atom_cookie_t cookie = xcb_intern_atom_unchecked(KWin::connection(), false, name.length(), name.toUtf8()); - xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(KWin::connection(), cookie, nullptr); - m_netWMStateAtom = reply->atom; - free(reply); + connect(KWin::effects, &KWin::EffectsHandler::windowAdded, this, &RoundedWindow::handleWindowAdded); + connect(KWin::effects, &KWin::EffectsHandler::windowDeleted, this, &RoundedWindow::handleWindowDeleted); - name = "_NET_WM_STATE_MAXIMIZED_HORZ"; - cookie = xcb_intern_atom_unchecked(KWin::connection(), false, name.length(), name.toUtf8()); - reply = xcb_intern_atom_reply(KWin::connection(), cookie, nullptr); - m_netWMStateMaxHorzAtom = reply->atom; - free(reply); + const auto windows = KWin::effects->stackingOrder(); + for (KWin::EffectWindow *window : windows) { + handleWindowAdded(window); + } +} - name = "_NET_WM_STATE_MAXIMIZED_VERT"; - cookie = xcb_intern_atom_unchecked(KWin::connection(), false, name.length(), name.toUtf8()); - reply = xcb_intern_atom_reply(KWin::connection(), cookie, nullptr); - m_netWMStateMaxVertAtom = reply->atom; - free(reply); +RoundedWindow::~RoundedWindow() = default; - m_shader = getShader(); - m_texure = getTexture(m_frameRadius); +bool RoundedWindow::supported() +{ + return KWin::effects->isOpenGLCompositing() && KWin::OffscreenEffect::supported(); } -RoundedWindow::~RoundedWindow() +bool RoundedWindow::enabledByDefault() { + return supported(); } -bool RoundedWindow::supported() +void RoundedWindow::reconfigure(ReconfigureFlags flags) { - const QByteArray desktop = qgetenv("XDG_CURRENT_DESKTOP"); + Q_UNUSED(flags) - if (desktop.isEmpty()) - return false; + QSettings settings(QSettings::UserScope, QStringLiteral("cutefishos"), QStringLiteral("theme")); + qreal devicePixelRatio = settings.value(QStringLiteral("PixelRatio"), 1.0).toReal(); + if (devicePixelRatio <= 0) { + devicePixelRatio = 1.0; + } + m_frameRadius = 11 * devicePixelRatio; - return desktop == "Cutefish" && KWin::effects->isOpenGLCompositing() && KWin::GLRenderTarget::supported(); + const auto windows = m_redirected; + for (KWin::EffectWindow *window : windows) { + window->addRepaintFull(); + } } -bool RoundedWindow::enabledByDefault() +void RoundedWindow::handleWindowAdded(KWin::EffectWindow *window) { - return supported(); + if (!window) { + return; + } + + connect(window, &KWin::EffectWindow::windowMaximizedStateChanged, this, + [this, window] { updateWindow(window); }); + connect(window, &KWin::EffectWindow::windowFullScreenChanged, this, + [this, window] { updateWindow(window); }); + connect(window, &KWin::EffectWindow::windowFrameGeometryChanged, this, + [this, window] { updateWindow(window); }); + connect(window, &KWin::EffectWindow::windowDecorationChanged, this, + [this, window] { updateWindow(window); }); + + updateWindow(window); } -#if KWIN_EFFECT_API_VERSION < 233 -bool RoundedWindow::hasShadow(KWin::WindowQuadList &qds) +void RoundedWindow::handleWindowDeleted(KWin::EffectWindow *window) { - for (int i = 0; i < qds.count(); ++i) - if (qds.at(i).type() == KWin::WindowQuadShadow) - return true; - - return false; + m_redirected.remove(window); } -#endif -bool RoundedWindow::isMaximized(KWin::EffectWindow *w) +void RoundedWindow::updateWindow(KWin::EffectWindow *window) { - if (m_netWMStateAtom == 0) - return false; + const bool wanted = shouldRound(window); + const bool redirected = m_redirected.contains(window); - QByteArray rawAtomData = w->readProperty(m_netWMStateAtom, XCB_ATOM_ATOM, 32); - if (!rawAtomData.isEmpty()) { - for (int i = 0; i < rawAtomData.length(); i += sizeof(xcb_atom_t)) { - xcb_atom_t atom = static_cast(rawAtomData.data()[i]) + 512; - if (atom == m_netWMStateMaxHorzAtom || atom == m_netWMStateMaxVertAtom) - return true; + if (wanted == redirected) { + return; + } + + if (wanted) { + if (KWin::GLShader *s = shader()) { + redirect(window); + setShader(window, s); + m_redirected.insert(window); } + } else { + unredirect(window); + m_redirected.remove(window); } - return false; + window->addRepaintFull(); } -void RoundedWindow::drawWindow(KWin::EffectWindow *w, int mask, const QRegion ®ion, KWin::WindowPaintData &data) +bool RoundedWindow::shouldRound(KWin::EffectWindow *window) const { - if (!w->isPaintingEnabled() || ((mask & PAINT_WINDOW_LANCZOS))) { - return KWin::Effect::drawWindow(w, mask, region, data); + if (!window || window->isDeleted() || !window->isOnCurrentDesktop()) { + return false; } - if (isMaximized(w)) { - return KWin::Effect::drawWindow(w, mask, region, data); + if (s_allowList.contains(window->windowClass())) { + return true; } - if (KWin::effects->hasActiveFullScreenEffect() || w->isFullScreen()) { - return KWin::Effect::drawWindow(w, mask, region, data); + if (!window->isManaged() || window->isFullScreen() || isMaximized(window)) { + return false; } - if (w->isDesktop() - || w->isMenu() - || w->isDock() - || w->isPopupWindow() - || w->isPopupMenu() - #if KWIN_EFFECT_API_VERSION < 233 - || !hasShadow(data.quads) - #endif - ) { - if (!allowList.contains(w->windowClass())) - return KWin::Effect::drawWindow(w, mask, region, data); + // Client side decorated windows - the CutefishOS applications themselves - + // paint their own rounded corners and shadow. Cutting into them would only + // produce artefacts, so this effect is for windows KWin decorates. + if (!window->hasDecoration()) { + return false; } - // 设置 alpha 通道混合 - if (!w->hasAlpha()) { - if (setDepthfunc) { - setDepthfunc(w->parent(), 32); - } + if (window->isDesktop() || window->isDock() || window->isMenu() || window->isDropdownMenu() + || window->isPopupMenu() || window->isPopupWindow() || window->isTooltip() + || window->isNotification() || window->isCriticalNotification() + || window->isOnScreenDisplay() || window->isDNDIcon() || window->isSplash()) { + return false; } - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - float borderColor[] = { 1.0f, 1.0f, 1.0f, 1.0f }; - - auto textureTopLeft = m_texure; - glActiveTexture(GL_TEXTURE10); - textureTopLeft->bind(); - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); - glActiveTexture(GL_TEXTURE0); - - auto textureTopRight = m_texure; - glActiveTexture(GL_TEXTURE11); - textureTopRight->bind(); - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); - glActiveTexture(GL_TEXTURE0); - - auto textureBottomLeft = m_texure; - glActiveTexture(GL_TEXTURE12); - textureBottomLeft->bind(); - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); - glActiveTexture(GL_TEXTURE0); - - auto textureBottomRight = m_texure; - glActiveTexture(GL_TEXTURE13); - textureBottomRight->bind(); - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); - glActiveTexture(GL_TEXTURE0); - - KWin::GLShader *oldShader = data.shader; - data.shader = m_shader; - KWin::ShaderManager::instance()->pushShader(m_shader); - - m_shader->setUniform("topleft", 10); - m_shader->setUniform("scale", QVector2D(w->width() * 1.0 / textureTopLeft->width(), - w->height() * 1.0 / textureTopLeft->height())); - - m_shader->setUniform("topright", 11); - m_shader->setUniform("scale1", QVector2D(w->width() * 1.0 / textureTopRight->width(), - w->height() * 1.0 / textureTopRight->height())); + return window->isNormalWindow() || window->isDialog() || window->isUtility(); +} - m_shader->setUniform("bottomleft", 12); - m_shader->setUniform("scale2", QVector2D(w->width() * 1.0 / textureBottomLeft->width(), - w->height() * 1.0 / textureBottomLeft->height())); +bool RoundedWindow::isMaximized(KWin::EffectWindow *window) const +{ + const QRectF maximizedArea = KWin::effects->clientArea(KWin::MaximizeArea, window); + return window->frameGeometry() == maximizedArea; +} - m_shader->setUniform("bottomright", 13); - m_shader->setUniform("scale3", QVector2D(w->width() * 1.0 / textureBottomRight->width(), - w->height() * 1.0 / textureBottomRight->height())); +KWin::GLShader *RoundedWindow::shader() +{ + if (!m_shader) { + m_shader = KWin::ShaderManager::instance()->generateCustomShader( + KWin::ShaderTrait::MapTexture | KWin::ShaderTrait::Modulate | KWin::ShaderTrait::AdjustSaturation, + QByteArray(), fragmentShaderSource()); - KWin::Effect::drawWindow(w, mask, region, data); - KWin::ShaderManager::instance()->popShader(); + if (m_shader && !m_shader->isValid()) { + m_shader.reset(); + } + } - data.shader = oldShader; + return m_shader.get(); +} - glActiveTexture(GL_TEXTURE10); - textureTopLeft->unbind(); - glActiveTexture(GL_TEXTURE0); +void RoundedWindow::prePaintWindow(KWin::EffectWindow *window, + KWin::WindowPrePaintData &data, + std::chrono::milliseconds presentTime) +{ + if (m_redirected.contains(window)) { + // The corners are cut out of the window, so it can no longer be treated + // as an opaque window - otherwise nothing would be blended with what is + // behind it and the cut out corners would stay black. + data.setTranslucent(); + } - glActiveTexture(GL_TEXTURE11); - textureTopRight->unbind(); - glActiveTexture(GL_TEXTURE0); + KWin::OffscreenEffect::prePaintWindow(window, data, presentTime); +} - glActiveTexture(GL_TEXTURE12); - textureBottomLeft->unbind(); - glActiveTexture(GL_TEXTURE0); +void RoundedWindow::drawWindow(const KWin::RenderTarget &renderTarget, + const KWin::RenderViewport &viewport, + KWin::EffectWindow *window, + int mask, + const QRegion ®ion, + KWin::WindowPaintData &data) +{ + if (m_redirected.contains(window)) { + if (KWin::GLShader *s = shader()) { + const QRectF expanded = window->expandedGeometry(); + const QRectF frame = window->frameGeometry(); + + // The offscreen texture covers the expanded geometry of the window, + // texcoord0 runs from (0, 0) to (1, 1) over it. + KWin::ShaderBinder binder(s); + s->setUniform("textureSize", QVector2D(expanded.width(), expanded.height())); + s->setUniform("frameRect", QVector4D(frame.x() - expanded.x(), + frame.y() - expanded.y(), + frame.width(), + frame.height())); + s->setUniform("radius", float(m_frameRadius)); + } + } - glActiveTexture(GL_TEXTURE13); - textureBottomRight->unbind(); - glActiveTexture(GL_TEXTURE0); + KWin::OffscreenEffect::drawWindow(renderTarget, viewport, window, mask, region, data); +} - glDisable(GL_BLEND); +namespace KWin +{ +KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED(RoundedWindow, + "metadata.json", + return RoundedWindow::supported();, + return RoundedWindow::enabledByDefault();) } + +#include "roundedwindow.moc" diff --git a/plugins/roundedwindow/roundedwindow.h b/plugins/roundedwindow/roundedwindow.h index c15ac01..d64c0f6 100644 --- a/plugins/roundedwindow/roundedwindow.h +++ b/plugins/roundedwindow/roundedwindow.h @@ -1,65 +1,57 @@ -/* - * Copyright © 2021 Reion Wong - * Copyright © 2021 Reven Martin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. if not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ +#pragma once -#ifndef ROUNDEDWINDOW_H -#define ROUNDEDWINDOW_H +#include -#include -#include -#include +#include -#include +#include -class RoundedWindow : public KWin::Effect +namespace KWin +{ +class GLShader; +} + +/** + * Rounds the corners of ordinary windows. + * + * The window is redirected into an offscreen texture, which is then painted with + * a shader that masks out the four corners with a rounded rectangle. The offscreen + * texture always has an alpha channel, so windows that do not have one themselves + * can be given a rounded shape as well. + */ +class RoundedWindow : public KWin::OffscreenEffect { Q_OBJECT public: - enum DataRole { - BaseRole = KWin::DataRole::LanczosCacheRole + 100, - WindowRadiusRole = BaseRole + 1, - WindowClipPathRole = BaseRole + 2, - WindowMaskTextureRole = BaseRole + 3, - WindowDepthRole = BaseRole + 4 - }; - - RoundedWindow(QObject *parent = nullptr, const QVariantList &args = QVariantList()); - ~RoundedWindow(); + RoundedWindow(); + ~RoundedWindow() override; static bool supported(); static bool enabledByDefault(); - bool hasShadow(KWin::WindowQuadList &qds); - bool isMaximized(KWin::EffectWindow *w); - - void drawWindow(KWin::EffectWindow* w, int mask, const QRegion ®ion, KWin::WindowPaintData& data) override; + void reconfigure(ReconfigureFlags flags) override; -private: - KWin::GLShader *m_shader; - KWin::GLTexture *m_texure; + void prePaintWindow(KWin::EffectWindow *window, + KWin::WindowPrePaintData &data, + std::chrono::milliseconds presentTime) override; - xcb_atom_t m_netWMStateAtom = 0; - xcb_atom_t m_netWMStateMaxHorzAtom = 0; - xcb_atom_t m_netWMStateMaxVertAtom = 0; + void drawWindow(const KWin::RenderTarget &renderTarget, + const KWin::RenderViewport &viewport, + KWin::EffectWindow *window, + int mask, + const QRegion ®ion, + KWin::WindowPaintData &data) override; - int m_frameRadius; +private: + void handleWindowAdded(KWin::EffectWindow *window); + void handleWindowDeleted(KWin::EffectWindow *window); + void updateWindow(KWin::EffectWindow *window); + bool shouldRound(KWin::EffectWindow *window) const; + bool isMaximized(KWin::EffectWindow *window) const; + KWin::GLShader *shader(); + + std::unique_ptr m_shader; + QSet m_redirected; + qreal m_frameRadius = 11; }; - -#endif diff --git a/plugins/roundedwindow/roundedwindow.json b/plugins/roundedwindow/roundedwindow.json deleted file mode 100644 index 2171d6e..0000000 --- a/plugins/roundedwindow/roundedwindow.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "KPlugin": { - "Authors": [ - { - "Email": "therealestrob@gmail.com", - "Name": "Rob" - } - ], - "Category": "Appearance", - "Dependencies": [ - ], - "Description": "An effect that shapes/rounds corners of windows.", - "EnabledByDefault": true, - "Icon": "", - "Id": "kwin4_effect_roundedwindow", - "License": "GPL", - "Name": "RoundedWindow", - "ServiceTypes": [ - "KWin/Effect" - ], - "Version": "git" - }, - "org.kde.kwin.effect": { - "video": "", - "exclusiveGroup": "", - "enabledByDefaultMethod": true - }, - "X-KDE-Ordering": "5", - "X-Plasma-API": "", - "X-Plasma-MainScript": "" -} diff --git a/plugins/roundedwindow/shaders.frag.110 b/plugins/roundedwindow/shaders.frag.110 deleted file mode 100644 index 4d42e27..0000000 --- a/plugins/roundedwindow/shaders.frag.110 +++ /dev/null @@ -1,14 +0,0 @@ -#version 110 - -uniform sampler2D sampler; -uniform sampler2D corner; - -varying vec2 texcoord0; - -void main() -{ - vec4 tex = texture2D(sampler, texcoord0); - vec4 texCorner = texture2D(corner, texcoord0); - tex.a = texCorner.a; - gl_FragColor = tex; -} \ No newline at end of file diff --git a/plugins/roundedwindow/shaders.frag.140 b/plugins/roundedwindow/shaders.frag.140 deleted file mode 100644 index 76da8e5..0000000 --- a/plugins/roundedwindow/shaders.frag.140 +++ /dev/null @@ -1,15 +0,0 @@ -#version 140 - -uniform sampler2D sampler; -uniform sampler2D corner; - -in vec2 texcoord0; -out vec4 fragColor; - -void main(void) -{ - vec4 tex = texture(sampler, texcoord0); - vec4 texCorner = texture(corner, texcoord0); - tex.a = texCorner.a; - fragColor = tex; -} \ No newline at end of file