feat: port the rounded corners effect to KWin 6 and drop the KWin 5 plugins

main
Reion Wong 3 weeks ago
parent 06bf723f3b
commit cd371a398d

@ -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

@ -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

1
debian/control vendored

@ -5,6 +5,7 @@ Maintainer: CutefishOS <cutefishos@foxmail.com>
Build-Depends: cmake,
debhelper (>= 9),
extra-cmake-modules,
kwin-dev,
libkdecorations3-dev,
libkf6coreaddons-dev,
qt6-base-dev

@ -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()

@ -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")

@ -1,50 +1,35 @@
/*
* Copyright (C) 2020 PandaOS Team.
*
* Author: rekols <rekols@foxmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "button.h"
#include "decoration.h"
#include <KDecoration2/DecoratedClient>
#include <KDecoration2/Decoration>
#include <KDecoration3/DecoratedWindow>
#include <QPainter>
#include <QPainterPath>
Button::Button(KDecoration2::DecorationButtonType type, const QPointer<KDecoration2::Decoration> &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<KDecorati
}
}
KDecoration2::DecorationButton *Button::create(KDecoration2::DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent)
KDecoration3::DecorationButton *Button::create(KDecoration3::DecorationButtonType type,
KDecoration3::Decoration *decoration,
QObject *parent)
{
return new Button(type, decoration, parent);
}
void Button::paint(QPainter *painter, const QRect &repaintRegion)
void Button::paint(QPainter *painter, const QRectF &repaintArea)
{
Q_UNUSED(repaintRegion)
Cutefish::Decoration *decoration = qobject_cast<Cutefish::Decoration *>(this->decoration());
Q_UNUSED(repaintArea)
if (!decoration)
auto *decoration = qobject_cast<Cutefish::Decoration *>(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();
}

@ -1,36 +1,18 @@
/*
* Copyright (C) 2020 PandaOS Team.
*
* Author: rekols <rekols@foxmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef BUTTON_H
#define BUTTON_H
#include <KDecoration3/DecorationButton>
#include <KDecoration2/DecorationButton>
class Button : KDecoration2::DecorationButton
class Button : public KDecoration3::DecorationButton
{
public:
explicit Button(KDecoration2::DecorationButtonType type, const QPointer<KDecoration2::Decoration> &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

@ -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"
]
}
}

@ -1,393 +1,316 @@
/*
* Copyright (C) 2020 PandaOS Team.
*
* Author: rekols <rekols@foxmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
// own
#include "decoration.h"
#include "button.h"
// KDecoration
#include <KDecoration2/DecoratedClient>
#include <KDecoration2/DecorationSettings>
#include <KDecoration2/DecorationShadow>
#include <KPluginFactory>
#include <KDecoration3/DecoratedWindow>
#include <KDecoration3/DecorationSettings>
#include <KDecoration3/DecorationShadow>
// Qt
#include <QApplication>
#include <QPainter>
#include <QSettings>
#include <QSharedPointer>
#include <QFontMetricsF>
#include <QImageReader>
#include <QTimer>
#include <KPluginFactory>
#include <QPainter>
#include <QRadialGradient>
#include <cmath>
K_PLUGIN_FACTORY_WITH_JSON(
CutefishDecorationFactory,
"cutefishos.json",
registerPlugin<Cutefish::Decoration>(););
K_PLUGIN_FACTORY_WITH_JSON(CutefishDecorationFactory, "cutefishos.json",
registerPlugin<Cutefish::Decoration>();)
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<KDecoration2::DecorationShadow> g_sShadow;
static int s_decorationCount = 0;
static int s_shadowRadius = -1;
static std::shared_ptr<KDecoration3::DecorationShadow> 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<KDecoration2::DecorationButton> &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<KDecoration2::DecorationShadow>::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<KDecoration3::DecorationShadow>();
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"

@ -1,118 +1,65 @@
/*
* Copyright (C) 2020 PandaOS Team.
*
* Author: rekols <rekols@foxmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
// KDecoration
#include <KDecoration2/Decoration>
#include <KDecoration2/DecorationButtonGroup>
#include <KDecoration3/Decoration>
#include <KDecoration3/DecorationButtonGroup>
// Qt
#include <QFileSystemWatcher>
#include <QPixmap>
#include <QSettings>
#include <QVariant>
#include <QIcon>
#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;
};
}

@ -1,31 +0,0 @@
#include "x11shadow.h"
#include <QX11Info>
#include <xcb/xcb.h>
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);
}

@ -1,18 +0,0 @@
#ifndef X11SHADOW_H
#define X11SHADOW_H
#include <QObject>
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

@ -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")

@ -1,91 +0,0 @@
/*
* Copyright (C) 2020 PandaOS Team.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "button.h"
#include "decoration.h"
#include <KDecoration3/DecoratedWindow>
#include <QPainter>
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<Cutefish::Decoration *>(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();
}

@ -1,24 +0,0 @@
/*
* Copyright (C) 2020 PandaOS Team.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <KDecoration3/DecorationButton>
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;
};

@ -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"
]
}
}

@ -1,369 +0,0 @@
/*
* Copyright (C) 2020 PandaOS Team.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "decoration.h"
#include "button.h"
#include <KPluginFactory>
#include <KDecoration3/DecoratedWindow>
#include <KDecoration3/DecorationSettings>
#include <KDecoration3/DecorationShadow>
#include <QFontMetricsF>
#include <QImageReader>
#include <QPainter>
#include <QRadialGradient>
#include <cmath>
K_PLUGIN_FACTORY_WITH_JSON(CutefishDecorationFactory, "cutefishos.json",
registerPlugin<Cutefish::Decoration>();)
namespace Cutefish
{
static int s_decorationCount = 0;
static int s_shadowRadius = -1;
static std::shared_ptr<KDecoration3::DecorationShadow> 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<KDecoration3::DecorationShadow>();
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"

@ -1,71 +0,0 @@
/*
* Copyright (C) 2020 PandaOS Team.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <KDecoration3/Decoration>
#include <KDecoration3/DecorationButtonGroup>
#include <QFileSystemWatcher>
#include <QPixmap>
#include <QSettings>
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;
};
}

@ -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")

@ -1,42 +0,0 @@
/*
* Copyright © 2021 Reion Wong <reionwong@gmail.com>
*
* 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 <KPluginFactory>
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<RoundedWindow>();)
K_EXPORT_PLUGIN_VERSION(KWIN_EFFECT_API_VERSION)
#include "main.moc"

@ -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
}

@ -1,6 +0,0 @@
<RCC>
<qresource prefix="/">
<file>shaders.frag.110</file>
<file>shaders.frag.140</file>
</qresource>
</RCC>

@ -1,376 +1,303 @@
/*
* Copyright © 2021 Reion Wong <reionwong@gmail.com>
* Copyright © 2021 Reven Martin <revenmartin@gmail.com>
*
* 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 <QFile>
#include <QPainter>
#include <QPainterPath>
#include <QRegion>
#include <QDebug>
#include <effect/effecthandler.h>
#include <opengl/glshader.h>
#include <opengl/glshadermanager.h>
#include <opengl/openglcontext.h>
#include <utils/version.h>
#include <QSettings>
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 <QVector2D>
#include <QVector4D>
// 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<xcb_atom_t>(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 &region, 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 &region,
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"

@ -1,65 +1,57 @@
/*
* Copyright © 2021 Reion Wong <reionwong@gmail.com>
* Copyright © 2021 Reven Martin <revenmartin@gmail.com>
*
* 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 <effect/offscreeneffect.h>
#include <kwineffects.h>
#include <kwinglplatform.h>
#include <kwinglutils.h>
#include <QSet>
#include <xcb/xcb_atom.h>
#include <memory>
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 &region, 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 &region,
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<KWin::GLShader> m_shader;
QSet<KWin::EffectWindow *> m_redirected;
qreal m_frameRadius = 11;
};
#endif

@ -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": ""
}

@ -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;
}

@ -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;
}
Loading…
Cancel
Save