feat: port the rounded corners effect to KWin 6 and drop the KWin 5 plugins
parent
06bf723f3b
commit
cd371a398d
@ -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,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,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 ®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"
|
||||
|
||||
@ -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…
Reference in New Issue