diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d8bc972d..d3ec2997c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,10 @@ find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS X11Extras ) +find_package(PkgConfig) +pkg_check_modules(Qsettings REQUIRED gsettings-qt) +include_directories(${Qsettings_INCLUDE_DIRS}) + find_package(Qt5Test ${QT_MIN_VERSION} CONFIG QUIET) set_package_properties(Qt5Test PROPERTIES PURPOSE "Required for tests" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2cdd67bc2..1ade3cbbc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -224,6 +224,8 @@ target_link_libraries(kwin epoxy::epoxy Threads::Threads + + ${Qsettings_LIBRARIES} ) add_subdirectory(backends) diff --git a/src/abstract_client.cpp b/src/abstract_client.cpp index 7e8a7eec0..ab16328f1 100644 --- a/src/abstract_client.cpp +++ b/src/abstract_client.cpp @@ -96,6 +96,7 @@ AbstractClient::AbstractClient() connect(ApplicationMenu::self(), &ApplicationMenu::applicationMenuEnabledChanged, this, [this] { Q_EMIT hasApplicationMenuChanged(hasApplicationMenu()); }); + m_bTabletToPcRestoreFlag = false; } AbstractClient::~AbstractClient() @@ -1129,6 +1130,9 @@ void AbstractClient::handleInteractiveMoveResize(int x, int y, int x_root, int y if (isWaitingForInteractiveMoveResizeSync()) return; // we're still waiting for the client or the timeout + if (workspace()->isInTabletMode()) + return; + const Gravity gravity = interactiveMoveResizeGravity(); if ((gravity == Gravity::None && !isMovableAcrossScreens()) || (gravity != Gravity::None && (isShade() || !isResizable()))) { @@ -2387,6 +2391,11 @@ bool AbstractClient::processDecorationButtonPress(QMouseEvent *event, bool ignor if (!wantsInput()) // we cannot be active, use it anyway active = true; + if(workspace()->isInTabletMode()) { + Workspace::self()->performWindowOperation(this, Options::NoOp); + return false; + } + // check whether it is a double click if (event->button() == Qt::LeftButton && titlebarPositionUnderMouse()) { if (m_decoration.doubleClickTimer.isValid()) { diff --git a/src/abstract_client.h b/src/abstract_client.h index ee6494c21..e92b322eb 100644 --- a/src/abstract_client.h +++ b/src/abstract_client.h @@ -371,6 +371,14 @@ public: return m_icon; } + void setTabletToPcRestoreFlag(bool bTabletToPcRestoreFlag){ + m_bTabletToPcRestoreFlag = bTabletToPcRestoreFlag; + } + + bool getTabletToPcRestoreFlag() const { + return m_bTabletToPcRestoreFlag; + } + bool isZombie() const; bool isActive() const { return m_active; @@ -1239,6 +1247,7 @@ private: bool m_keepBelow = false; bool m_demandsAttention = false; bool m_minimized = false; + bool m_bTabletToPcRestoreFlag; //平板切换pc后,如果此时窗口处于最小化,则该标志置为true,激活后用于将最大化窗口还原,再次为false QTimer *m_autoRaiseTimer = nullptr; QTimer *m_shadeHoverTimer = nullptr; ShadeMode m_shadeMode = ShadeNone; diff --git a/src/decorations/decorationbridge.cpp b/src/decorations/decorationbridge.cpp index 66a3ac712..3d4a11853 100644 --- a/src/decorations/decorationbridge.cpp +++ b/src/decorations/decorationbridge.cpp @@ -31,6 +31,10 @@ // Qt #include #include +#include +#include +#include +#include namespace KWin { @@ -120,6 +124,101 @@ void DecorationBridge::init() if (waylandServer()) { waylandServer()->decorationManager()->setDefaultMode(m_factory ? ServerSideDecorationManagerInterface::Mode::Server : ServerSideDecorationManagerInterface::Mode::None); } + + //读取dpi值 + m_dpi = 96; + QScreen *primary = QGuiApplication::primaryScreen(); + if (primary) { + m_dpi = primary->logicalDotsPerInchX(); + if(m_dpi < 30) + { + m_dpi = 30; //设置一个限度,不允许dpi小于30 + } + } + + QGSettings* pThemeSettings = new QGSettings("org.ukui.style", "/org/ukui/style/", this); + QString strTheme; + if (true == pThemeSettings->keys().contains("styleName")){ + strTheme = pThemeSettings->get("style-name").toString(); + qDebug() << "DecorationBridge::init theme:" << strTheme; + } + + if("ukui-light" == strTheme) + { + m_themeId = 0; + } + else if("ukui-dark" == strTheme) + { + m_themeId = 1; + } + else //默认ukui-default + { + m_themeId = 0; + } + + const QFont font = QFontDatabase::systemFont(QFontDatabase::TitleFont); + m_nFont = font.pointSize(); + m_strFontFamily = font.family(); + + QDBusConnection::sessionBus().connect(QString(), + QStringLiteral("/KGlobalSettings"), + QStringLiteral("org.kde.KGlobalSettings"), + QStringLiteral("slotThemeChange"), + this, SLOT(slotThemeUpdate(int))); + + QDBusConnection::sessionBus().connect(QString(), + QStringLiteral("/KGlobalSettings"), + QStringLiteral("org.kde.KGlobalSettings"), + QStringLiteral("slotFontChange"), + this, SLOT(fontUpdate(int, QString))); + + m_bTabletMode = false; + //kwin启动时获取运行模式: 0:PC模式, 1:平板模式 + QDBusMessage message = QDBusMessage::createMethodCall("com.kylin.statusmanager.interface", + "/", + "com.kylin.statusmanager.interface", + "get_current_tabletmode"); + + QDBusMessage response = QDBusConnection::sessionBus().call(message); + + if (response.type() == QDBusMessage::ReplyMessage) { + m_bTabletMode = response.arguments().takeFirst().toBool(); + //printf("TabletMode = %d\n", m_bTabletMode); + } else { + printf("TabletMode qdus invalid\n"); + } + //通过dbus监听PC-平板模式切换 + QDBusConnection::sessionBus().connect("com.kylin.statusmanager.interface", + "/", + "com.kylin.statusmanager.interface", + "mode_change_signal", + this, + SLOT(slotSetSurface(bool))); +} + +void DecorationBridge::slotThemeUpdate(int themeId) +{ + m_themeId = themeId; +} + +void DecorationBridge::fontUpdate(int nfont, QString strFamily) +{ + m_nFont = nfont; + m_strFontFamily = strFamily; + QFont font; + font.setPointSize(nfont); + font.setFamily(strFamily); + + Q_EMIT sig_updateFont(font); +} + +void DecorationBridge::slotSetSurface(bool bSurface) +{ + if(m_bTabletMode == bSurface) + { + return; + } + m_bTabletMode = bSurface; } void DecorationBridge::initPlugin() @@ -255,6 +354,11 @@ KDecoration2::Decoration *DecorationBridge::createDecoration(AbstractClient *cli if (!m_theme.isEmpty()) { args.insert(QStringLiteral("theme"), m_theme); } + args.insert(QStringLiteral("dpi"), m_dpi); //每创建一个渲染端,就把dpi值带过去,后面每新建一个客户就不需要反复获取获取dpi值 + args.insert(QStringLiteral("themeId"), m_themeId); //针对UKUI定制的主题id + args.insert(QStringLiteral("systemFontSize"), m_nFont); //标题栏字体大小 + args.insert(QStringLiteral("systemFont"), m_strFontFamily); //标题栏字体类型 + args.insert(QStringLiteral("tabletMode"), m_bTabletMode); auto deco = m_factory->create(client, QVariantList({args})); deco->setSettings(m_settings); deco->init(); diff --git a/src/decorations/decorationbridge.h b/src/decorations/decorationbridge.h index 530364fb0..7daed5e87 100644 --- a/src/decorations/decorationbridge.h +++ b/src/decorations/decorationbridge.h @@ -66,6 +66,12 @@ public: Q_SIGNALS: void metaDataLoaded(); + void sig_updateFont(QFont); + +public Q_SLOTS: + void slotThemeUpdate(int); + void fontUpdate(int nfont, QString strFamily); + void slotSetSurface(bool bSurface); private: QString readPlugin(); @@ -83,6 +89,13 @@ private: QString m_theme; QSharedPointer m_settings; bool m_noPlugin; + + int m_dpi; //dpi值 + int m_themeId; //主题id + int m_nFont; + QString m_strFontFamily; + bool m_bTabletMode; + KWIN_SINGLETON(DecorationBridge) }; } // Decoration diff --git a/src/decorations/settings.cpp b/src/decorations/settings.cpp index e493da6bc..0fe0f51a2 100644 --- a/src/decorations/settings.cpp +++ b/src/decorations/settings.cpp @@ -49,6 +49,7 @@ SettingsImpl::SettingsImpl(KDecoration2::DecorationSettings *parent) ); connect(Workspace::self(), &Workspace::configChanged, this, &SettingsImpl::readSettings); connect(DecorationBridge::self(), &DecorationBridge::metaDataLoaded, this, &SettingsImpl::readSettings); + connect(DecorationBridge::self(), &DecorationBridge::sig_updateFont, this, &SettingsImpl::updateFont); } SettingsImpl::~SettingsImpl() = default; @@ -142,6 +143,14 @@ static KDecoration2::BorderSize stringToSize(const QString &name) return it.value(); } +void SettingsImpl::updateFont(QFont font) +{ + if (font != m_font) { + m_font = font; + Q_EMIT decorationSettings()->fontChanged(m_font); + } +} + void SettingsImpl::readSettings() { KConfigGroup config = kwinApp()->config()->group(QStringLiteral("org.kde.kdecoration2")); diff --git a/src/decorations/settings.h b/src/decorations/settings.h index 250447f05..d12b79971 100644 --- a/src/decorations/settings.h +++ b/src/decorations/settings.h @@ -42,6 +42,9 @@ public: return m_font; } +public Q_SLOTS: + void updateFont(QFont font); + private: void readSettings(); QVector< KDecoration2::DecorationButtonType > readDecorationButtons(const KConfigGroup &config, diff --git a/src/effectloader.cpp b/src/effectloader.cpp index af48885cf..542082960 100644 --- a/src/effectloader.cpp +++ b/src/effectloader.cpp @@ -8,6 +8,8 @@ */ // own #include "effectloader.h" +// system +#include // KWin #include #include @@ -168,12 +170,27 @@ void ScriptedEffectLoader::queryAndLoadAll() QList ScriptedEffectLoader::findAllEffects() const { - return KPackage::PackageLoader::self()->listPackages(s_serviceType, QStringLiteral("kwin/effects")); +#if defined(QT_NO_DEBUG) + const QString packageRoot = QStringLiteral("kwin/effects"); +#else + QString packageRoot = QCoreApplication::applicationDirPath() + QLatin1String("/../../src/effects"); + if (access(packageRoot.toStdString().c_str(), F_OK) == -1) + packageRoot = QStringLiteral("kwin/effects"); + qCDebug(KWIN_CORE) << "listPackages from:" << packageRoot; +#endif + return KPackage::PackageLoader::self()->listPackages(s_serviceType, packageRoot); } KPluginMetaData ScriptedEffectLoader::findEffect(const QString &name) const { - const auto plugins = KPackage::PackageLoader::self()->findPackages(s_serviceType, QStringLiteral("kwin/effects"), +#if defined(QT_NO_DEBUG) + const QString packageRoot = QStringLiteral("kwin/effects"); +#else + QString packageRoot = QCoreApplication::applicationDirPath() + QLatin1String("/../../src/effects"); + if (access(packageRoot.toStdString().c_str(), F_OK) == -1) + packageRoot = QStringLiteral("kwin/effects"); +#endif + const auto plugins = KPackage::PackageLoader::self()->findPackages(s_serviceType, packageRoot, [name] (const KPluginMetaData &metadata) { return metadata.pluginId().compare(name, Qt::CaseInsensitive) == 0; } diff --git a/src/effects/CMakeLists.txt b/src/effects/CMakeLists.txt index 9d06969b9..ec4c75ed9 100644 --- a/src/effects/CMakeLists.txt +++ b/src/effects/CMakeLists.txt @@ -160,6 +160,8 @@ add_subdirectory(sheet) add_subdirectory(snaphelper) add_subdirectory(startupfeedback) add_subdirectory(trackmouse) +add_subdirectory(touchclick) +add_subdirectory(touchtrail) add_subdirectory(wobblywindows) ############################################################################### diff --git a/src/effects/touchclick/CMakeLists.txt b/src/effects/touchclick/CMakeLists.txt new file mode 100644 index 000000000..03f4d5808 --- /dev/null +++ b/src/effects/touchclick/CMakeLists.txt @@ -0,0 +1,13 @@ +####################################### +# Effect + +set(touchclick_SOURCES + touchclick.cpp + main.cpp +) + +kwin4_add_effect_module(kwin4_effect_touchclick ${touchclick_SOURCES}) + +# Data files +install(FILES data/circletexture.png DESTINATION ${KDE_INSTALL_DATADIR}/${KWIN_NAME}) +install(FILES data/ringstexture.png DESTINATION ${KDE_INSTALL_DATADIR}/${KWIN_NAME}) diff --git a/src/effects/touchclick/data/circletexture.png b/src/effects/touchclick/data/circletexture.png new file mode 100644 index 000000000..cb6148876 Binary files /dev/null and b/src/effects/touchclick/data/circletexture.png differ diff --git a/src/effects/touchclick/data/ringstexture.png b/src/effects/touchclick/data/ringstexture.png new file mode 100644 index 000000000..770ef2469 Binary files /dev/null and b/src/effects/touchclick/data/ringstexture.png differ diff --git a/src/effects/touchclick/main.cpp b/src/effects/touchclick/main.cpp new file mode 100644 index 000000000..496a1ee51 --- /dev/null +++ b/src/effects/touchclick/main.cpp @@ -0,0 +1,17 @@ +/* + SPDX-FileCopyrightText: 2022 Hongfei Shang + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "touchclick.h" + +namespace KWin +{ + +KWIN_EFFECT_FACTORY_SUPPORTED(TouchClickEffect, + "metadata.json.stripped", + return TouchClickEffect::supported();) +} // namespace KWin + +#include "main.moc" diff --git a/src/effects/touchclick/metadata.json b/src/effects/touchclick/metadata.json new file mode 100644 index 000000000..44af82a67 --- /dev/null +++ b/src/effects/touchclick/metadata.json @@ -0,0 +1,61 @@ +{ + "KPlugin": { + "Category": "Appearance", + "Description": "Visualize touch points", + "Description[ar]": "تصور نقاط اللمس", + "Description[az]": "Ekranda toxunan nöqtələri canlandırır", + "Description[ca@valencia]": "Visualitza els punts de contacte", + "Description[ca]": "Visualitza els punts de contacte", + "Description[cs]": "Vizualizovat dotekové body", + "Description[en_GB]": "Visualise touch points", + "Description[es]": "Visualizar los puntos de contacto", + "Description[fi]": "Korosta kosketuspisteet", + "Description[fr]": "Afficher les points tactiles", + "Description[hu]": "Vizualizálja az érintési pontokat", + "Description[ia]": "Visualisa punctos de contacto", + "Description[id]": "Memvisualkan titik sentuh", + "Description[it]": "Visualizza i punti di tocco", + "Description[ko]": "터치 지점 표시", + "Description[nl]": "Aanraakpunten visualiseren", + "Description[nn]": "Visualiser kontaktpunkt", + "Description[pl]": "Uwidacznia punkty dotyku", + "Description[pt_BR]": "Visualizar pontos de toque", + "Description[ru]": "Визуализация событий касания экрана", + "Description[sk]": "Vizualizovať dotykové body", + "Description[sl]": "Vizualiziraj točke dotika", + "Description[sv]": "Åskådliggör beröringspunkter", + "Description[tr]": "Dokunma noktalarını görselleştir", + "Description[uk]": "Візуалізувати точки дотику", + "Description[x-test]": "xxVisualize touch pointsxx", + "Description[zh_CN]": "高亮显示在触控屏幕上的触摸点", + "EnabledByDefault": true, + "Id": "touchclick", + "License": "GPL", + "Name": "Touch Points", + "Name[ar]": "نقاط اللمس", + "Name[az]": "Toxunma nöqtələri", + "Name[ca@valencia]": "Punts de contacte", + "Name[ca]": "Punts de contacte", + "Name[cs]": "Dotekové body", + "Name[en_GB]": "Touch Points", + "Name[es]": "Puntos de contacto", + "Name[fi]": "Kosketuspisteet", + "Name[fr]": "Points tactiles", + "Name[hu]": "Érintési pontok", + "Name[ia]": "Punctos de contacto", + "Name[it]": "Punti di tocco", + "Name[ko]": "터치 지점", + "Name[nl]": "Aanraakpunten", + "Name[nn]": "Kontaktpunkt", + "Name[pl]": "Punkty dotyku", + "Name[pt_BR]": "Pontos de toque", + "Name[ru]": "Точки прикосновения", + "Name[sk]": "Dotykové body", + "Name[sl]": "Točke dotika", + "Name[sv]": "Beröringspunkter", + "Name[tr]": "Dokunma Noktaları", + "Name[uk]": "Точки дотику", + "Name[x-test]": "xxTouch Pointsxx", + "Name[zh_CN]": "触摸点高亮" + } +} diff --git a/src/effects/touchclick/touchclick.cpp b/src/effects/touchclick/touchclick.cpp new file mode 100644 index 000000000..3c5ad29c2 --- /dev/null +++ b/src/effects/touchclick/touchclick.cpp @@ -0,0 +1,433 @@ +#include "touchclick.h" + +#include +#include + +#include + +namespace KWin { + +bool TouchClickEffect::supported() +{ + return effects->isOpenGLCompositing(); +} + +TouchClickEffect::TouchClickEffect() + : Effect() + , m_ringsTexture(nullptr) + , m_ringsBgTexture(nullptr) + , m_circleTexture(nullptr) +{ + m_isActiveLongPress = false; + m_drawProgress = 0; + + m_drawAnimation = new QPropertyAnimation(this, "drawValue"); + m_drawAnimation->setDuration(LONG_PRESS_LAST_TIME); + m_drawAnimation->setEasingCurve(QEasingCurve::Linear); + + m_activeTimer = new QTimer(this); + m_activeTimer->setSingleShot(true); + m_activeTimer->setInterval(LONG_PRESS_WAIT_TIME); + connect(m_activeTimer, &QTimer::timeout, this, &TouchClickEffect::onActiveTimerTimeout); + + loadTexture(); +} + +TouchClickEffect::~TouchClickEffect() +{ + if (m_ringsTexture) + delete m_ringsTexture; + m_ringsTexture = nullptr; + + if (m_ringsBgTexture) + delete m_ringsBgTexture; + m_ringsBgTexture = nullptr; + + if (m_circleTexture) + delete m_circleTexture; + m_circleTexture = nullptr; +} + +void TouchClickEffect::loadTexture() +{ +#if defined(QT_NO_DEBUG) + QString ringTextureFilePath = {QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("ringstexture.png"))}; +#else + QString ringTextureFilePath = QCoreApplication::applicationDirPath() + QStringLiteral("/../../src/effects/touchclick/data/ringstexture.png"); +#endif + QImage ringsTextureImage(ringTextureFilePath); + if (ringsTextureImage.isNull()) { + qWarning() << "load image [" << ringTextureFilePath << "] failed"; + return; + } + // 纹理与颜色融合, 纹理仅控制形状 + // uint ringR = 229, ringG = 239, ringB = 247, ringA = 165; + uint ringR = 35, ringG = 108, ringB = 218, ringA = 216; + for (int h = 0; h < ringsTextureImage.height(); ++h) { + for (int w = 0; w < ringsTextureImage.width(); ++w) { + const QColor &c = ringsTextureImage.pixelColor(w, h); + ringsTextureImage.setPixelColor(w, h, QColor(ringR, ringG, ringB, fmin(ringA, c.alpha()))); + } + } + if (!m_ringsTexture) + m_ringsTexture = new GLTexture(ringsTextureImage); + + QImage ringsBgTextureImage(ringTextureFilePath); + // 纹理与颜色融合, 纹理仅控制形状 + uint ringBgR = 229, ringBgG = 239, ringBgB = 247, ringBgA = 165; + // uint ringR = 35, ringG = 108, ringB = 218, ringA = 216; + for (int h = 0; h < ringsBgTextureImage.height(); ++h) { + for (int w = 0; w < ringsBgTextureImage.width(); ++w) { + const QColor &c = ringsBgTextureImage.pixelColor(w, h); + ringsBgTextureImage.setPixelColor(w, h, QColor(ringBgR, ringBgG, ringBgB, fmin(ringBgA, c.alpha()))); + } + } + if (!m_ringsBgTexture) + m_ringsBgTexture = new GLTexture(ringsBgTextureImage); + +#if defined(QT_NO_DEBUG) + QString circleTextureFilePath = {QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("circletexture.png"))}; +#else + QString circleTextureFilePath = QCoreApplication::applicationDirPath() + QStringLiteral("/../../src/effects/touchclick/data/circletexture.png"); +#endif + QImage circleTextureImage(circleTextureFilePath); + if (circleTextureImage.isNull()) { + qWarning() << "load image [" << ringTextureFilePath << "] failed"; + return; + } + // 纹理与颜色融合, 纹理仅控制形状 + uint circleR = 229, circleG = 239, circleB = 247, circleA = 165; + for (int h = 0; h < circleTextureImage.height(); ++h) { + for (int w = 0; w < circleTextureImage.width(); ++w) { + const QColor &c = circleTextureImage.pixelColor(w, h); + circleTextureImage.setPixelColor(w, h, QColor(circleR, circleG, circleB, fmin(circleA, c.alpha()))); + } + } + if (!m_circleTexture) + m_circleTexture = new GLTexture(circleTextureImage); +} + +void TouchClickEffect::prePaintScreen(ScreenPrePaintData& data, std::chrono::milliseconds presentTime) +{ + int time = 0; + if (m_lastPresentTime.count()) { + time = (presentTime - m_lastPresentTime).count(); + } + + auto it = m_allPoints.begin(); + while (it != m_allPoints.end()) { + it->time += time; + if (it->time > TOUCH_POINT_LIFE) { + it = m_allPoints.erase(it); + } else { + it++; + } + } + + if (m_allPoints.isEmpty()) { + m_lastPresentTime = std::chrono::milliseconds::zero(); + } else { + m_lastPresentTime = presentTime; + } + + effects->prePaintScreen(data, presentTime); +} + +void TouchClickEffect::paintScreen(int mask, const QRegion ®ion, ScreenPaintData &data) +{ + effects->paintScreen(mask, region, data); + if (effects->isOpenGLCompositing()) + paintScreenGL(mask, region, data); +} + +double TouchClickEffect::getDrawValue() const +{ + return m_drawProgress; +} + +void TouchClickEffect::setDrawValue(double value) +{ + m_drawProgress = value; + repaint(); +} + +void TouchClickEffect::onActiveTimerTimeout() +{ + m_isActiveLongPress = true; + + m_drawProgress = 0; + m_drawAnimation->setStartValue(0.0); + m_drawAnimation->setEndValue(1.0); + m_drawAnimation->start(); + + repaint(); +} + +void TouchClickEffect::paintScreenGL(int mask, const QRegion ®ion, ScreenPaintData &data) +{ + GLShader *shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); + shader->setUniform(GLShader::ModelViewProjectionMatrix, data.projectionMatrix()); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + //! 绘制点击的点 + for (int i = 0; i < m_allPoints.size(); ++i) { + TouchPoint &p = m_allPoints[i]; + float cr = (TOUCH_POINT_LIFE - p.time) * 1.0 / TOUCH_POINT_LIFE * CIRCLE_RADIUS; + // drawCircleGl(QColor(229, 239, 247, 216), p.pos.x(), p.pos.y(), cr, 0, 1); + drawRingsGl(m_circleTexture, p.pos.x(), p.pos.y(), 0, cr, 0, 1); + } + + //! 绘制长按动画 + if (m_allPoints.size() == 1 && m_allPoints[0].time == -1 && m_isActiveLongPress) { + QPointF &p = m_allPoints[0].pos; + // drawRingsGl(QColor(229, 239, 247, 165), p.x(), p.y(), RINGS_MINRADIUS, RINGS_MAXRADIUS, 1); + // drawRingsGl(QColor(35, 108, 218, 216), p.x(), p.y(), RINGS_MINRADIUS, RINGS_MAXRADIUS, m_drawProgress); + drawRingsGl(m_ringsBgTexture, p.x(), p.y(), RINGS_MINRADIUS, RINGS_MAXRADIUS, 0 ,1); + drawRingsGl(m_ringsTexture, p.x(), p.y(), RINGS_MINRADIUS, RINGS_MAXRADIUS, 0 ,m_drawProgress); +// drawRingsGl(m_circleTexture, p.x(), p.y() - RINGS_MINRADIUS - RINGS_WIDTH / 2, +// 0, RINGS_WIDTH / 2, M_PI, 0.5); +// drawRingsGl(m_circleTexture, p.x() + (RINGS_MINRADIUS + RINGS_WIDTH / 2) * sin(2 * M_PI * m_drawProgress), +// p.y() - (RINGS_MINRADIUS + RINGS_WIDTH / 2) * cos(2 * M_PI * m_drawProgress), +// 0, RINGS_WIDTH / 2, 2 * M_PI * m_drawProgress, 0.5); + } + glDisable(GL_BLEND); + ShaderManager::instance()->popShader(); +} + +void TouchClickEffect::drawCircleGl(const QColor& color, float cx, float cy, float r, float offset, float alpha) +{ + const int num_segments = 80; + const float theta = 2.0 * M_PI * alpha / float(num_segments); + const float c = cosf(theta); + const float s = sinf(theta); + float t; + float x = r * sinf(offset); + float y = -r * cosf(offset); + + GLVertexBuffer* vbo = GLVertexBuffer::streamingBuffer(); + vbo->reset(); + vbo->setUseColor(true); + vbo->setColor(color); + QVector verts; + verts.reserve(num_segments * 2 + 4); + + verts << cx << cy; + for (int ii = 0; ii < num_segments; ++ii) { + verts << x + cx << y + cy; + t = x; + x = c * x - s * y; + y = s * t + c * y; + } + verts << x + cx << y + cy; + vbo->setData(verts.size() / 2, 2, verts.data(), nullptr); + vbo->render(GL_TRIANGLE_FAN); +} + +QPointF TouchClickEffect::calculateExternalRectAndRoundRaysIntersect(float cx, float cy, float r, float alpha) +{ + QPointF res; + float dy, dx; + if (alpha <= M_PI_4 || alpha >= 2 * M_PI - M_PI_4) { + dy = r; + dx = dx * tan(alpha); + } else if (alpha >= M_PI_4 && alpha <= M_PI_2 + M_PI_4) { + dx = r; + dy = dx * (1.0 / tan(alpha)); + } else if (alpha >= M_PI_2 + M_PI_4 && alpha <= M_PI + M_PI_4) { + dy = -r; + dx = dy * tan(alpha); + } else { + dx = -r; + dy = dx * (1.0 / tan(alpha)); + } + // 处理成Qt里的坐标系 + dy = -dy; + + res.setX(cx + dx); + res.setY(cy + dy); + return res; +} + +void TouchClickEffect::drawCircleGl(GLTexture *texture, float cx, float cy, float r, float offset, float alpha) +{ + static const QPointF rectPoint[] = { + {1.0f, 1.0f}, + {0.0f, 1.0f}, + {0.0f, 0.0f}, + {1.0f, 0.0f} + }; + + QVector verts, texCoords; + verts << cx << cy; + texCoords << 0.5f << 0.5f; + + //! 计算坐标映射的关键点 + QPointF bPos = calculateExternalRectAndRoundRaysIntersect(cx, cy, r, offset); + QPointF ePos = calculateExternalRectAndRoundRaysIntersect(cx, cy, r, offset + alpha * 2.0 * M_PI); + + verts << bPos.x() << bPos.y(); + texCoords << (bPos.x() - cx) / r + 0.5f << (cy - bPos.y()) / r + 0.5f; + + // 太难计算,暂时先使用绘制圆环的方式去绘制圆 + // TODO 计算矩形上的关键点 + +} + +void TouchClickEffect::drawRingsGl(const QColor &color, float cx, float cy, float rmin, float rmax, float alpha) +{ + const int num_segments = 100; + const float theta = 2.0 * M_PI * alpha / float(num_segments); + const float c = cosf(theta); + const float s = sinf(theta); + + float tmin, tmax; + float xmin = 0, xmax = 0; + float ymin = -rmin, ymax = -rmax; + GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); + vbo->reset(); + vbo->setUseColor(true); + vbo->setColor(color); + QVector verts; + verts.reserve(num_segments * 4 + 4); + + for (int i = 0;i < num_segments; ++i) { + verts << xmin + cx << ymin + cy; + verts << xmax + cx << ymax + cy; + tmin = xmin; + tmax = xmax; + xmin = c * xmin - s * ymin; + xmax = c * xmax - s * ymax; + ymin = s * tmin + c * ymin; + ymax = s * tmax + c * ymax; + } + verts << xmin + cx << ymin + cy; + verts << xmax + cx << ymax + cy; + vbo->setData(verts.size() / 2, 2, verts.data(), nullptr); + vbo->render(GL_TRIANGLE_STRIP); +} + +void TouchClickEffect::drawRingsGl(GLTexture *texture, float cx, float cy, float rmin, float rmax, float offset, float alpha) +{ + const int num_segments = 100; + const float theta = 2.0 * M_PI * alpha / float(num_segments); + const float c = cosf(theta); + const float s = sinf(theta); + + float tmin, tmax; + float xmin = rmin * sin(offset), xmax = rmax * sin(offset); + float ymin = -rmin * cos(offset), ymax = -rmax * cos(offset); + GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); + vbo->reset(); + + QVector verts; + QVector texCoords; + verts.reserve(num_segments * 4 + 4); + + for (int i = 0;i < num_segments; ++i) { + verts << xmin + cx << ymin + cy; + verts << xmax + cx << ymax + cy; + tmin = xmin; + tmax = xmax; + xmin = c * xmin - s * ymin; + xmax = c * xmax - s * ymax; + ymin = s * tmin + c * ymin; + ymax = s * tmax + c * ymax; + texCoords << i * 1.0f / num_segments << 1.0f; + texCoords << i * 1.0f / num_segments << 0.0f; + } + verts << xmin + cx << ymin + cy; + verts << xmax + cx << ymax + cy; + texCoords << 1.0f << 1.0f; + texCoords << 1.0f << 0.0f; + + texture->bind(); + vbo->setData(verts.size() / 2, 2, verts.data(), texCoords.data()); + vbo->render(GL_TRIANGLE_STRIP); + texture->unbind(); +} + +void TouchClickEffect::repaint() +{ +// QRect screenRect = effects->virtualScreenGeometry(); +// QRegion dirtyArea; +// for (int i = 0;i < m_allPoints.size(); ++i) { +// QPointF &p = m_allPoints[i].pos; +// dirtyArea |= QRegion(QRect(p.x() - RINGS_MAXRADIUS - 10, +// p.y() - RINGS_MAXRADIUS - 10, +// p.x() + RINGS_MAXRADIUS + 10, +// p.y() + RINGS_MAXRADIUS + 10)); +// } +// dirtyArea &= screenRect; +// effects->addRepaint(dirtyArea); + //! TODO 计算dirtyArea时,在边缘时kwin有bug,不更新边缘区域。先全屏重绘。 + effects->addRepaintFull(); +} + +void TouchClickEffect::postPaintScreen() +{ + effects->postPaintScreen(); + repaint(); +} + +bool TouchClickEffect::isActive() const +{ + return m_allPoints.size(); +} + +bool TouchClickEffect::touchDown(qint32 id, const QPointF &pos, quint32 time) +{ + if (!m_fingerDetails.size()) { + //! 倒计时去激活长按动画 + m_activeTimer->start(); + } else { + if (m_activeTimer->isActive()) { + m_activeTimer->stop(); + } + m_isActiveLongPress = false; + } + m_fingerDetails.insert(id); + m_allPoints.append({-1, id, pos}); + repaint(); + return false; +} + +bool TouchClickEffect::touchMotion(qint32 id, const QPointF &pos, quint32 time) +{ + for (int i = 0;i < m_allPoints.size(); ++i) { + TouchPoint &p = m_allPoints[i]; + if (p.detail != id) continue; + if (p.time >= 0) break; + qreal dis = sqrt(pow(pos.x() - p.pos.x(), 2) + pow(pos.y() - p.pos.y(), 2)); + if (dis > MIN_SHAKE_DISTANCE) { + p.time = 0; + } + break; + } + repaint(); + return false; +} + +bool TouchClickEffect::touchUp(qint32 id, quint32 time) +{ + Q_UNUSED(time) + if (m_fingerDetails.find(id) == m_fingerDetails.end()) + return false; + m_fingerDetails.erase(id); + if (!m_fingerDetails.size()) { + if (m_activeTimer->isActive()) { + m_activeTimer->stop(); + } + m_isActiveLongPress = false; + } + for (int i = 0;i < m_allPoints.size(); ++i) { + TouchPoint &p = m_allPoints[i]; + if (p.detail != id) continue; + if (p.time >= 0) break; + p.time = 0; + } + return false; +} + +} diff --git a/src/effects/touchclick/touchclick.h b/src/effects/touchclick/touchclick.h new file mode 100644 index 000000000..a2597fd46 --- /dev/null +++ b/src/effects/touchclick/touchclick.h @@ -0,0 +1,94 @@ +#ifndef KWIN_TOUCHCLICK_H +#define KWIN_TOUCHCLICK_H + +#include +#include + +#include +#include +#include +#include +#include + +namespace KWin { + +#define MIN_SHAKE_DISTANCE 7 // 防抖处理 +#define CIRCLE_RADIUS 24 +#define RINGS_WIDTH 12 +#define RINGS_MINRADIUS (CIRCLE_RADIUS) +#define RINGS_MAXRADIUS (RINGS_MINRADIUS + RINGS_WIDTH) +#define LONG_PRESS_WAIT_TIME 300/*ms*/ +#define LONG_PRESS_LAST_TIME 500/*ms*/ +#define TOUCH_POINT_LIFE 250 + +/*! + * \brief touch click effect and long press effect + * \author Yunpeng Zhu. + */ +class TouchClickEffect + : public Effect +{ + Q_OBJECT + Q_PROPERTY(double drawValue READ getDrawValue WRITE setDrawValue) + + typedef struct _touch_point { + //! 当time==0时,开始消失 + //! 当time==-1时,触摸点不消失 + int64_t time; + int detail; + QPointF pos; + } TouchPoint; + +public: + TouchClickEffect(); + ~TouchClickEffect(); + void prePaintScreen(ScreenPrePaintData& data, std::chrono::milliseconds presentTime) override; + void paintScreen(int mask, const QRegion ®ion, ScreenPaintData& data) override; + void postPaintScreen() override; + bool isActive() const override; + bool touchDown(qint32 id, const QPointF &pos, quint32 time) override; + bool touchMotion(qint32 id, const QPointF &pos, quint32 time) override; + bool touchUp(qint32 id, quint32 time) override; + + double getDrawValue() const; + void setDrawValue(double value); + + static bool supported(); + +private Q_SLOTS: + void onActiveTimerTimeout(); + +private: + void paintScreenGL(int mask, const QRegion ®ion, ScreenPaintData &data); + /*! + * \brief 计算圆心发出的射线和圆外接矩形相交的点的位置 + * \param (cx, cy)圆心 r半径 + * \param 射线与向量[(cx,cy)->(cx, cy - r)]成夹角alpha + * \return 相交点的位置 + */ + QPointF calculateExternalRectAndRoundRaysIntersect(float cx, float cy, float r, float alpha); + void drawCircleGl(const QColor& color, float cx, float cy, float r, float offset, float alpha); + void drawCircleGl(GLTexture *texture, float cx, float cy, float r, float offset, float alpha); + void drawRingsGl(const QColor& color, float cx, float cy, float rmin, float rmax, float alpha/*0-1*/); + void drawRingsGl(GLTexture *texture, float cx, float cy, float rmin, float rmax, float offset, float alpha/*0-1*/); + void repaint(); + void loadTexture(); + + std::chrono::milliseconds m_lastPresentTime = std::chrono::milliseconds::zero(); + bool m_isActiveLongPress; + std::unordered_set m_fingerDetails; + + QTimer *m_activeTimer; + QPropertyAnimation *m_drawAnimation; + //! 当前绘制的进度 + double m_drawProgress; + + QVector m_allPoints; + GLTexture *m_ringsTexture; + GLTexture *m_ringsBgTexture; + GLTexture *m_circleTexture; +}; + +} + +#endif //KWIN_TOUCHCLICK_H diff --git a/src/effects/touchtrail/CMakeLists.txt b/src/effects/touchtrail/CMakeLists.txt new file mode 100644 index 000000000..153592beb --- /dev/null +++ b/src/effects/touchtrail/CMakeLists.txt @@ -0,0 +1,14 @@ +####################################### +# Effect + +set(touchtrail_SOURCES + touchtrail.cpp + main.cpp +) + +kwin4_add_effect_module(kwin4_effect_touchtrail ${touchtrail_SOURCES}) + + +# Data files +install(FILES data/trailing.png DESTINATION ${KDE_INSTALL_DATADIR}/${KWIN_NAME}) + diff --git a/src/effects/touchtrail/data/trailing.png b/src/effects/touchtrail/data/trailing.png new file mode 100644 index 000000000..03a140943 Binary files /dev/null and b/src/effects/touchtrail/data/trailing.png differ diff --git a/src/effects/touchtrail/main.cpp b/src/effects/touchtrail/main.cpp new file mode 100644 index 000000000..02d702138 --- /dev/null +++ b/src/effects/touchtrail/main.cpp @@ -0,0 +1,18 @@ +/* + SPDX-FileCopyrightText: 2022 Hongfei Shang + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "touchtrail.h" + +namespace KWin +{ + +KWIN_EFFECT_FACTORY_SUPPORTED(TouchTrailEffect, + "metadata.json.stripped", + return TouchTrailEffect::supported();) + +} // namespace KWin + +#include "main.moc" diff --git a/src/effects/touchtrail/metadata.json b/src/effects/touchtrail/metadata.json new file mode 100644 index 000000000..073d01526 --- /dev/null +++ b/src/effects/touchtrail/metadata.json @@ -0,0 +1,61 @@ +{ + "KPlugin": { + "Category": "Appearance", + "Description": "Visualize touch points", + "Description[ar]": "تصور نقاط اللمس", + "Description[az]": "Ekranda toxunan nöqtələri canlandırır", + "Description[ca@valencia]": "Visualitza els punts de contacte", + "Description[ca]": "Visualitza els punts de contacte", + "Description[cs]": "Vizualizovat dotekové body", + "Description[en_GB]": "Visualise touch points", + "Description[es]": "Visualizar los puntos de contacto", + "Description[fi]": "Korosta kosketuspisteet", + "Description[fr]": "Afficher les points tactiles", + "Description[hu]": "Vizualizálja az érintési pontokat", + "Description[ia]": "Visualisa punctos de contacto", + "Description[id]": "Memvisualkan titik sentuh", + "Description[it]": "Visualizza i punti di tocco", + "Description[ko]": "터치 지점 표시", + "Description[nl]": "Aanraakpunten visualiseren", + "Description[nn]": "Visualiser kontaktpunkt", + "Description[pl]": "Uwidacznia punkty dotyku", + "Description[pt_BR]": "Visualizar pontos de toque", + "Description[ru]": "Визуализация событий касания экрана", + "Description[sk]": "Vizualizovať dotykové body", + "Description[sl]": "Vizualiziraj točke dotika", + "Description[sv]": "Åskådliggör beröringspunkter", + "Description[tr]": "Dokunma noktalarını görselleştir", + "Description[uk]": "Візуалізувати точки дотику", + "Description[x-test]": "xxVisualize touch pointsxx", + "Description[zh_CN]": "高亮显示在触控屏幕上的触摸点", + "EnabledByDefault": true, + "Id": "touchtrail", + "License": "GPL", + "Name": "Touch Points", + "Name[ar]": "نقاط اللمس", + "Name[az]": "Toxunma nöqtələri", + "Name[ca@valencia]": "Punts de contacte", + "Name[ca]": "Punts de contacte", + "Name[cs]": "Dotekové body", + "Name[en_GB]": "Touch Points", + "Name[es]": "Puntos de contacto", + "Name[fi]": "Kosketuspisteet", + "Name[fr]": "Points tactiles", + "Name[hu]": "Érintési pontok", + "Name[ia]": "Punctos de contacto", + "Name[it]": "Punti di tocco", + "Name[ko]": "터치 지점", + "Name[nl]": "Aanraakpunten", + "Name[nn]": "Kontaktpunkt", + "Name[pl]": "Punkty dotyku", + "Name[pt_BR]": "Pontos de toque", + "Name[ru]": "Точки прикосновения", + "Name[sk]": "Dotykové body", + "Name[sl]": "Točke dotika", + "Name[sv]": "Beröringspunkter", + "Name[tr]": "Dokunma Noktaları", + "Name[uk]": "Точки дотику", + "Name[x-test]": "xxTouch Pointsxx", + "Name[zh_CN]": "触摸点高亮" + } +} diff --git a/src/effects/touchtrail/touchtrail.cpp b/src/effects/touchtrail/touchtrail.cpp new file mode 100644 index 000000000..2d07374f8 --- /dev/null +++ b/src/effects/touchtrail/touchtrail.cpp @@ -0,0 +1,340 @@ +#include "touchtrail.h" + +#include + +#include +#include + +#include + +namespace KWin{ + +TouchTrailEffect::TouchTrailEffect() + : Effect() + , m_texture(nullptr) +{ + loadTexture(); + loadShader(); +} + +TouchTrailEffect::~TouchTrailEffect() +{ + if (m_texture) + delete m_texture; + m_texture = nullptr; +} + +bool TouchTrailEffect::supported() +{ + return effects->isOpenGLCompositing(); +} + +void TouchTrailEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) +{ + int time = 0; + if (m_lastPresentTime.count()) { + time = (presentTime - m_lastPresentTime).count(); + } + + for (int i = 0; i < m_touchPoints.size(); ++i) { + QVector::iterator it = m_touchPoints[i].begin(); + while (it != m_touchPoints[i].end()) { + it->life += time; + ++it; + } + + while (m_touchPoints[i].size() && m_touchPoints[i].first().life > MAX_LIFE) { + if (m_touchPoints[i].size() == 1) { + int detail = m_touchPoints[i].first().detail; + if (!m_idIsUse[i]) { + m_detail2ID.erase(detail); + } + } + m_touchPoints[i].removeFirst(); + } + } + calTriPoints(); + + if (0 == m_detail2ID.size()) { + m_lastPresentTime = std::chrono::milliseconds::zero(); + } else { + m_lastPresentTime = presentTime; + } + + effects->prePaintScreen(data, presentTime); +} + +void TouchTrailEffect::paintScreen(int mask, const QRegion ®ion, ScreenPaintData &data) +{ + // printf("adasdasds\n"); + effects->paintScreen(mask, region, data); + if (effects->isOpenGLCompositing()) { + paintScreenGL(data); + } +} + +void TouchTrailEffect::paintScreenGL(ScreenPaintData &data) +{ + GLShader *shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); + shader->setUniform(GLShader::ModelViewProjectionMatrix, data.projectionMatrix()); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); + + for (int i = 0; i < m_triPoints.size(); ++i) + { + QVector vert, texCoord; + const int &n = m_triPoints[i].size(); + for (int j = 0; j < n; ++j) { + vert << m_triPoints[i][j].x() << m_triPoints[i][j].y(); + texCoord << ((j & 1) ? 1.0f : 0.0f) << (j >> 1) * 1.0 / (n >> 1); + } + vbo->reset(); + m_texture->bind(); + vbo->setData(vert.size() / 2, 2, vert.data(), texCoord.data()); + vbo->render(GL_TRIANGLE_STRIP); + m_texture->unbind(); + } + + + glDisable(GL_BLEND); + ShaderManager::instance()->popShader(); +} + +void TouchTrailEffect::postPaintScreen() +{ + effects->postPaintScreen(); + repaint(); +} + +bool TouchTrailEffect::isActive() const +{ + return m_detail2ID.size(); +} + +bool TouchTrailEffect::touchDown(qint32 id, const QPointF &pos, quint32 time) +{ + // printf("当前手指ID=%d\n", id); + insertTouchPoint(id, pos); + repaint(); + return false; +} + +bool TouchTrailEffect::touchMotion(qint32 id, const QPointF &pos, quint32 time) +{ + updateTouchPoint(id, pos); + repaint(); + return false; +} + +bool TouchTrailEffect::touchUp(qint32 id, quint32 time) +{ + // printf("离开的手指ID=%d\n", id); + removeTouchPoint(id); + // 各触摸点开始快速衰减 + // TODO 加上滑动距离作为衰减因子 + for (int i = 0; i < m_touchPoints.size(); ++i) { + int sub; + for (int j = 0; j < m_touchPoints[i].size(); j++) { + if (!j) { + sub = MAX_LIFE - m_touchPoints[i][j].life; + m_touchPoints[i][j].life = MAX_LIFE; + + } else { + m_touchPoints[i][j].life += sub; + } + } + } + return false; +} + +void TouchTrailEffect::insertTouchPoint(qint32 detail, QPointF pos) +{ + int id = -1; + do { + ++id; + if (id == m_touchPoints.size()) { + m_touchPoints.append(QVector()); + m_triPoints.append(QVector()); + m_idIsUse.append(false); + } + } while (m_idIsUse[id] || m_touchPoints[id].size()); + + m_detail2ID[detail] = id; + m_idIsUse[id] = true; + m_touchPoints[id].append({detail, pos, 0}); + + // printf("当前分配的ID为 %d\n", id); +} + +void TouchTrailEffect::updateTouchPoint(qint32 detail, QPointF pos) +{ + // 开关混成有可能导致事件不成对 + if (m_detail2ID.find(detail) == m_detail2ID.end()) { + return; + } + int id = m_detail2ID[detail]; + m_touchPoints[id].append({detail, pos, 0}); +} + +void TouchTrailEffect::removeTouchPoint(qint32 detail) +{ + // 开关混成有可能导致事件不成对 + if (m_detail2ID.find(detail) == m_detail2ID.end()) { + return; + } + int id = m_detail2ID[detail]; + m_idIsUse[id] = false; + if (!m_touchPoints[id].size()) { + m_detail2ID.erase(detail); + } +} + +void TouchTrailEffect::loadTexture() +{ +#if defined(QT_NO_DEBUG) + QString file_path = {QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral(DEFAULT_TEXTURE_IMAGE))}; +#else + QString file_path = QCoreApplication::applicationDirPath() + QStringLiteral("/../../src/effects/touchtrail/data/") + QStringLiteral(DEFAULT_TEXTURE_IMAGE); +#endif + QImage img(file_path); + if (img.isNull()) { + qWarning() << "load image [" << file_path << "] failed"; + return; + } + + //! TODO 使用KWin中的纹理融合算法 + //! 没有找到kwin里,opengl的纹理融合 + //! 先手写个简单的纹理和颜色的融合,简单支持 + qreal Rd = 139.0 / 255.0; + qreal Gd = 134.0 / 255.0; + qreal Bd = 130.0 / 255.0; + qreal Ad = 30.0 / 255.0; + + for (int i = 0; i < img.width(); ++i) { + for (int j = 0; j < img.height(); ++j) { + QRgb p = img.pixel(i, j); + qreal Rs = qRed(p) / 255.0; + qreal Gs = qGreen(p) / 255.0; + qreal Bs = qBlue(p) / 255.0; + qreal As = qAlpha(p) / 255.0; + // printf("%d\n", As); + //! 纹理和颜色融合 +// img.setPixel(i, j, qRgba((Rs * As + Rd * (1 - As)) * 255, +// (Gs * As + Gd * (1 - As)) * 255, +// (Bs * As + Bd * (1 - As)) * 255, +// (fmin(As, Ad)) * 255)); + //! 纹理仅控制形状 + img.setPixel(i, j, qRgba(Rd * 255, + Gd * 255, + Bd * 255, + (fmin(As, Ad)) * 255)); + } + } + if (!m_texture) + m_texture = new GLTexture(img); +} + +void TouchTrailEffect::loadShader() +{ + // m_shader.reset(ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture, QString(), QStringLiteral("coverswitch-reflection.glsl"))); +} + +void TouchTrailEffect::calTriPoints() +{ + for (int i = 0; i < m_touchPoints.size(); ++i) + { + m_triPoints[i].clear(); + if (m_touchPoints[i].size() < 3) continue; + QPointF last(m_touchPoints[i].first().pos); + QVector::iterator it = m_touchPoints[i].begin(); + for (++it; it != m_touchPoints[i].end(); ++it) { + QPointF &cur = it->pos; + qreal d = dist(cur.x(), cur.y(), last.x(), last.y()); + if (d < MIN_POINT_SEG) continue; + if (cur.x() == last.x() || cur.y() == last.y()) { + continue; + } + // 方向为 last->cur 的向量 + QPointF vec_l2c(cur.x() - last.x(), cur.y() - last.y()); + // cur last线段上的中点 + QPointF mid((cur.x() + last.x()) / 2, (cur.y() + last.y()) / 2); + d /= 2; + // 向量的斜率k + qreal k = (cur.y() - last.y()) / (cur.x() - last.x()); + qreal vlk = 1.0 / k; + qreal alpha = atan(fabs(vlk)); + qreal dx = DEFAULT_STROKE_WIDTH * cos(alpha); + qreal dy = DEFAULT_STROKE_WIDTH * sin(alpha); + QPointF p1, p2; + + if (k > 0 && vec_l2c.x() > 0) { + p1.setX(mid.x() - dx); p1.setY(mid.y() + dy); + p2.setX(mid.x() + dx); p2.setY(mid.y() - dy); + } else if (k > 0 && vec_l2c.x() < 0) { + p1.setX(mid.x() + dx); p1.setY(mid.y() - dy); + p2.setX(mid.x() - dx); p2.setY(mid.y() + dy);; + } else if (k < 0 && vec_l2c.x() > 0) { + p1.setX(mid.x() + dx); p1.setY(mid.y() + dy); + p2.setX(mid.x() - dx); p2.setY(mid.y() - dy); + } else { + p1.setX(mid.x() - dx); p1.setY(mid.y() - dy); + p2.setX(mid.x() + dx); p2.setY(mid.y() + dy); + } + m_triPoints[i].append(p1); + m_triPoints[i].append(p2); + last = cur; + + if (it + 1 == m_touchPoints[i].end()) { + mid = it->pos; + if (k > 0 && vec_l2c.x() > 0) { + p1.setX(mid.x() - dx); p1.setY(mid.y() + dy); + p2.setX(mid.x() + dx); p2.setY(mid.y() - dy); + } else if (k > 0 && vec_l2c.x() < 0) { + p1.setX(mid.x() + dx); p1.setY(mid.y() - dy); + p2.setX(mid.x() - dx); p2.setY(mid.y() + dy);; + } else if (k < 0 && vec_l2c.x() > 0) { + p1.setX(mid.x() + dx); p1.setY(mid.y() + dy); + p2.setX(mid.x() - dx); p2.setY(mid.y() - dy); + } else { + p1.setX(mid.x() - dx); p1.setY(mid.y() - dy); + p2.setX(mid.x() + dx); p2.setY(mid.y() + dy); + } + m_triPoints[i].append(p1); + m_triPoints[i].append(p2); + } + } + } + +} + +void TouchTrailEffect::repaint() +{ + // effects->addRepaintFull(); + + if (m_triPoints.size()) { + QRect dirtyRect; + QPoint topLeft(INT32_MAX, INT32_MAX), bottomRight(INT32_MIN, INT32_MIN); + for (int i = 0; i < m_triPoints.size(); ++i) { + for (int j = 0; j < m_triPoints[i].size(); ++j) { + QPointF &p = m_triPoints[i][j]; + topLeft.setX(fmin(topLeft.x(), p.x())); + topLeft.setY(fmin(topLeft.y(), p.y())); + bottomRight.setX(fmax(bottomRight.x(), p.x())); + bottomRight.setY(fmax(bottomRight.y(), p.y())); + } + } + dirtyRect.setTopLeft(topLeft); + dirtyRect.setBottomRight(bottomRight); + + m_dirtyRect = dirtyRect; + effects->addRepaint(m_dirtyRect); + } else { + effects->addRepaintFull(); + } + return; +} + +} diff --git a/src/effects/touchtrail/touchtrail.h b/src/effects/touchtrail/touchtrail.h new file mode 100644 index 000000000..eba27a4d1 --- /dev/null +++ b/src/effects/touchtrail/touchtrail.h @@ -0,0 +1,81 @@ +#ifndef KWIN_TOUCHMOTIONSTREAK_H +#define KWIN_TOUCHMOTIONSTREAK_H + +#include +#include + +#include + +#include + +namespace KWin { + +#define MAX_FINGER_COUNT 10 // 触摸屏支持最大触摸点的个数 +#define MIN_POINT_SEG 2/*px*/ // 触摸点之间允许的最小欧氏距离 +#define DEFAULT_STROKE_WIDTH 3/*px*/ // 条带的宽度 +#define MAX_POINT_NUMBER 8 // 保存的触摸点的最大数量 +#define DEFAULT_TEXTURE_IMAGE "trailing.png" // 默认纹理 +#define MAX_LIFE 200 // 每个触摸点存活的最长时间 + +#define dist(x1,y1,x2,y2) sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)) + +/*! + * \brief UKUI Touch Motion Streak Effect + * \author Yunpeng Zhu. + */ +class TouchTrailEffect + : public Effect +{ + Q_OBJECT + typedef struct _touch_point{ + int detail; + QPointF pos; + int life; + }TouchPoint; +public: + TouchTrailEffect(); + virtual ~TouchTrailEffect(); + + void prePaintScreen(ScreenPrePaintData& data, std::chrono::milliseconds presentTime) override; + void paintScreen(int mask, const QRegion ®ion, ScreenPaintData& data) override; + void postPaintScreen() override; + + bool isActive() const override; + bool touchDown(qint32 id, const QPointF &pos, quint32 time) override; + bool touchMotion(qint32 id, const QPointF &pos, quint32 time) override; + bool touchUp(qint32 id, quint32 time) override; + + static bool supported(); + +private: + void paintScreenGL(ScreenPaintData &data); + void insertTouchPoint(qint32 detail, QPointF pos); + void updateTouchPoint(qint32 detail, QPointF pos); + void removeTouchPoint(qint32 detail); + void loadTexture(); + void loadShader(); + /*! + * \brief 计算三角形条带顶点的位置 + */ + void calTriPoints(); + void repaint(); + + //! 保存触摸点坐标 + QVector> m_touchPoints; + //! 保存所需要绘制的三角形条带的坐标 + QVector> m_triPoints; + //! 标记id是否被使用 + QVector m_idIsUse; + //! 将detail映射到0~MAX_FINGER_COUNT-1 + std::unordered_map m_detail2ID; + //! 绘制 + GLTexture *m_texture; + //! 记录需要重绘的区域 + QRect m_dirtyRect; + std::chrono::milliseconds m_lastPresentTime = std::chrono::milliseconds::zero(); + +}; + +} + +#endif // KWIN_TOUCHMOTIONSTREAK_H diff --git a/src/main_wayland.cpp b/src/main_wayland.cpp index 346426fd7..9e8be17ad 100644 --- a/src/main_wayland.cpp +++ b/src/main_wayland.cpp @@ -40,6 +40,8 @@ #include #include #include +#include +#include // system #if HAVE_SYS_PRCTL_H @@ -58,6 +60,15 @@ #include #include +#if defined(QT_NO_DEBUG) +#define LOG_FILE_COUNT_MAX 2 +#else +#define LOG_FILE_COUNT_MAX 4 +#endif +#define LOG_FILE_SIZE_MAX 20*1024*1024 +#define LOG_FILE_PREFIX "ukui_kwin" +#define LOG_FILE_PATH "/.log/" + Q_IMPORT_PLUGIN(KWinIntegrationPlugin) Q_IMPORT_PLUGIN(KGlobalAccelImpl) Q_IMPORT_PLUGIN(KWindowSystemKWinPlugin) @@ -360,8 +371,106 @@ void dropNiceCapability() } // namespace + +/* +* find next log file, and do some check. +*/ +static int checkLogFile(int index) { + int logFileIndex = index; + for (size_t i = 0; i < LOG_FILE_COUNT_MAX; i++) { + logFileIndex = (logFileIndex + 1) % LOG_FILE_COUNT_MAX; + QString logFilePath = QDir::homePath() + LOG_FILE_PATH + "/" + QStringLiteral("%1_%2.log").arg(LOG_FILE_PREFIX).arg(logFileIndex); + if (!QFile::exists(logFilePath)) + break; + QFile temp(logFilePath); + if (temp.size() < LOG_FILE_SIZE_MAX) + break; + if ((LOG_FILE_COUNT_MAX - 1) == i) { + // here is the next log file. + logFileIndex = (logFileIndex + 1) % LOG_FILE_COUNT_MAX; + QString nextLogFilePath = QDir::homePath() + LOG_FILE_PATH + "/" + QStringLiteral("%1_%2.log").arg(LOG_FILE_PREFIX).arg(logFileIndex); + + // prepare rotate to next log file, so we should log some info in prev log file to make a better understanding. + FILE* tailFile = fopen(logFilePath.toLocal8Bit().constData(), "a+"); + if (tailFile) { + fprintf(tailFile, "to be continue, please check:\r\n\t[%s]", nextLogFilePath.toStdString().c_str()); + fclose(tailFile); + } + + // and make next log file be clear + QFile nextLogFile(nextLogFilePath); + nextLogFile.remove(); + } + } + return logFileIndex; +} + +static void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + QByteArray localMsg = msg.toLocal8Bit(); + + QDateTime dateTime = QDateTime::currentDateTime(); + QString strFormat = "yyMMdd hh:mm:ss.zzz"; + QString strDateTime = dateTime.toString(strFormat); + + FILE *log_file = nullptr; + QString logFilePath; + int fileSize; + static int currentLogFileIndex = -1; + QDir dir; + bool flag = 0; + + logFilePath = QDir::homePath() + LOG_FILE_PATH; + if (dir.mkpath(logFilePath)) { + flag = 1; + } + if (flag) { + if (-1 == currentLogFileIndex) + currentLogFileIndex = checkLogFile(currentLogFileIndex); + logFilePath = logFilePath + "/" + QStringLiteral("%1_%2.log").arg(LOG_FILE_PREFIX).arg(currentLogFileIndex); + log_file = fopen(logFilePath.toLocal8Bit().constData(), "a+"); + } + + QString src = QString(context.file ? context.file : "").section('/', -1); + QRegularExpression re("(\\w([\\S]*?)[\\s]?)(\\()"); + QRegularExpressionMatch match = re.match(context.function); + QString func = match.hasMatch() ? match.captured(1) : ""; + QString exinfo = src.isEmpty() ? QString() : QStringLiteral("(%1:%2 %3)").arg(src).arg(context.line).arg(func); + switch (type) { + case QtDebugMsg: + if (!log_file) { + break; + } + fprintf(log_file, "Debug: %s: %s %s\n", strDateTime.toStdString().c_str(), localMsg.constData(), exinfo.toStdString().c_str()); + break; + case QtInfoMsg: + fprintf(log_file? log_file: stdout, "Info: %s: %s %s\n", strDateTime.toStdString().c_str(), localMsg.constData(), exinfo.toStdString().c_str()); + break; + case QtWarningMsg: + fprintf(log_file? log_file: stderr, "Warning: %s: %s %s\n", strDateTime.toStdString().c_str(), localMsg.constData(), exinfo.toStdString().c_str()); + break; + case QtCriticalMsg: + fprintf(log_file? log_file: stderr, "Critical: %s: %s %s\n", strDateTime.toStdString().c_str(), localMsg.constData(), exinfo.toStdString().c_str()); + break; + case QtFatalMsg: + fprintf(log_file? log_file: stderr, "Fatal: %s: %s %s\n", strDateTime.toStdString().c_str(), localMsg.constData(), exinfo.toStdString().c_str()); + break; + } + + if (log_file) { + fileSize = ftell(log_file); + fclose(log_file); + if (fileSize >= LOG_FILE_SIZE_MAX) + currentLogFileIndex = checkLogFile(currentLogFileIndex); + } +} + int main(int argc, char * argv[]) { + qDebug() << "==================kdemain in before message handler==========================="; + qInstallMessageHandler(messageOutput); + qDebug() << "==================kdemain in==========================="; + KWin::disablePtrace(); KWin::Application::setupMalloc(); KWin::Application::setupLocalizedString(); @@ -391,6 +500,27 @@ int main(int argc, char * argv[]) // reset QT_QPA_PLATFORM so we don't propagate it to our children (e.g. apps launched from the overview effect) qunsetenv("QT_QPA_PLATFORM"); +#if !defined(QT_NO_DEBUG) + QStringList newLibPaths(a.applicationDirPath()); + const QStringList oldLibPaths = QCoreApplication::libraryPaths(); + if (newLibPaths.contains("/usr/local/bin")) { + // when do “make install”, we need add plugins directory before normal qt plugins directory. + // we can get arch lib directory name from qt plugins directory and do a replace + // from "/usr/lib/x86_64-linux-gnu/qt5/plugins" to "/usr/local/lib/x86_64-linux-gnu/plugins" + // then kwin will load the right plugins. + QRegularExpression re("^/usr/lib/(.*)/qt.*$"); + QStringList plugins = oldLibPaths.filter(QRegularExpression("^/usr/lib/")) + .replaceInStrings(re, "/usr/local/lib/\\1/plugins"); + newLibPaths.append(plugins); + } + newLibPaths.append(oldLibPaths); + QCoreApplication::setLibraryPaths(newLibPaths); + qDebug() << "debug mode:"; + qDebug() << " set application dir path to be the first of libraryPaths,"; + qDebug() << " so we can be happy to debug kwin plugins."; + qDebug() << " libraryPaths:" << newLibPaths; +#endif + KWin::Application::createAboutData(); KQuickAddons::QtQuickSettings::init(); diff --git a/src/main_x11.cpp b/src/main_x11.cpp index 03535831f..f92345164 100644 --- a/src/main_x11.cpp +++ b/src/main_x11.cpp @@ -36,6 +36,8 @@ #include #include #include +#include +#include // system #ifdef HAVE_UNISTD_H @@ -43,6 +45,15 @@ #endif // HAVE_UNISTD_H #include +#if defined(QT_NO_DEBUG) +#define LOG_FILE_COUNT_MAX 2 +#else +#define LOG_FILE_COUNT_MAX 4 +#endif +#define LOG_FILE_SIZE_MAX 20*1024*1024 +#define LOG_FILE_PREFIX "ukui_kwin" +#define LOG_FILE_PATH "/.log/" + Q_LOGGING_CATEGORY(KWIN_CORE, "kwin_core", QtWarningMsg) namespace KWin @@ -334,8 +345,105 @@ void ApplicationX11::crashHandler(int signal) } // namespace +/* +* find next log file, and do some check. +*/ +static int checkLogFile(int index) { + int logFileIndex = index; + for (size_t i = 0; i < LOG_FILE_COUNT_MAX; i++) { + logFileIndex = (logFileIndex + 1) % LOG_FILE_COUNT_MAX; + QString logFilePath = QDir::homePath() + LOG_FILE_PATH + "/" + QStringLiteral("%1_%2.log").arg(LOG_FILE_PREFIX).arg(logFileIndex); + if (!QFile::exists(logFilePath)) + break; + QFile temp(logFilePath); + if (temp.size() < LOG_FILE_SIZE_MAX) + break; + if ((LOG_FILE_COUNT_MAX - 1) == i) { + // here is the next log file. + logFileIndex = (logFileIndex + 1) % LOG_FILE_COUNT_MAX; + QString nextLogFilePath = QDir::homePath() + LOG_FILE_PATH + "/" + QStringLiteral("%1_%2.log").arg(LOG_FILE_PREFIX).arg(logFileIndex); + + // prepare rotate to next log file, so we should log some info in prev log file to make a better understanding. + FILE* tailFile = fopen(logFilePath.toLocal8Bit().constData(), "a+"); + if (tailFile) { + fprintf(tailFile, "to be continue, please check:\r\n\t[%s]", nextLogFilePath.toStdString().c_str()); + fclose(tailFile); + } + + // and make next log file be clear + QFile nextLogFile(nextLogFilePath); + nextLogFile.remove(); + } + } + return logFileIndex; +} + +static void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + QByteArray localMsg = msg.toLocal8Bit(); + + QDateTime dateTime = QDateTime::currentDateTime(); + QString strFormat = "yyMMdd hh:mm:ss.zzz"; + QString strDateTime = dateTime.toString(strFormat); + + FILE *log_file = nullptr; + QString logFilePath; + int fileSize; + static int currentLogFileIndex = -1; + QDir dir; + bool flag = 0; + + logFilePath = QDir::homePath() + LOG_FILE_PATH; + if (dir.mkpath(logFilePath)) { + flag = 1; + } + if (flag) { + if (-1 == currentLogFileIndex) + currentLogFileIndex = checkLogFile(currentLogFileIndex); + logFilePath = logFilePath + "/" + QStringLiteral("%1_%2.log").arg(LOG_FILE_PREFIX).arg(currentLogFileIndex); + log_file = fopen(logFilePath.toLocal8Bit().constData(), "a+"); + } + + QString src = QString(context.file ? context.file : "").section('/', -1); + QRegularExpression re("(\\w([\\S]*?)[\\s]?)(\\()"); + QRegularExpressionMatch match = re.match(context.function); + QString func = match.hasMatch() ? match.captured(1) : ""; + QString exinfo = src.isEmpty() ? QString() : QStringLiteral("(%1:%2 %3)").arg(src).arg(context.line).arg(func); + switch (type) { + case QtDebugMsg: + if (!log_file) { + break; + } + fprintf(log_file, "Debug: %s: %s %s\n", strDateTime.toStdString().c_str(), localMsg.constData(), exinfo.toStdString().c_str()); + break; + case QtInfoMsg: + fprintf(log_file? log_file: stdout, "Info: %s: %s %s\n", strDateTime.toStdString().c_str(), localMsg.constData(), exinfo.toStdString().c_str()); + break; + case QtWarningMsg: + fprintf(log_file? log_file: stderr, "Warning: %s: %s %s\n", strDateTime.toStdString().c_str(), localMsg.constData(), exinfo.toStdString().c_str()); + break; + case QtCriticalMsg: + fprintf(log_file? log_file: stderr, "Critical: %s: %s %s\n", strDateTime.toStdString().c_str(), localMsg.constData(), exinfo.toStdString().c_str()); + break; + case QtFatalMsg: + fprintf(log_file? log_file: stderr, "Fatal: %s: %s %s\n", strDateTime.toStdString().c_str(), localMsg.constData(), exinfo.toStdString().c_str()); + break; + } + + if (log_file) { + fileSize = ftell(log_file); + fclose(log_file); + if (fileSize >= LOG_FILE_SIZE_MAX) + currentLogFileIndex = checkLogFile(currentLogFileIndex); + } +} + int main(int argc, char * argv[]) { + qDebug() << "==================kdemain in before message handler==========================="; + qInstallMessageHandler(messageOutput); + qDebug() << "==================kdemain in==========================="; + KWin::Application::setupMalloc(); KWin::Application::setupLocalizedString(); @@ -429,6 +537,27 @@ int main(int argc, char * argv[]) KWin::ApplicationX11 a(argc, argv); a.setupTranslator(); +#if !defined(QT_NO_DEBUG) + QStringList newLibPaths(a.applicationDirPath()); + const QStringList oldLibPaths = QCoreApplication::libraryPaths(); + if (newLibPaths.contains("/usr/local/bin")) { + // when do “make install”, we need add plugins directory before normal qt plugins directory. + // we can get arch lib directory name from qt plugins directory and do a replace + // from "/usr/lib/x86_64-linux-gnu/qt5/plugins" to "/usr/local/lib/x86_64-linux-gnu/plugins" + // then kwin will load the right plugins. + QRegularExpression re("^/usr/lib/(.*)/qt.*$"); + QStringList plugins = oldLibPaths.filter(QRegularExpression("^/usr/lib/")) + .replaceInStrings(re, "/usr/local/lib/\\1/plugins"); + newLibPaths.append(plugins); + } + newLibPaths.append(oldLibPaths); + QCoreApplication::setLibraryPaths(newLibPaths); + qDebug() << "debug mode:"; + qDebug() << " set application dir path to be the first of libraryPaths,"; + qDebug() << " so we can be happy to debug kwin plugins."; + qDebug() << " libraryPaths:" << newLibPaths; +#endif + KWin::Application::createAboutData(); KQuickAddons::QtQuickSettings::init(); diff --git a/src/placement.cpp b/src/placement.cpp index 3a7782938..4f37921f9 100644 --- a/src/placement.cpp +++ b/src/placement.cpp @@ -834,6 +834,10 @@ void Workspace::quickTileWindow(QuickTileMode mode) return; } + if(m_bTabletMode) { + return; + } + // If the user invokes two of these commands in a one second period, try to // combine them together to enable easy and intuitive corner tiling #define FLAG(name) QuickTileMode(QuickTileFlag::name) diff --git a/src/plugins/kdecorations/CMakeLists.txt b/src/plugins/kdecorations/CMakeLists.txt index 2bce237af..4e524152c 100644 --- a/src/plugins/kdecorations/CMakeLists.txt +++ b/src/plugins/kdecorations/CMakeLists.txt @@ -1,2 +1,3 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kwin_clients\") add_subdirectory(aurorae) +add_subdirectory(ukui) diff --git a/src/plugins/kdecorations/aurorae/src/aurorae.cpp b/src/plugins/kdecorations/aurorae/src/aurorae.cpp index 23426ae2c..11ccde6ed 100644 --- a/src/plugins/kdecorations/aurorae/src/aurorae.cpp +++ b/src/plugins/kdecorations/aurorae/src/aurorae.cpp @@ -105,7 +105,12 @@ void Helper::unref() } static const QString s_defaultTheme = QStringLiteral("kwin4_decoration_qml_plastik"); +#if defined(QT_NO_DEBUG) static const QString s_qmlPackageFolder = QStringLiteral(KWIN_NAME "/decorations/"); +#else +static const QString s_qmlPackageFolder = QCoreApplication::applicationDirPath() + QStringLiteral("/../../src/plugins/kdecorations/aurorae/themes/"); +#endif + /* * KDecoration2::BorderSize doesn't map to the indices used for the Aurorae SVG Button Sizes. * BorderSize defines None and NoSideBorder as index 0 and 1. These do not make sense for Button @@ -169,7 +174,15 @@ QQmlComponent *Helper::loadComponent(const QString &themeName) const KPluginMetaData &service = offers.first(); const QString pluginName = service.pluginId(); const QString scriptName = service.value(QStringLiteral("X-Plasma-MainScript")); +#if defined(QT_NO_DEBUG) const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, s_qmlPackageFolder + pluginName + QLatin1String("/contents/") + scriptName); +#else + QString qmlName = pluginName; + QString file = s_qmlPackageFolder + qmlName.remove("kwin4_decoration_qml_") + QLatin1String("/package/contents/") + scriptName; + if (!QFile::exists(file)) + file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, s_qmlPackageFolder + pluginName + QLatin1String("/contents/") + scriptName); + qCDebug(AURORAE) << "Try to load qml file:" << file; +#endif if (file.isNull()) { qCDebug(AURORAE) << "Could not find script file for " << pluginName; // TODO: what to do in error case? diff --git a/src/plugins/kdecorations/ukui/CMakeLists.txt b/src/plugins/kdecorations/ukui/CMakeLists.txt new file mode 100644 index 000000000..2a786ecb7 --- /dev/null +++ b/src/plugins/kdecorations/ukui/CMakeLists.txt @@ -0,0 +1,63 @@ +cmake_minimum_required(VERSION 3.5) + +message("${PLUGIN_INSTALL_DIR}") +message("${QT_INSTALL_PLUGINS}") + +add_definitions(-DCMAKE_INSTALL_PREFIX="/usr") + +project(kwin-style-ukui LANGUAGES CXX) + +find_package(ECM 0.0.9 REQUIRED NO_MODULE) +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) + +include(ECMFindModuleHelpers) +include(ECMInstallIcons) +include(KDEInstallDirs) +include(KDECMakeSettings) +#include(KDECompilerSettings NO_POLICY_SCOPE) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt5 COMPONENTS Gui LinguistTools DBus X11Extras REQUIRED) + +#set(TS_FILES kwin-style-ukui_zh_CN.ts) + +add_library(kwin-style-ukui SHARED + ukui-decoration.cpp + ukui-decoration.h + button.cpp + button.h + shadow-helper.cpp + shadow-helper.h + xatom-helper.cpp + xatom-helper.h + kwin-style-ukui.json + icon.qrc + #${TS_FILES} +) +set_target_properties(kwin-style-ukui PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/org.kde.kdecoration2/") + + +add_executable(test-csd test/test-csd.cpp xatom-helper.cpp xatom-helper.h) + +find_package(KDecoration2) +find_package(KF5CoreAddons) +find_package(KF5WindowSystem) +find_package(KF5GuiAddons) +find_package(XCB) +target_link_libraries(kwin-style-ukui PUBLIC KDecoration2::KDecoration KF5::ConfigWidgets KF5::CoreAddons KF5::WindowSystem PRIVATE Qt5::Gui Qt5::DBus Qt5::X11Extras KF5::GuiAddons kwineffects XCB::XCB XCB::UTIL -lX11) +target_link_libraries(test-csd PUBLIC KDecoration2::KDecoration KF5::CoreAddons KF5::WindowSystem PRIVATE Qt5::Gui Qt5::DBus Qt5::X11Extras KF5::GuiAddons XCB::XCB XCB::UTIL -lX11) + +#target_compile_definitions(kwin-style-ukui PUBLIC KWINSTYLEUKUI_LIBRARY) + +#qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) + +message("${PLUGIN_INSTALL_DIR}") +message("${QT_INSTALL_PLUGINS}") + +install(TARGETS kwin-style-ukui DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kdecoration2) diff --git a/src/plugins/kdecorations/ukui/LICENSE b/src/plugins/kdecorations/ukui/LICENSE new file mode 100644 index 000000000..0a041280b --- /dev/null +++ b/src/plugins/kdecorations/ukui/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/src/plugins/kdecorations/ukui/README.md b/src/plugins/kdecorations/ukui/README.md new file mode 100644 index 000000000..abad28b1d --- /dev/null +++ b/src/plugins/kdecorations/ukui/README.md @@ -0,0 +1,65 @@ +# kwin-style-ukui -- UKUI KWin Decoration Project + +## Introduction & Background +KDE is a well-known desktop environment in the worldwide. Its window manager - KWin is also an amayzing project, which providing many cool effects, and allowding us to extend it with plugins or scripts. + +The ukui-kwin-style is based on the KDecoration2, which is an extensible frameworks for KWin to custom a window decoration style, such as title bar, border and shadow. The projects similar with this one are kwin-style-breeze and kwin-decoration-oxygen, etc... Breeze and Oxygen are also providing the applications style based on Qt style frameworks, infuenting most Qt applications. + +This project has 2 main targets: +1. providing the decorations for normal windows, and keep the decorations suiting for ukui styles. +2. providing the decorations for special windows (a client side decoration window), such as peony, ukui-control-center, decorating special windows with shadow and a resize handler. + +The difference between normal windows and special windows (csd windows) in X11 is window's motif hints atom, which is a protocol of NETWM. However, there are many arguments about client side decoration, so breeze and oxygen choose to ignore some flags of motif hints (actually not handled, motif hints only handled at kwin self), which related csd. + +As a developer, I'd like to stand by ssd (sever side decoration) side, but I have to meet the needs of UI designers. I have tried many ways to archives those goals, and as rebasing the rules as possible. I am still looking for the best solution, and this project may just be a step on the way. Actually I don't think I have found a good solution for resolve the arguments on csd, and I gusse it will continue a loog while. + +## HOW it works? +In X Window System, there are many protocol between X client (a window) and X sever. Window Manager take over the works of X sever in current PC (In x11 platform), and handle these protocols. + +For now, **ICCCM** and **NETWM** is the common protocols in different X11 desktop environment. One protocol of NETWM -- **motif hints** is a protocol to control the window decoration and supported actions provided by window manager. + +Throughing motif hints, window can define a rules telling window manager how to decorate it, and enable/disable window interaction actions. + +KWin's decoration frameworks, KDecoration2, provides us the convinience access between window and window manger. It let us just need consider how to render decoration for a window. For a decoration to be painted, it can get the window handle from client() which provided by the frameworks. So, we can check the window motif hints durring rendering it. + +For a csd window, the windows motif hints should be different with the normal one, this is mostly handled by gui libraries, such as Qt and GTK. In X11 platform, Qt's frameless window and GTK's csd window both change the motif hints of the window, but their hints are different, that cause a csd window seems better than frameless one. For example, csd window can resize without any hacking, and also has shadow rounded. + +The task is taked over by ourself. Implement a KDecoration2 plugin, and use it to handle the different window (note that some window will not handled in plugin, such as frameless window, and gtk csd window, it handled by kwin itself). + +If you want to learn more about kwin, the [**offical document**](https://community.kde.org/KWin) is recommend. + +## Build and Test +- Dependencies (in debian): + - cmake + - extra-cmake-modules + - libkdecorations2-dev + - libqt5x11extras5-dev + - libkf5coreaddons-dev + - libkf5windowsystem-dev + +- Build and Install + - clone this project and enter into top directory + - mkdir build && cd build + - cmake .. + - make + - sudo make install + - ./test-csd (if use kwin-style-ukui) + +- Test + - open KDE System Settings (systemsettings5) + - Apperance > Application Style > Window Decoration + - choose UKUI as decoration and apply + - run a demo, which windows' motif hints is different. + +## ScreenShot +![pictrue1](screenshots/kwin-style-ukui-deco-with-different-motif-hints.png) + +![pictrue2](screenshots/csd-window.png) + +![pictrue3](screenshots/window-screenshot-of-csd-window.png) + +## TODO +- Basic decoration rendering. Not only be a demo. +- Support unity border radius protocol, and handle the rounded-corner window's shadow. +- Register an atom to _NET_SUPPORTED. Let window know if kwin-ukui-style used. +- An implement in wayland? \ No newline at end of file diff --git a/src/plugins/kdecorations/ukui/button.cpp b/src/plugins/kdecorations/ukui/button.cpp new file mode 100644 index 000000000..71248fe17 --- /dev/null +++ b/src/plugins/kdecorations/ukui/button.cpp @@ -0,0 +1,190 @@ +/* + * KWin Style UKUI + * + * Copyright (C) 2020, KylinSoft Co., Ltd. + * + * 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 + * (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. If not, see . + * + * Authors: Yue Lan + * + */ + +#include "button.h" +#include +#include +#include "xatom-helper.h" +#include + +using namespace UKUI; + +Button *Button::create(KDecoration2::DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent) +{ + auto d = static_cast(decoration); + auto b = new Button(type, d, parent); + switch (type) { + case KDecoration2::DecorationButtonType::Menu: + QObject::connect(d->client().data(), &KDecoration2::DecoratedClient::iconChanged, b, [b]() {b->update();}); + break; + + case KDecoration2::DecorationButtonType::OnAllDesktops: + b->setVisible(false); + break; + + case KDecoration2::DecorationButtonType::Minimize: + { + bool bRet = XAtomHelper::getInstance()->checkButtonAvailable(d->client().data()->windowId(), KDecoration2::DecorationButtonType::Minimize); + if(false == bRet) + { + b->setVisible(false); + } + //QObject::connect(d->client().data(), &KDecoration2::DecoratedClient::minimizeableChanged, b, &Button::setVisible ); + break; + } + + case KDecoration2::DecorationButtonType::Maximize: + { + bool bRet = XAtomHelper::getInstance()->checkButtonAvailable(d->client().data()->windowId(), KDecoration2::DecorationButtonType::Maximize); + if(false == bRet) + { + b->setVisible(false); + } + //QObject::connect(d->client().data(), &KDecoration2::DecoratedClient::maximizeableChanged, b, &Button::setVisible ); + break; + } + + case KDecoration2::DecorationButtonType::Close: + { + bool bRet = XAtomHelper::getInstance()->checkButtonAvailable(d->client().data()->windowId(), KDecoration2::DecorationButtonType::Close); + if(false == bRet) + { + b->setVisible(false); + } + break; + } + + case KDecoration2::DecorationButtonType::ContextHelp: + b->setVisible(false); + + default: + break; + } + + return b; +} + +Button::Button(QObject *parent, const QVariantList &args) : KDecoration2::DecorationButton(args.at(0).value(), args.at(1).value(), parent) +{ + +} + +Button::Button(KDecoration2::DecorationButtonType type, UKUI::Decoration *decoration, QObject *parent) : KDecoration2::DecorationButton(type, decoration, parent) +{ + +} + +Button::~Button() +{ + +} + +void Button::paint(QPainter *painter, const QRect &repaintRegion) +{ + if(false == isVisible()) + { + return; + } + + if(!decoration()) + { + return; + } + + auto d = qobject_cast(decoration()); + + //menu button + if(type() == KDecoration2::DecorationButtonType::Menu) + { + const QRectF iconRect(geometry().topLeft(), geometry().size().toSize()); + decoration()->client().data()->icon().paint(painter, iconRect.toRect()); + return; + } + + QString strPath; + QString strIconTheme; + QString strIconType; + QString strIconState; + QString strIconStyle; + + // handle icon theme + UKUI::ThemeProject themProject = d->getThemeProject(); + switch(themProject) { + case UKUI::THEME_PROJECT_UKUI_BASE: + strIconTheme = "ukui-base"; + break; + case UKUI::THEME_PROJECT_LAIKA: + strIconTheme = "laika"; + break; + case UKUI::THEME_PROJECT_MAVIS: + strIconTheme = "mavis"; + break; + default: + break; + } + + // handle icon state + if (isPressed()) { + strIconState = "clicked"; + } else if (isHovered()) { + strIconState = "hover"; + } else { + strIconState = "common"; + } + + // handle icon style + if (0 == d->themeId()) { + strIconStyle = "black"; + } else { + strIconStyle = "white"; + } + + // handle icon type + switch (type()) { + case KDecoration2::DecorationButtonType::Minimize: + strIconType = "minimize"; + break; + case KDecoration2::DecorationButtonType::Maximize: + strIconType = isChecked() ? "restore" : "maximize"; + break; + case KDecoration2::DecorationButtonType::Close: + strIconType = "close"; + break; + default: + return; + break; + } + + // construct icon path + strPath = QStringLiteral(":/icon/%1/%2-%3-%4.svg").arg(strIconTheme).arg(strIconState).arg(strIconType).arg(strIconStyle); + QFile ddd(strPath); + if (!ddd.exists()) + strPath = QStringLiteral(":/icon/%1/%2-%3.svg").arg(strIconTheme).arg(strIconState).arg(strIconType); + + const QRectF iconRect(geometry().topLeft(), geometry().size().toSize()); + QIcon icon(strPath); + icon.paint(painter, iconRect.toRect()); + + return; +} + + diff --git a/src/plugins/kdecorations/ukui/button.h b/src/plugins/kdecorations/ukui/button.h new file mode 100644 index 000000000..96b1c64b4 --- /dev/null +++ b/src/plugins/kdecorations/ukui/button.h @@ -0,0 +1,50 @@ +/* + * KWin Style UKUI + * + * Copyright (C) 2020, KylinSoft Co., Ltd. + * + * 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 + * (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. If not, see . + * + * Authors: Yue Lan + * + */ + +#ifndef BUTTON_H +#define BUTTON_H + +#include + +#include "ukui-decoration.h" +#include + +namespace UKUI { + + class Button : public KDecoration2::DecorationButton + { + Q_OBJECT + public: + explicit Button(QObject *parent = nullptr, const QVariantList &args = QVariantList()); + explicit Button(KDecoration2::DecorationButtonType type, UKUI::Decoration *decoration, QObject *parent); + + ~Button(); + + //* button creation + static Button *create(KDecoration2::DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent); + + //* render + void paint(QPainter *painter, const QRect &repaintRegion) override; + }; +} + +#endif // BUTTON_H diff --git a/src/plugins/kdecorations/ukui/icon.qrc b/src/plugins/kdecorations/ukui/icon.qrc new file mode 100644 index 000000000..6bce2c102 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon.qrc @@ -0,0 +1,45 @@ + + + icon/ukui-base/clicked-close.svg + icon/ukui-base/common-close-black.svg + icon/ukui-base/common-close-white.svg + icon/ukui-base/common-maximize-black.svg + icon/ukui-base/common-maximize-white.svg + icon/ukui-base/common-minimize-black.svg + icon/ukui-base/common-minimize-white.svg + icon/ukui-base/common-restore-black.svg + icon/ukui-base/common-restore-white.svg + icon/ukui-base/hover-close.svg + icon/ukui-base/clicked-maximize-black.svg + icon/ukui-base/clicked-maximize-white.svg + icon/ukui-base/clicked-minimize-black.svg + icon/ukui-base/clicked-minimize-white.svg + icon/ukui-base/clicked-restore-black.svg + icon/ukui-base/clicked-restore-white.svg + icon/ukui-base/hover-maximize-black.svg + icon/ukui-base/hover-maximize-white.svg + icon/ukui-base/hover-minimize-black.svg + icon/ukui-base/hover-minimize-white.svg + icon/ukui-base/hover-restore-black.svg + icon/ukui-base/hover-restore-white.svg + + + + icon/laika/clicked-close.svg + icon/laika/clicked-maximize.svg + icon/laika/clicked-minimize.svg + icon/laika/clicked-restore.svg + icon/laika/common-close-black.svg + icon/laika/common-close-white.svg + icon/laika/common-maximize-black.svg + icon/laika/common-maximize-white.svg + icon/laika/common-minimize-black.svg + icon/laika/common-minimize-white.svg + icon/laika/common-restore-black.svg + icon/laika/common-restore-white.svg + icon/laika/hover-close.svg + icon/laika/hover-maximize.svg + icon/laika/hover-minimize.svg + icon/laika/hover-restore.svg + + diff --git a/src/plugins/kdecorations/ukui/icon/laika/clicked-close.svg b/src/plugins/kdecorations/ukui/icon/laika/clicked-close.svg new file mode 100644 index 000000000..4c0886edf --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/laika/clicked-close.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/laika/clicked-maximize.svg b/src/plugins/kdecorations/ukui/icon/laika/clicked-maximize.svg new file mode 100644 index 000000000..59afd3876 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/laika/clicked-maximize.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/laika/clicked-minimize.svg b/src/plugins/kdecorations/ukui/icon/laika/clicked-minimize.svg new file mode 100644 index 000000000..cacdef1c1 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/laika/clicked-minimize.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/laika/clicked-restore.svg b/src/plugins/kdecorations/ukui/icon/laika/clicked-restore.svg new file mode 100644 index 000000000..bc89f5fc1 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/laika/clicked-restore.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/laika/common-close-black.svg b/src/plugins/kdecorations/ukui/icon/laika/common-close-black.svg new file mode 100644 index 000000000..3c6fbb069 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/laika/common-close-black.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/laika/common-close-white.svg b/src/plugins/kdecorations/ukui/icon/laika/common-close-white.svg new file mode 100644 index 000000000..6cfe76762 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/laika/common-close-white.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/laika/common-maximize-black.svg b/src/plugins/kdecorations/ukui/icon/laika/common-maximize-black.svg new file mode 100644 index 000000000..3ff760b18 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/laika/common-maximize-black.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/laika/common-maximize-white.svg b/src/plugins/kdecorations/ukui/icon/laika/common-maximize-white.svg new file mode 100644 index 000000000..101aa0ab9 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/laika/common-maximize-white.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/laika/common-minimize-black.svg b/src/plugins/kdecorations/ukui/icon/laika/common-minimize-black.svg new file mode 100644 index 000000000..8d9caa19a --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/laika/common-minimize-black.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/laika/common-minimize-white.svg b/src/plugins/kdecorations/ukui/icon/laika/common-minimize-white.svg new file mode 100644 index 000000000..6a6c1e68d --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/laika/common-minimize-white.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/laika/common-restore-black.svg b/src/plugins/kdecorations/ukui/icon/laika/common-restore-black.svg new file mode 100644 index 000000000..442c28790 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/laika/common-restore-black.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/laika/common-restore-white.svg b/src/plugins/kdecorations/ukui/icon/laika/common-restore-white.svg new file mode 100644 index 000000000..3b3439c17 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/laika/common-restore-white.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/laika/hover-close.svg b/src/plugins/kdecorations/ukui/icon/laika/hover-close.svg new file mode 100644 index 000000000..f1853c670 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/laika/hover-close.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/laika/hover-maximize.svg b/src/plugins/kdecorations/ukui/icon/laika/hover-maximize.svg new file mode 100644 index 000000000..2967b3391 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/laika/hover-maximize.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/laika/hover-minimize.svg b/src/plugins/kdecorations/ukui/icon/laika/hover-minimize.svg new file mode 100644 index 000000000..918484ada --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/laika/hover-minimize.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/laika/hover-restore.svg b/src/plugins/kdecorations/ukui/icon/laika/hover-restore.svg new file mode 100644 index 000000000..948397a66 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/laika/hover-restore.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-close.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-close.svg new file mode 100644 index 000000000..b840a382a --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-close.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-maximize-black.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-maximize-black.svg new file mode 100644 index 000000000..21483e65c --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-maximize-black.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-maximize-white.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-maximize-white.svg new file mode 100644 index 000000000..0ef420b82 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-maximize-white.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-minimize-black.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-minimize-black.svg new file mode 100644 index 000000000..3336f0dca --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-minimize-black.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-minimize-white.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-minimize-white.svg new file mode 100644 index 000000000..8b561cd66 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-minimize-white.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-restore-black.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-restore-black.svg new file mode 100644 index 000000000..45a3fb506 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-restore-black.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-restore-white.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-restore-white.svg new file mode 100644 index 000000000..15c8eea31 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/clicked-restore-white.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/common-close-black.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/common-close-black.svg new file mode 100644 index 000000000..03009168e --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/common-close-black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/common-close-white.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/common-close-white.svg new file mode 100644 index 000000000..bda8589bd --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/common-close-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/common-maximize-black.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/common-maximize-black.svg new file mode 100644 index 000000000..9e20866c0 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/common-maximize-black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/common-maximize-white.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/common-maximize-white.svg new file mode 100644 index 000000000..930bfcede --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/common-maximize-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/common-minimize-black.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/common-minimize-black.svg new file mode 100644 index 000000000..563a11b80 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/common-minimize-black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/common-minimize-white.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/common-minimize-white.svg new file mode 100644 index 000000000..af446dac3 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/common-minimize-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/common-restore-black.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/common-restore-black.svg new file mode 100644 index 000000000..c4d636a72 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/common-restore-black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/common-restore-white.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/common-restore-white.svg new file mode 100644 index 000000000..a608479b1 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/common-restore-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/hover-close.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/hover-close.svg new file mode 100644 index 000000000..4ef746633 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/hover-close.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/hover-maximize-black.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/hover-maximize-black.svg new file mode 100644 index 000000000..c0dba89a3 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/hover-maximize-black.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/hover-maximize-white.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/hover-maximize-white.svg new file mode 100644 index 000000000..4dd02a479 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/hover-maximize-white.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/hover-minimize-black.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/hover-minimize-black.svg new file mode 100644 index 000000000..2299a3f8a --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/hover-minimize-black.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/hover-minimize-white.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/hover-minimize-white.svg new file mode 100644 index 000000000..e3c6e3465 --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/hover-minimize-white.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/hover-restore-black.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/hover-restore-black.svg new file mode 100644 index 000000000..684d2d1ff --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/hover-restore-black.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/icon/ukui-base/hover-restore-white.svg b/src/plugins/kdecorations/ukui/icon/ukui-base/hover-restore-white.svg new file mode 100644 index 000000000..1192754ff --- /dev/null +++ b/src/plugins/kdecorations/ukui/icon/ukui-base/hover-restore-white.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/src/plugins/kdecorations/ukui/kwin-style-ukui.json b/src/plugins/kdecorations/ukui/kwin-style-ukui.json new file mode 100644 index 000000000..a505bbc6b --- /dev/null +++ b/src/plugins/kdecorations/ukui/kwin-style-ukui.json @@ -0,0 +1,12 @@ +{ + "KPlugin": { + "Id": "UKUI", + "ServiceTypes": [ + "org.kde.kdecoration2" + ] + }, + "org.kde.kdecoration2": { + "blur": false, + "kcmodule": false + } +} diff --git a/src/plugins/kdecorations/ukui/shadow-helper.cpp b/src/plugins/kdecorations/ukui/shadow-helper.cpp new file mode 100644 index 000000000..693fb4e35 --- /dev/null +++ b/src/plugins/kdecorations/ukui/shadow-helper.cpp @@ -0,0 +1,316 @@ +/* + * KWin Style UKUI + * + * Copyright (C) 2020, KylinSoft Co., Ltd. + * + * 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 + * (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. If not, see . + * + * Authors: Yue Lan + * + */ + +#include "shadow-helper.h" + +#include +#include +#include +#include + +#include + +#define INNERRECT_WIDTH 1 +#define BORDERCOLOR_ALPHAF 0.15 + +extern void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed); + +using namespace UKUI; + +static ShadowHelper *global_instance = nullptr; + +ShadowHelper *ShadowHelper::globalInstance() +{ + if (!global_instance) + global_instance = new ShadowHelper; + return global_instance; +} + +void ShadowHelper::releaseShadows() +{ + for (auto shadow : m_shadowsCache) { + shadow.clear(); + } + m_shadowsCache.clear(); +} + +QSharedPointer ShadowHelper::getShadow(const ShadowIndex &index) +{ + int borderRadiusTopLeft = index.topLeft(); + int borderRadiusTopRight = index.topRight(); + int borderRadiusBottomLeft = index.bottomLeft(); + int borderRadiusBottomRight = index.bottomRight(); + qreal darkness = index.darkness(); + qreal shadow_border = index.borderWidth(); + + if (borderRadiusTopLeft < 1) { + borderRadiusTopLeft = 1; + } + if (borderRadiusTopRight < 1) { + borderRadiusTopRight = 1; + } + if (borderRadiusBottomLeft < 1) { + borderRadiusBottomLeft = 1; + } + if (borderRadiusBottomRight < 1) { + borderRadiusBottomRight = 1; + } + + auto shadow = m_shadowsCache.value(index); + if (!shadow.isNull()) + { + shadow.clear(); + this->releaseShadows(); + m_shadowsCache.remove(index); + } + + shadow = QSharedPointer::create(); + auto pix = this->getShadowPixmap(index.color(), shadow_border, darkness, borderRadiusTopLeft, borderRadiusTopRight, borderRadiusBottomLeft, borderRadiusBottomRight); + auto img = pix.toImage(); + shadow->setShadow(img); + int maxTopRadius = qMax(borderRadiusTopLeft, borderRadiusTopRight); + int maxBottomRadius = qMax(borderRadiusBottomLeft, borderRadiusBottomRight); + int maxRadius = qMax(maxTopRadius, maxBottomRadius); + maxRadius = qMax(12, maxRadius); + QRect innerRect = QRect(shadow_border + maxRadius, shadow_border + maxRadius, INNERRECT_WIDTH, INNERRECT_WIDTH); + shadow->setInnerShadowRect(innerRect); + shadow->setPadding(QMargins(shadow_border, shadow_border, shadow_border, shadow_border)); + + m_shadowsCache.insert(index, shadow); + return shadow; +} + +ShadowHelper::ShadowHelper() +{ + +} + +QPixmap ShadowHelper::getShadowPixmap(const QColor &color, int shadow_border, qreal darkness, int borderRadiusTopLeft, int borderRadiusTopRight, int borderRadiusBottomLeft, int borderRadiusBottomRight) +{ + int maxTopRadius = qMax(borderRadiusTopLeft, borderRadiusTopRight); + int maxBottomRadius = qMax(borderRadiusBottomLeft, borderRadiusBottomRight); + int maxRadius = qMax(maxTopRadius, maxBottomRadius); + maxRadius = qMax(12, maxRadius); + QPixmap pix(QSize(2 * maxRadius + 2 * shadow_border + INNERRECT_WIDTH, 2 * maxRadius + 2 * shadow_border + INNERRECT_WIDTH)); + pix.fill(Qt::transparent); + + int squareWidth = 2 * maxRadius + INNERRECT_WIDTH; + + QPainterPath windowRelativePath; + windowRelativePath.setFillRule(Qt::WindingFill); + QPoint currentPos; + + // move to top left arc start point + windowRelativePath.moveTo(borderRadiusTopLeft, 0); + // top left arc + auto topLeftBorderRadiusRect = QRect(0, 0, 2 * borderRadiusTopLeft, 2 * borderRadiusTopLeft); + windowRelativePath.arcTo(topLeftBorderRadiusRect, 90, 90); + // move to bottom left arc start point + currentPos = QPoint(0, maxRadius + INNERRECT_WIDTH + maxRadius - borderRadiusBottomLeft); + //windowRelativePath.moveTo(currentPos); + // bottom left arc + auto bottomLeftRect = QRect(0, currentPos.y() - borderRadiusBottomLeft, 2 * borderRadiusBottomLeft, 2 * borderRadiusBottomLeft); + windowRelativePath.arcTo(bottomLeftRect, 180, 90); + // move to bottom right arc start point + currentPos = QPoint(2 * maxRadius + INNERRECT_WIDTH - borderRadiusBottomRight, 2 * maxRadius + INNERRECT_WIDTH); + //windowRelativePath.moveTo(currentPos); + // bottom right arc + auto bottomRightRect = QRect(currentPos.x() - borderRadiusBottomRight, currentPos.y() - 2 * borderRadiusBottomRight, 2 * borderRadiusBottomRight, 2 * borderRadiusBottomRight); + windowRelativePath.arcTo(bottomRightRect, 270, 90); + // move to top right arc start point + currentPos = QPoint(2 * maxRadius + INNERRECT_WIDTH, borderRadiusTopRight); + //windowRelativePath.moveTo(currentPos); + // top right arc + auto topRightRect = QRect(squareWidth - 2 * borderRadiusTopRight, 0, 2 * borderRadiusTopRight, 2 * borderRadiusTopRight); + windowRelativePath.arcTo(topRightRect, 0, 90); + + QPainter painter(&pix); + painter.save(); + painter.translate(shadow_border, shadow_border); +// painter.fillPath(windowRelativePath, color); + painter.fillPath(windowRelativePath, QColor(0, 0, 0)); + painter.restore(); + + QImage rawImg = pix.toImage(); + qt_blurImage(rawImg, shadow_border, true, true); + + QPixmap target = QPixmap::fromImage(rawImg); + QPainter painter2(&target); + painter2.save(); + painter2.setRenderHint(QPainter::Antialiasing); + painter2.translate(shadow_border, shadow_border); + painter2.setCompositionMode(QPainter::CompositionMode_Clear); + painter2.fillPath(windowRelativePath, Qt::transparent); + painter2.restore(); + painter2.end(); + + // handle darkness + QImage newImg = target.toImage(); + for (int x = 0; x < newImg.width(); x++) { + for (int y = 0; y < newImg.height(); y++) { + auto color = newImg.pixelColor(x, y); + if (color.alpha() == 0) + continue; + color.setAlphaF(darkness * color.alphaF()); + newImg.setPixelColor(x, y, color); + } + } + + QPixmap darkerTarget = QPixmap::fromImage(newImg); + painter2.begin(&darkerTarget); + + auto borderPath = caculateRelativePainterPath(borderRadiusTopLeft + 0.5, borderRadiusTopRight + 0.5, borderRadiusBottomLeft + 0.5, borderRadiusBottomRight + 0.5); + painter2.setCompositionMode(QPainter::CompositionMode_DestinationOver); + painter2.setRenderHint(QPainter::HighQualityAntialiasing); + QColor borderColor = QColor(255 - color.red(), 255 - color.green(), 255 - color.blue()); + borderColor.setAlphaF(BORDERCOLOR_ALPHAF); + + painter2.setPen(QPen(borderColor, 1.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); + painter2.setBrush(Qt::NoBrush); + painter2.translate(shadow_border, shadow_border); + painter2.translate(-0.5, -0.5); + painter2.drawPath(borderPath); + + //封口 + borderPath.closeSubpath(); + painter2.drawPath(borderPath); + + return darkerTarget; +} + +QPainterPath ShadowHelper::caculateRelativePainterPath(qreal borderRadiusTopLeft, qreal borderRadiusTopRight, qreal borderRadiusBottomLeft, qreal borderRadiusBottomRight) +{ + qreal maxTopRadius = qMax(borderRadiusTopLeft, borderRadiusTopRight); + qreal maxBottomRadius = qMax(borderRadiusBottomLeft, borderRadiusBottomRight); + qreal maxRadius = qMax(maxTopRadius, maxBottomRadius); + maxRadius = qMax(12.0, maxRadius); + + qreal squareWidth = 2 * maxRadius + INNERRECT_WIDTH; + + QPainterPath windowRelativePath; + windowRelativePath.setFillRule(Qt::WindingFill); + QPointF currentPos; + + // move to top left arc start point + windowRelativePath.moveTo(borderRadiusTopLeft, 0); + // top left arc + auto topLeftBorderRadiusRect = QRectF(0, 0, 2 * borderRadiusTopLeft, 2 * borderRadiusTopLeft); + windowRelativePath.arcTo(topLeftBorderRadiusRect, 90, 90); + // move to bottom left arc start point + currentPos = QPointF(0, maxRadius + INNERRECT_WIDTH + maxRadius - borderRadiusBottomLeft); + //windowRelativePath.moveTo(currentPos); + // bottom left arc + auto bottomLeftRect = QRectF(0, currentPos.y() - borderRadiusBottomLeft, 2 * borderRadiusBottomLeft, 2 * borderRadiusBottomLeft); + windowRelativePath.arcTo(bottomLeftRect, 180, 90); + // move to bottom right arc start point + currentPos = QPointF(2 * maxRadius + INNERRECT_WIDTH - borderRadiusBottomRight, 2 * maxRadius + INNERRECT_WIDTH); + //windowRelativePath.moveTo(currentPos); + // bottom right arc + auto bottomRightRect = QRectF(currentPos.x() - borderRadiusBottomRight, currentPos.y() - 2 * borderRadiusBottomRight, 2 * borderRadiusBottomRight, 2 * borderRadiusBottomRight); + windowRelativePath.arcTo(bottomRightRect, 270, 90); + // move to top right arc start point + currentPos = QPointF(2 * maxRadius + INNERRECT_WIDTH, borderRadiusTopRight); + //windowRelativePath.moveTo(currentPos); + // top right arc + auto topRightRect = QRectF(squareWidth - 2 * borderRadiusTopRight, 0, 2 * borderRadiusTopRight, 2 * borderRadiusTopRight); + windowRelativePath.arcTo(topRightRect, 0, 90); + + return windowRelativePath; +} + +ShadowIndex::ShadowIndex(const QColor &color, int topLeft, int topRight, int bottomLeft, int bottomRight, qreal darkness, int borderWidth) +{ + m_color = color; + m_topLeft = topLeft; + m_topRight = topRight; + m_bottomLeft = bottomLeft; + m_bottomRight = bottomRight; + m_darkness = darkness; + m_borderWidth = borderWidth; +} + +bool ShadowIndex::operator ==(const ShadowIndex &index) +{ + if (m_color != index.m_color) { + return false; + } + if (m_topLeft != index.m_topLeft) { + return false; + } + if (m_topRight != index.m_topRight) { + return false; + } + if (m_bottomLeft != index.m_bottomLeft) { + return false; + } + if (m_bottomRight != index.m_bottomRight) { + return false; + } + if (m_darkness != index.m_darkness) { + return false; + } + if (m_borderWidth != index.m_borderWidth) { + return false; + } + return true; +} + +bool ShadowIndex::operator <(const ShadowIndex &index) const +{ + return m_topLeft < index.m_topLeft; +} + +QColor ShadowIndex::color() const +{ + return m_color; +} + + +int ShadowIndex::topLeft() const +{ + return m_topLeft; +} + +int ShadowIndex::topRight() const +{ + return m_topRight; +} + +int ShadowIndex::bottomLeft() const +{ + return m_bottomLeft; +} + +int ShadowIndex::bottomRight() const +{ + return m_bottomRight; +} + +qreal ShadowIndex::darkness() const +{ + return m_darkness; +} + +int ShadowIndex::borderWidth() const +{ + return m_borderWidth; +} diff --git a/src/plugins/kdecorations/ukui/shadow-helper.h b/src/plugins/kdecorations/ukui/shadow-helper.h new file mode 100644 index 000000000..a4eaa4b27 --- /dev/null +++ b/src/plugins/kdecorations/ukui/shadow-helper.h @@ -0,0 +1,97 @@ +/* + * KWin Style UKUI + * + * Copyright (C) 2020, KylinSoft Co., Ltd. + * + * 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 + * (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. If not, see . + * + * Authors: Yue Lan + * + */ + +#ifndef SHADOWHELPER_H +#define SHADOWHELPER_H + +#include +#include +#include +#include + +namespace UKUI { + +class Decoration; + +class ShadowIndex { +public: + ShadowIndex(const QColor &color, int topLeft, int topRight, int bottomLeft, int bottomRight, qreal darkness, int borderWidth); + bool operator ==(const ShadowIndex &index); + bool operator <(const ShadowIndex &index) const; + + QColor color() const; + int topLeft() const; + int topRight() const; + int bottomLeft() const; + int bottomRight() const; + qreal darkness() const; + int borderWidth() const; + +private: + QColor m_color; + int m_topLeft = -1; + int m_topRight = -1; + int m_bottomLeft = -1; + int m_bottomRight = -1; + qreal m_darkness = -1; + int m_borderWidth = -1; +}; + +class ShadowHelper +{ + friend class Decoration; +public: + enum State { + Active, + Inactive, + }; + + static ShadowHelper *globalInstance(); + + void releaseShadows(); + + QSharedPointer getShadow(const ShadowIndex &index); + +private: + ShadowHelper(); + QPixmap getShadowPixmap(const QColor &color, + int shadow_border, + qreal darkness, + int borderRadiusTopLeft = 0, + int borderRadiusTopRight = 0, + int borderRadiusBottomLeft = 0, + int borderRadiusBottomRight = 0); + + QMap, QSharedPointer> m_inactiveShadowsCache; + QMap, QSharedPointer> m_activeShadowsCache; + + QMap> m_shadowsCache; + + QPainterPath caculateRelativePainterPath(qreal borderRadiusTopLeft = 0, + qreal borderRadiusTopRight = 0, + qreal borderRadiusBottomLeft = 0, + qreal borderRadiusBottomRight = 0); +}; + +} + +#endif // SHADOWHELPER_H diff --git a/src/plugins/kdecorations/ukui/test/test-csd.cpp b/src/plugins/kdecorations/ukui/test/test-csd.cpp new file mode 100644 index 000000000..925dacb79 --- /dev/null +++ b/src/plugins/kdecorations/ukui/test/test-csd.cpp @@ -0,0 +1,106 @@ +/* + * KWin Style UKUI + * + * Copyright (C) 2020, KylinSoft Co., Ltd. + * + * 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 + * (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. If not, see . + * + * Authors: Yue Lan + * + */ + +#include + +#include +#include + +#include "xatom-helper.h" + +#include + +#include +#include +#include +class WindowEffect : public QGraphicsEffect +{ +public: + explicit WindowEffect(QWidget *parent = nullptr); + + void draw(QPainter *painter) override; +}; + + +WindowEffect::WindowEffect(QWidget *parent) : QGraphicsEffect(parent) +{ + parent->setAttribute(Qt::WA_TranslucentBackground); + //parent->setWindowFlag(Qt::FramelessWindowHint); + parent->setGraphicsEffect(this); +} + +void WindowEffect::draw(QPainter *painter) +{ + QPoint offset; + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + if (sourceIsPixmap()) { + // No point in drawing in device coordinates (pixmap will be scaled anyways). + const QPixmap pixmap = sourcePixmap(Qt::LogicalCoordinates, &offset); + painter->translate(offset); + QPainterPath path; + path.addRoundedRect(pixmap.rect(), 0, 0); + painter->setClipPath(path); + painter->fillRect(pixmap.rect(), qApp->palette().window()); + painter->drawPixmap(QPoint(), pixmap); + } else { + // Draw pixmap in device coordinates to avoid pixmap scaling; + const QPixmap pixmap = sourcePixmap(Qt::DeviceCoordinates, &offset); + painter->setWorldTransform(QTransform()); + painter->translate(offset); + QPainterPath path; + path.addRoundedRect(pixmap.rect(), 0, 0); + painter->setClipPath(path); + painter->fillRect(pixmap.rect(), qApp->palette().window()); + painter->drawPixmap(QPoint(), pixmap); + } + painter->restore(); +} + +int main(int argc, char *argv[]) { + QApplication a(argc, argv); + + QMainWindow w; + w.setProperty("doNotBlur", true); + + WindowEffect e(&w); + + MotifWmHints hints; + hints.flags = MWM_HINTS_FUNCTIONS|MWM_HINTS_DECORATIONS; + hints.functions = MWM_FUNC_ALL; + hints.decorations = MWM_DECOR_BORDER; + XAtomHelper::getInstance()->setWindowMotifHint(w.winId(), hints); + + UnityCorners corners; + corners.topLeft = 12; + corners.topRight = 12; + corners.bottomLeft = 12; + corners.bottomRight = 12; + // XAtomHelper::getInstance()->setWindowBorderRadius(w.winId(), corners); + // auto result = XAtomHelper::getInstance()->getWindowBorderRadius(w.winId()); + // qDebug()<setUKUIDecoraiontHint(w.winId(), true); + //w.setWindowFlag(Qt::FramelessWindowHint); + w.show(); + + return a.exec(); +} diff --git a/src/plugins/kdecorations/ukui/ukui-decoration.cpp b/src/plugins/kdecorations/ukui/ukui-decoration.cpp new file mode 100644 index 000000000..eb2550b10 --- /dev/null +++ b/src/plugins/kdecorations/ukui/ukui-decoration.cpp @@ -0,0 +1,549 @@ +/* + * KWin Style UKUI + * + * Copyright (C) 2020, KylinSoft Co., Ltd. + * + * 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 + * (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. If not, see . + * + * Authors: Yue Lan + * + */ + +#include + +#include "ukui-decoration.h" +#include "button.h" + +#include //必须放xatom-helper.h前面 +#include "shadow-helper.h" +#include "xatom-helper.h" + +#include +#include + +#include +#include +#include +#include + +#include +#include + + +#define FONT_SIZE 11 //字体大小 +#define CUSOR_BORDER 13 //边框伸展光标范围 +#define SHADOW_BORDER 30 //阴影边框大小:30小边框、100中边框、200大边框 +#define ACTIVE_DARKNESS 0.5 //阴影颜色深度:1.0深、1.5很深、2.0超深 +#define RADIUS 12 +#define SHADOWCOLOR_ALPHAF 0.16 + + +K_PLUGIN_FACTORY_WITH_JSON( + UKUIDecotionFactory, + "kwin-style-ukui.json", + registerPlugin(); +) + +using namespace UKUI; + +Decoration::Decoration(QObject *parent, const QVariantList &args) + : KDecoration2::Decoration(parent, args) +{ + int nDpi = 96; + m_themeId = 0; + m_themeProject = THEME_PROJECT_UKUI_BASE; + m_bTabletMode = false; + m_Font.setPointSize(FONT_SIZE); //setPointSize可以根据dpi自动调整,所以m_nFont不需要乘缩放系数,而setPixelSize会写死 + if (false == args.isEmpty()) + { + const auto map = args.first().toMap(); + auto it = map.constFind(QStringLiteral("dpi")); + if (it != map.constEnd()) { + nDpi = it.value().toInt(); + } + + it = map.constFind(QStringLiteral("themeId")); + if (it != map.constEnd()) { + m_themeId = it.value().toBool(); + } + + it = map.constFind(QStringLiteral("themeProject")); + if (it != map.constEnd()) { + m_themeProject = static_cast(it.value().toInt()); + } + + it = map.constFind(QStringLiteral("systemFontSize")); + if (it != map.constEnd()) { + m_Font.setPointSize(it.value().toInt()); + } + + it = map.constFind(QStringLiteral("systemFont")); + if (it != map.constEnd()) { + m_Font.setFamily(it.value().toString()); + } + + it = map.constFind(QStringLiteral("tabletMode")); + if (it != map.constEnd()) { + m_bTabletMode = it.value().toBool(); + } + } + int nScaler = qRound(nDpi / 96.0f); + m_borderLeft = nScaler * 0; + m_borderTop = nScaler * 38; + m_borderRight = nScaler * 0; + m_borderBottom = nScaler * 0; + + m_buttonWidth = nScaler * 30; + m_buttonHeight = nScaler * 30; + + m_leftButtonWidth = nScaler * 24; + m_leftButtonHeight = nScaler * 24; + + m_ButtonMarginTop = nScaler * 4; + + m_buttonSpacing = nScaler * 4; + + m_leftButtons = nullptr; + m_rightButtons = nullptr; +} + +void Decoration::init() +{ + m_shadowRadius = RADIUS; + + //从读取配置文件的方式,判断kwin是否能开启毛玻璃效果,如果不能,则阴影圆角为0. + auto config = KSharedConfig::openConfig("kwinrc"); + auto group = KConfigGroup(config, "Compositing"); + if (group.readEntry("Backend") == "XRender" || group.readEntry("OpenGLIsUnsafe") == "true") { + m_shadowRadius = 0; + } + if (XAtomHelper::getInstance()->isFrameLessWindow(this->client().data()->windowId())) { + m_shadowRadius = 0; + } + + XAtomHelper::getInstance()->setUKUIDecoraiontHint(client().data()->windowId(), true); + + QDBusConnection::sessionBus().connect(QString(), + QStringLiteral("/KGlobalSettings"), + QStringLiteral("org.kde.KGlobalSettings"), + QStringLiteral("slotThemeChange"), + this, SLOT(themeUpdate(int))); + themeUpdate(m_themeId); + + bool isDecoBorderOnly = XAtomHelper::getInstance()->isWindowDecorateBorderOnly(client().data()->windowId()); //是否是仅修饰边框 + if (!isDecoBorderOnly) { + //标题栏dbus监听PC-平板模式切换 + QDBusConnection::sessionBus().connect("com.kylin.statusmanager.interface", + "/", + "com.kylin.statusmanager.interface", + "mode_change_signal", + this, + SLOT(slotSwitchTablet(bool))); + + themeUpdate(m_themeId); + calculateBorders(); + //button + m_leftButtons = new KDecoration2::DecorationButtonGroup(KDecoration2::DecorationButtonGroup::Position::Left, this, &UKUI::Button::create); + m_leftButtons->setSpacing(m_buttonSpacing); + + m_nleftButtonCout = 0; + for (const QPointer& button : m_leftButtons->buttons()) + { + button.data()->setGeometry(QRectF(QPointF(0, 0), QSizeF(m_leftButtonWidth, m_leftButtonHeight))); + if(false == button.data()->isVisible()) + { + continue; + } + + if(KDecoration2::DecorationButtonType::Menu == button.data()->type()) + { + m_nleftButtonCout++; + } + } + + m_rightButtons = new KDecoration2::DecorationButtonGroup(KDecoration2::DecorationButtonGroup::Position::Right, this, &UKUI::Button::create); + m_rightButtons->setSpacing(m_buttonSpacing); + + calculateRightButtonCout(); + + connect(settings().data(), &KDecoration2::DecorationSettings::decorationButtonsRightChanged, this, &UKUI::Decoration::updateButtonsGeometry); + connect(settings().data(), &KDecoration2::DecorationSettings::fontChanged, this, &UKUI::Decoration::updatefont); + connect(client().data(), &KDecoration2::DecoratedClient::sizeChanged, this, &UKUI::Decoration::updateButtonsGeometry); + connect(client().data(), &KDecoration2::DecoratedClient::paletteChanged, this, static_cast(&Decoration::update)); + connect(client().data(), &KDecoration2::DecoratedClient::activeChanged, this, static_cast(&Decoration::update)); + connect(client().data(), &KDecoration2::DecoratedClient::maximizeableChanged, this, &Decoration::calculateRightButtonCout); //安装兼容应用全屏后还原,可最大化按钮有改变 + connect(client().data(), &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::calculateBorders); + connect(client().data(), &KDecoration2::DecoratedClient::captionChanged, this, + [this]() + { + update(); //更新标题栏标题内容, update(titleBar())这有时不能即时更新标题栏内容 + } + ); + + connect(client().data(), &KDecoration2::DecoratedClient::widthChanged, this, &Decoration::updateTitleBar); + connect(client().data(), &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::updateTitleBar); + } else { + setBorders(QMargins(0, 0, 0, 0)); + setResizeOnlyBorders(QMargins(CUSOR_BORDER, CUSOR_BORDER, CUSOR_BORDER, CUSOR_BORDER)); + //connect(client().data(), &KDecoration2::DecoratedClient::activeChanged, this, &Decoration::updateShadow); + } + + connect(client().data(), &KDecoration2::DecoratedClient::sizeChanged, this, [=](){ + auto effectManager = KWin::effects; + if (!effectManager) + return; + for (auto window : effectManager->stackingOrder()) { + if (!window) + continue; + bool sameCaption = false; + bool sameSize = false; + if (window->caption() == this->client().data()->caption()) { + sameCaption = true; + } + if (window->geometry().size() == client().data()->decoration().data()->size()) { + sameSize = true; + } + if (sameCaption && sameSize) { + bool isEdge = client().data()->adjacentScreenEdges() != Qt::Edge(); + if (isEdge) { + window->setData(1000, true); + } else { + window->setData(1000, QVariant()); + } + } + } + }); + + // get theme project + if (m_themeProject == THEME_PROJECT_UKUI_BASE) { + QSettings osinfo("/etc/os-release", QSettings::IniFormat); + if (osinfo.contains(QString("PROJECT_CODENAME"))) { + QString project_codename = osinfo.value(QString("PROJECT_CODENAME")).toString(); + if (0 == project_codename.compare("V10SP1-edu", Qt::CaseInsensitive)) + m_themeProject = THEME_PROJECT_LAIKA; + else if (0 == project_codename.compare("V10SPX-mavis-edu", Qt::CaseInsensitive)) + m_themeProject = THEME_PROJECT_MAVIS; + } + } +} + +void Decoration::updatefont(QFont font) +{ + m_Font = font; + QToolTip::setFont(font); + update(); +} + +void Decoration::updateShadow(int themeId) +{ + m_themeId = themeId; + + auto ubr = XAtomHelper::getInstance()->getWindowBorderRadius(client().data()->windowId()); + + // 控制左上、右上的阴影,ubr不生效时,阴影也应该是0 + if (ubr.topLeft <= 0) { + ubr.topLeft = m_shadowRadius; + } + if (ubr.topRight <= 0) { + ubr.topRight = m_shadowRadius; + } + if (ubr.bottomLeft <= 0) { + ubr.bottomLeft = m_shadowRadius; + } + if (ubr.bottomRight <= 0) { + ubr.bottomRight = m_shadowRadius; + } + + // m_themeId is useless here now, but just keep it. +// if(m_themeId==1) +// { +// m_BorderColor = QColor(255,255,255); +// }else{ +// m_BorderColor = QColor(38,38,38); +// } + m_BorderColor = this->client().data()->palette().shadow().color(); + m_BorderColor.setAlphaF(SHADOWCOLOR_ALPHAF); + + ShadowIndex shadowIndex(m_BorderColor, ubr.topLeft, ubr.topRight, ubr.bottomLeft, ubr.bottomRight, ACTIVE_DARKNESS, SHADOW_BORDER); + auto shadow = ShadowHelper::globalInstance()->getShadow(shadowIndex); + shadow.data()->setPadding(QMargins(SHADOW_BORDER, SHADOW_BORDER, SHADOW_BORDER, SHADOW_BORDER)); + setShadow(shadow); +} + +void Decoration::updateTitleBar() +{ + bool isDecoBorderOnly = XAtomHelper::getInstance()->isWindowDecorateBorderOnly(client().data()->windowId()); + if (isDecoBorderOnly) + { + return; + } + auto c = client().data(); + const int x = (m_ButtonMarginTop + m_buttonSpacing) * 2 + m_nleftButtonCout * m_leftButtonWidth; + const int width = c->width() + (c->isMaximized() ? 0 : (m_borderLeft + m_borderRight)) + - (m_ButtonMarginTop + m_buttonSpacing) * 2 - m_nleftButtonCout * m_leftButtonWidth //减去左侧按钮空间 + - m_nrightButtonCout * (m_buttonWidth + m_buttonSpacing); //减去右侧按钮空间 + setTitleBar(QRect(x, 0, width, borderTop())); +} + +void Decoration::calculateBorders() +{ + bool maximized = client().data()->isMaximized(); + if(true == maximized) + { + //在最大化时需要对setBorders中其中一个参数调整一下大小,否则,兆芯笔记本使用VSCode开源软件最大化时,会出现页面刷新不完全的情况。此处选择将m_borderTop - 1进行调整。 + //后来由于使用新版VSCode不会出现该问题,故又将m_borderTop - 1调整回m_borderTop + setBorders(QMargins(0, m_borderTop, 0, 0)); //真正的边框尺寸 + setResizeOnlyBorders(QMargins(0, 0, 0, 0)); //边框伸展光标范围 + } + else + { + setBorders(QMargins(m_borderLeft, m_borderTop, m_borderRight, m_borderBottom)); //真正的边框尺寸 + setResizeOnlyBorders(QMargins(CUSOR_BORDER, CUSOR_BORDER, CUSOR_BORDER, CUSOR_BORDER)); //边框伸展光标范围 + } +} + +void Decoration::slotSwitchTablet(bool bTabletMode) +{ + if(bTabletMode == m_bTabletMode) + { + return; + } + + m_bTabletMode = bTabletMode; + + calculateRightButtonCout(); + calculateBorders(); + + return; +} + +void Decoration::calculateRightButtonCout() +{ + m_nrightButtonCout = 0; + for (const QPointer& button : m_rightButtons->buttons()) + { +// if(false == button.data()->isVisible()) +// { +// continue; +// } + + if(KDecoration2::DecorationButtonType::Minimize == button.data()->type()) + { + if(false == client().data()->isMinimizeable()) + { + button.data()->setVisible(false); + continue; + } + else + { + if (true == m_bTabletMode) + { + button.data()->setVisible(false); + } + else + { + button.data()->setVisible(true); + m_nrightButtonCout++; + } + } + } + + //安卓兼容应用全屏后还原,从可最大化转变为不可最大化,此处增加隐藏按钮设定 + if(KDecoration2::DecorationButtonType::Maximize == button.data()->type()) + { + if(false == client().data()->isMaximizeable()) + { + button.data()->setVisible(false); + continue; + } + else + { + if (true == m_bTabletMode) + { + button.data()->setVisible(false); + } + else + { + button.data()->setVisible(true); + m_nrightButtonCout++; + } + } + } + + if(KDecoration2::DecorationButtonType::Close == button.data()->type()) + { + if(false == client().data()->isCloseable()) + { + button.data()->setVisible(false); + continue; + } + else + { + if (true == m_bTabletMode) + { + button.data()->setVisible(false); + } + else + { + button.data()->setVisible(true); + m_nrightButtonCout++; + } + } + } + + button.data()->setGeometry(QRectF(QPointF(0, 0), QSizeF(m_buttonWidth, m_buttonHeight))); + } + + updateButtonsGeometry(); + updateTitleBar(); +} + +void Decoration::themeUpdate(int themeId) +{ + m_themeId = themeId; + if(1 == m_themeId) + { + m_frameActiveColor = QColor(18, 18, 18); + m_frameInactiveColor = QColor(28, 28, 28); + m_fontActiveColor = QColor(207, 207, 207); + m_fontInactiveColor = QColor(105, 105, 105); + + //修改标题栏右键菜单颜色 + auto config = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::SimpleConfig); + KConfigGroup viewConfig(config, QStringLiteral("Colors:View")); + viewConfig.writeEntry("BackgroundAlternate", QColor(49,54,59)); + viewConfig.writeEntry("BackgroundNormal", QColor(35,38,41)); + viewConfig.writeEntry("DecorationHover", QColor(61,174,233)); + viewConfig.writeEntry("ForegroundInactive", QColor(189,195,199)); + viewConfig.writeEntry("ForegroundNormal", QColor(239,240,241)); + + KConfigGroup windowConfig(config, QStringLiteral("Colors:Window")); + windowConfig.writeEntry("BackgroundAlternate", QColor(77,77,77)); + windowConfig.writeEntry("BackgroundNormal", QColor(49,54,59)); + windowConfig.writeEntry("DecorationHover", QColor(61,174,233)); + windowConfig.writeEntry("ForegroundInactive", QColor(189,195,199)); + windowConfig.writeEntry("ForegroundNormal", QColor(239,240,241)); + config->sync(); + + } + else + { + m_frameActiveColor = QColor(255, 255, 255); + m_frameInactiveColor = QColor(245, 245, 245); + m_fontActiveColor = QColor(0, 0, 0); + m_fontInactiveColor = QColor(105, 105, 105); + + auto config = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::SimpleConfig); + KConfigGroup viewConfig(config, QStringLiteral("Colors:View")); + viewConfig.writeEntry("BackgroundAlternate", QColor(239,240,241)); + viewConfig.writeEntry("BackgroundNormal", QColor(252,252,252)); + viewConfig.writeEntry("DecorationHover", QColor(147,206,233)); + viewConfig.writeEntry("ForegroundInactive", QColor(127,140,141)); + viewConfig.writeEntry("ForegroundNormal", QColor(35,38,39)); + + KConfigGroup windowConfig(config, QStringLiteral("Colors:Window")); + windowConfig.writeEntry("BackgroundAlternate", QColor(189,195,199)); + windowConfig.writeEntry("BackgroundNormal", QColor(239,240,241)); + windowConfig.writeEntry("DecorationHover", QColor(147,206,233)); + windowConfig.writeEntry("ForegroundInactive", QColor(127,140,141)); + windowConfig.writeEntry("ForegroundNormal", QColor(35,38,39)); + config->sync(); + } + + update(); + updateShadow(m_themeId); + +} + +void Decoration::paint(QPainter *painter, const QRect &repaintRegion) +{ + if (!repaintRegion.isEmpty()){ + painter->setClipRect(repaintRegion); + } + + auto c = client().data(); + + //总体框架刷颜色 + painter->save(); + if(c->isMaximized()) + { + auto rect = QRect(QPoint(0, 0), this->client().data()->size()); + painter->fillRect(rect, this->client().data()->isActive() ? this->m_frameActiveColor : this->m_frameInactiveColor); + } + else if(c->adjacentScreenEdges() != Qt::Edge()) + { + auto rect = QRect(0, 0, (c->size().width() + m_borderLeft + m_borderRight), (c->size().height() + m_borderTop + m_borderBottom)); + painter->setPen(Qt::NoPen); + painter->setBrush(this->client().data()->isActive() ? this->m_frameActiveColor : this->m_frameInactiveColor); + painter->drawRoundedRect(rect, 0, 0); + + } + else + { + auto rect = QRect(0, 0, (c->size().width() + m_borderLeft + m_borderRight), (c->size().height() + m_borderTop + m_borderBottom)); + painter->setPen(Qt::NoPen); + painter->setBrush(this->client().data()->isActive() ? this->m_frameActiveColor : this->m_frameInactiveColor); + // 控制左上、右上的阴影 + painter->drawRoundedRect(rect, m_shadowRadius, m_shadowRadius); + + auto rectLeftBottom = QRect(0, rect.height() - m_shadowRadius * 2, m_shadowRadius * 2, m_shadowRadius * 2); + painter->drawRoundedRect(rectLeftBottom, 0, 0); //左下角补角 + auto rectRightBottom = QRect(rect.width() - m_shadowRadius * 2, rect.height() - m_shadowRadius * 2, m_shadowRadius * 2, m_shadowRadius * 2); + painter->drawRoundedRect(rectRightBottom, 0, 0); + + } + painter->restore(); + + //写标题 + painter->setFont(m_Font); + painter->setPen(fontColor()); + + const auto cR = qMakePair(titleBar(), Qt::AlignVCenter | Qt::AlignLeft); + const QString caption = painter->fontMetrics().elidedText(c->caption(), Qt::ElideMiddle, cR.first.width()); + + painter->drawText(cR.first, cR.second | Qt::TextSingleLine, caption); + + //按钮组刷颜色 + m_leftButtons->paint(painter, repaintRegion); + m_rightButtons->paint(painter, repaintRegion); +} + +void Decoration::updateButtonsGeometry() +{ + auto c = client().data(); + + m_leftButtons->setPos(QPoint(m_ButtonMarginTop + m_buttonSpacing, m_ButtonMarginTop + m_buttonSpacing)); + + //由于上边檐是m_ButtonMarginTop,右侧有m_nrightButtonCout个按钮和按钮间隙 + auto posX = c->width() + (c->isMaximized() ? 0 : (m_borderLeft + m_borderRight)) - m_nrightButtonCout * (m_buttonWidth + m_buttonSpacing); + m_rightButtons->setPos(QPoint(posX, m_ButtonMarginTop)); + + update(); +} + +QColor Decoration::fontColor()const +{ + auto c = client().data(); + if(c->isActive()) + { + return m_fontActiveColor; + } + else + { + return m_fontInactiveColor; + } +} + + +#include diff --git a/src/plugins/kdecorations/ukui/ukui-decoration.h b/src/plugins/kdecorations/ukui/ukui-decoration.h new file mode 100644 index 000000000..f12895040 --- /dev/null +++ b/src/plugins/kdecorations/ukui/ukui-decoration.h @@ -0,0 +1,145 @@ +/* + * KWin Style UKUI + * + * Copyright (C) 2020, KylinSoft Co., Ltd. + * + * 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 + * (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. If not, see . + * + * Authors: Yue Lan + * + */ + +#ifndef UKUIDECORATION_H +#define UKUIDECORATION_H + +#include +#include +#include + +namespace KDecoration2 { +class DecorationButtonGroup; +} + +namespace UKUI { + +struct ShadowParams { + ShadowParams() + : offset(QPoint(0, 0)) + , radius(0) + , opacity(0) {} + + ShadowParams(const QPoint &offset, int radius, qreal opacity) + : offset(offset) + , radius(radius) + , opacity(opacity) {} + + QPoint offset; + int radius; + qreal opacity; +}; + +struct CompositeShadowParams { + CompositeShadowParams() = default; + + CompositeShadowParams( + const QPoint &offset, + const ShadowParams &shadow1, + const ShadowParams &shadow2) + : offset(offset) + , shadow1(shadow1) + , shadow2(shadow2) {} + + bool isNone() const { + return qMax(shadow1.radius, shadow2.radius) == 0; + } + + QPoint offset; + ShadowParams shadow1; + ShadowParams shadow2; +}; + +enum ThemeProject { + THEME_PROJECT_UKUI_BASE = 0, + THEME_PROJECT_LAIKA, + THEME_PROJECT_MAVIS, + THEME_PROJECT_XC_TABLET, + + THEME_PROJECT_MAX +}; + +class Decoration : public KDecoration2::Decoration +{ + Q_OBJECT +public: + explicit Decoration(QObject *parent = nullptr, const QVariantList &args = QVariantList()); + + void init(); + void paint(QPainter *painter, const QRect &repaintRegion) override; + + QColor fontColor() const; + int themeId(){return m_themeId;} + ThemeProject getThemeProject() { return m_themeProject; } + bool getTabletMode() {return m_bTabletMode;} + +private: + int m_borderLeft; + int m_borderTop; + int m_borderRight; + int m_borderBottom; + + int m_buttonWidth; //按钮宽度 + int m_buttonHeight; //按钮高度 + + int m_leftButtonWidth; //左侧按钮高度 + int m_leftButtonHeight; //左侧按钮宽度 + + int m_ButtonMarginTop; //按钮上空白 + + int m_buttonSpacing; //按钮空隙 + + QColor m_fontActiveColor; //活动字体颜色 + QColor m_fontInactiveColor; //非活动字体颜色 + + QColor m_frameActiveColor; + QColor m_frameInactiveColor; + + QColor m_BorderColor; //边框颜色 + + bool m_bTabletMode; + int m_nleftButtonCout; + int m_nrightButtonCout; + + int m_themeId; + ThemeProject m_themeProject; + + KDecoration2::DecorationButtonGroup* m_leftButtons; + KDecoration2::DecorationButtonGroup* m_rightButtons; + + int m_shadowRadius; + QFont m_Font; + +public Q_SLOTS: + void updateButtonsGeometry(); + void calculateBorders(); + void calculateRightButtonCout(); + void updateTitleBar(); + void updateShadow(int themeId); + void themeUpdate(int themeId); + void updatefont(QFont font); + void slotSwitchTablet(bool bTabletMode); +}; + +} + +#endif // UKUIDECORATION_H diff --git a/src/plugins/kdecorations/ukui/xatom-helper.cpp b/src/plugins/kdecorations/ukui/xatom-helper.cpp new file mode 100644 index 000000000..c3a3a31ed --- /dev/null +++ b/src/plugins/kdecorations/ukui/xatom-helper.cpp @@ -0,0 +1,336 @@ +/* + * KWin Style UKUI + * + * Copyright (C) 2020, KylinSoft Co., Ltd. + * + * 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 + * (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. If not, see . + * + * Authors: Yue Lan + * + */ + +#include +#include "xatom-helper.h" + +#include + +#include + +static XAtomHelper *global_instance = nullptr; + +XAtomHelper *XAtomHelper::getInstance() +{ + if (!global_instance) + global_instance = new XAtomHelper; + + if (!KWin::connection()) + return global_instance; + + if (!global_instance->m_motifWMHintsAtom) { + QString tmp("_MOTIF_WM_HINTS"); + xcb_intern_atom_cookie_t cookie1 = xcb_intern_atom(KWin::connection(), false, tmp.length(), tmp.toUtf8()); + tmp = "_UNITY_GTK_BORDER_RADIUS"; + xcb_intern_atom_cookie_t cookie2 = xcb_intern_atom(KWin::connection(), false, tmp.length(), tmp.toUtf8()); + tmp = "_KWIN_UKUI_DECORAION"; + xcb_intern_atom_cookie_t cookie3 = xcb_intern_atom(KWin::connection(), false, tmp.length(), tmp.toUtf8()); + + xcb_intern_atom_reply_t *reply1 = xcb_intern_atom_reply(KWin::connection(), cookie1, nullptr); + global_instance->m_motifWMHintsAtom = reply1->atom; + free(reply1); + + xcb_intern_atom_reply_t *reply2 = xcb_intern_atom_reply(KWin::connection(), cookie2, nullptr); + global_instance->m_unityBorderRadiusAtom = reply2->atom; + free(reply2); + + xcb_intern_atom_reply_t *reply3 = xcb_intern_atom_reply(KWin::connection(), cookie3, nullptr); + global_instance->m_ukuiDecorationAtion = reply3->atom; + free(reply3); + } + + return global_instance; +} + +bool XAtomHelper::isFrameLessWindow(int winId) +{ + auto hints = getInstance()->getWindowMotifHint(winId); + if (hints.flags == MWM_HINTS_DECORATIONS && hints.functions == 1) { + return true; + } + return false; +} + +bool XAtomHelper::isWindowDecorateBorderOnly(int winId) +{ + return isWindowMotifHintDecorateBorderOnly(getInstance()->getWindowMotifHint(winId)); +} + +bool XAtomHelper::isWindowMotifHintDecorateBorderOnly(const MotifWmHints &hint) +{ + bool isDeco = false; + if (hint.flags & MWM_HINTS_DECORATIONS) { + if (hint.decorations == MWM_DECOR_BORDER) { + isDeco = true; + } + } + return isDeco; +} + +bool XAtomHelper::isUKUICsdSupported() +{ + // fixme: + return false; +} + +bool XAtomHelper::isUKUIDecorationWindow(int winId) +{ + if (m_ukuiDecorationAtion == 0) + return false; + + uchar *data; + + bool isUKUIDecoration = false; + + xcb_get_property_cookie_t cookie = xcb_get_property(KWin::connection(), false, winId, m_ukuiDecorationAtion, XCB_ATOM_ANY, 0, 1); + xcb_get_property_reply_t *reply = xcb_get_property_reply(KWin::connection(), cookie, nullptr); + if (!reply) + return false; + + data = (uchar *)xcb_get_property_value(reply); + free(reply); + if (data) { + isUKUIDecoration = data[0]; + //free(data); + } + + return isUKUIDecoration; +} + +UnityCorners XAtomHelper::getWindowBorderRadius(int winId) +{ + UnityCorners corners; + corners.topLeft = 0; + corners.topRight = 0; + corners.bottomLeft = 0; + corners.bottomRight = 0; + + if (0 == winId) { + return corners; + } + + uchar *data; + + xcb_get_property_cookie_t cookie = xcb_get_property(KWin::connection(), false, winId, m_unityBorderRadiusAtom, XCB_ATOM_CARDINAL, 0, sizeof(UnityCorners)/sizeof(ulong)); + xcb_generic_error_t *error = nullptr; + xcb_get_property_reply_t *reply = xcb_get_property_reply(KWin::connection(), cookie, &error); + if (!reply) + return corners; + + if (error) { + free(error); + return corners; + } + + if (xcb_get_property_value_length(reply) != 4 * sizeof(int)) { + free(reply); + return corners; + } + + data = (uchar*)xcb_get_property_value(reply); + free(reply); + + if (data) { + corners.topLeft = static_cast(data[0]); + corners.topRight = static_cast(data[1*sizeof (int)]); + corners.bottomLeft = static_cast(data[2*sizeof (int)]); + corners.bottomRight = static_cast(data[3*sizeof (int)]); + //free(data); + } + + return corners; +} + +void XAtomHelper::setWindowBorderRadius(int winId, const UnityCorners &data) +{ + if (m_unityBorderRadiusAtom == 0) + return; + + ulong corners[4] = {data.topLeft, data.topRight, data.bottomLeft, data.bottomRight}; + + xcb_change_property(KWin::connection(), XCB_PROP_MODE_REPLACE, winId, m_unityBorderRadiusAtom, XCB_ATOM_CARDINAL, 32, sizeof(corners)/sizeof(corners[0]), &corners); + xcb_flush(KWin::connection()); +} + +void XAtomHelper::setWindowBorderRadius(int winId, int topLeft, int topRight, int bottomLeft, int bottomRight) +{ + if (m_unityBorderRadiusAtom == 0) + return; + + ulong corners[4] = {(ulong)topLeft, (ulong)topRight, (ulong)bottomLeft, (ulong)bottomRight}; + + xcb_change_property(KWin::connection(), XCB_PROP_MODE_REPLACE, winId, m_unityBorderRadiusAtom, XCB_ATOM_CARDINAL, 32, sizeof(corners)/sizeof(corners[0]), &corners); + xcb_flush(KWin::connection()); +} + +void XAtomHelper::setUKUIDecoraiontHint(int winId, bool set) +{ + if (m_ukuiDecorationAtion == 0) + return; + + xcb_change_property(KWin::connection(), XCB_PROP_MODE_REPLACE, winId, m_ukuiDecorationAtion, m_ukuiDecorationAtion, 32, 1, &set); + xcb_flush(KWin::connection()); +} + +void XAtomHelper::setWindowMotifHint(int winId, const MotifWmHints &hints) +{ + if (m_unityBorderRadiusAtom == 0) + return; + + xcb_change_property(KWin::connection(), XCB_PROP_MODE_REPLACE, winId, m_motifWMHintsAtom, m_motifWMHintsAtom, + 32, sizeof (MotifWmHints)/ sizeof (ulong), &hints); + xcb_flush(KWin::connection()); +} + +MotifWmHints XAtomHelper::getWindowMotifHint(int winId) +{ + MotifWmHints hints; + + if (m_motifWMHintsAtom == 0) + return hints; + + uchar *data; + + xcb_get_property_cookie_t cookie = xcb_get_property(KWin::connection(), false, winId, m_motifWMHintsAtom, XCB_ATOM_ANY, 0, sizeof (MotifWmHints)/ sizeof (ulong)); + xcb_get_property_reply_t *reply = xcb_get_property_reply(KWin::connection(), cookie, nullptr); + if (!reply) + return hints; + + if (reply->length != 5) { + free(reply); + return hints; + } + + data = (uchar*)xcb_get_property_value(reply); + free(reply); + if (!data) + return hints; + hints.flags = data[0]; + hints.functions = data[sizeof(int)]; + hints.decorations = data[sizeof(int)*2]; + hints.input_mode = data[sizeof(int)*3]; + hints.status = data[sizeof(int)*4]; + + return hints; +} + +XAtomHelper::XAtomHelper(QObject *parent) : QObject(parent) +{ + if (!KWin::connection()) + return; + + QString tmp("_MOTIF_WM_HINTS"); + xcb_intern_atom_cookie_t cookie1 = xcb_intern_atom_unchecked(KWin::connection(), false, tmp.length(), tmp.toUtf8()); + tmp = "_UNITY_GTK_BORDER_RADIUS"; + xcb_intern_atom_cookie_t cookie2 = xcb_intern_atom_unchecked(KWin::connection(), false, tmp.length(), tmp.toUtf8()); + tmp = "_KWIN_UKUI_DECORAION"; + xcb_intern_atom_cookie_t cookie3 = xcb_intern_atom_unchecked(KWin::connection(), false, tmp.length(), tmp.toUtf8()); + + xcb_intern_atom_reply_t *reply1 = xcb_intern_atom_reply(KWin::connection(), cookie1, nullptr); + m_motifWMHintsAtom = reply1->atom; + free(reply1); + + xcb_intern_atom_reply_t *reply2 = xcb_intern_atom_reply(KWin::connection(), cookie2, nullptr); + m_unityBorderRadiusAtom = reply2->atom; + free(reply2); + + xcb_intern_atom_reply_t *reply3 = xcb_intern_atom_reply(KWin::connection(), cookie3, nullptr); + m_ukuiDecorationAtion = reply3->atom; + free(reply3); +} + +xcb_atom_t XAtomHelper::registerUKUICsdNetWmSupportAtom() +{ + // fixme: + return 0; +} + +void XAtomHelper::unregisterUKUICsdNetWmSupportAtom() +{ + // fixme: +} + +bool XAtomHelper::checkButtonAvailable(int winId, KDecoration2::DecorationButtonType buttonType) +{ + MotifWmHints hint = getInstance()->getWindowMotifHint(winId); + //printf("XAtomHelper::checkButtonAvailable ==hint:%02X==hints.functions:%02X\n", hint.flags, hint.functions); + if((0 == hint.functions) || (0 == hint.flags)) + { + return true; + } + + bool bMinimizeButtonAvailable = true; + bool bMaximizeButtonAvailable = true; + bool bCloseButtonAvailable = true; + bool bToggleFlag; + + if(0 == (hint.functions & MWM_FUNC_ALL)) + { + bToggleFlag = true; + bMinimizeButtonAvailable = false; + bMaximizeButtonAvailable = false; + bCloseButtonAvailable = false; + } + else + { + bToggleFlag = false; + } + + switch (buttonType) { + case KDecoration2::DecorationButtonType::Minimize: + { + if(0 != (hint.functions & MWM_FUNC_MINIMIZE)) + { + bMinimizeButtonAvailable = bToggleFlag; + //printf("XAtomHelper::checkButtonAvailable ==bToggleFlag:%d\n", bToggleFlag); + } + //printf("XAtomHelper::checkButtonAvailable ==bMinimizeButtonAvailable:%d\n", bMinimizeButtonAvailable); + return bMinimizeButtonAvailable; + } + break; + + case KDecoration2::DecorationButtonType::Maximize: + { + if(0 != (hint.functions & MWM_FUNC_MAXIMIZE)) + { + bMaximizeButtonAvailable = bToggleFlag; + } + return bMaximizeButtonAvailable; + } + break; + + case KDecoration2::DecorationButtonType::Close: + { + if(0 != (hint.functions & MWM_FUNC_CLOSE)) + { + bCloseButtonAvailable = bToggleFlag; + } + return bCloseButtonAvailable; + } + break; + + default: + break; + } + + return true; +} diff --git a/src/plugins/kdecorations/ukui/xatom-helper.h b/src/plugins/kdecorations/ukui/xatom-helper.h new file mode 100644 index 000000000..479a28240 --- /dev/null +++ b/src/plugins/kdecorations/ukui/xatom-helper.h @@ -0,0 +1,113 @@ +/* + * KWin Style UKUI + * + * Copyright (C) 2020, KylinSoft Co., Ltd. + * + * 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 + * (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. If not, see . + * + * Authors: Yue Lan + * + */ + +#ifndef XATOMHELPER_H +#define XATOMHELPER_H + +#include + +#include +#include + + +struct UnityCorners { + ulong topLeft = 0; + ulong topRight = 0; + ulong bottomLeft = 0; + ulong bottomRight = 0; +}; + +typedef struct { + ulong flags = 0; + ulong functions = 0; + ulong decorations = 0; + long input_mode = 0; + ulong status = 0; +} MotifWmHints, MwmHints; + +#define MWM_HINTS_FUNCTIONS (1L << 0) +#define MWM_HINTS_DECORATIONS (1L << 1) +#define MWM_HINTS_INPUT_MODE (1L << 2) +#define MWM_HINTS_STATUS (1L << 3) + +#define MWM_FUNC_ALL (1L << 0) +#define MWM_FUNC_RESIZE (1L << 1) +#define MWM_FUNC_MOVE (1L << 2) +#define MWM_FUNC_MINIMIZE (1L << 3) +#define MWM_FUNC_MAXIMIZE (1L << 4) +#define MWM_FUNC_CLOSE (1L << 5) + +#define MWM_DECOR_ALL (1L << 0) +#define MWM_DECOR_BORDER (1L << 1) +#define MWM_DECOR_RESIZEH (1L << 2) +#define MWM_DECOR_TITLE (1L << 3) +#define MWM_DECOR_MENU (1L << 4) +#define MWM_DECOR_MINIMIZE (1L << 5) +#define MWM_DECOR_MAXIMIZE (1L << 6) + +#define MWM_INPUT_MODELESS 0 +#define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1 +#define MWM_INPUT_SYSTEM_MODAL 2 +#define MWM_INPUT_FULL_APPLICATION_MODAL 3 +#define MWM_INPUT_APPLICATION_MODAL MWM_INPUT_PRIMARY_APPLICATION_MODAL + +#define MWM_TEAROFF_WINDOW (1L<<0) + +namespace UKUI { +class Decoration; +} + +class XAtomHelper : public QObject +{ + friend class UKUI::Decoration; + Q_OBJECT +public: + static XAtomHelper *getInstance(); + + bool isFrameLessWindow(int winId); + + bool isWindowDecorateBorderOnly(int winId); + bool isWindowMotifHintDecorateBorderOnly(const MotifWmHints &hint); + bool isUKUICsdSupported(); + bool isUKUIDecorationWindow(int winId); + + UnityCorners getWindowBorderRadius(int winId); + void setWindowBorderRadius(int winId, const UnityCorners &data); + void setWindowBorderRadius(int winId, int topLeft, int topRight, int bottomLeft, int bottomRight); + void setUKUIDecoraiontHint(int winId, bool set = true); + + void setWindowMotifHint(int winId, const MotifWmHints &hints); + MotifWmHints getWindowMotifHint(int winId); + + bool checkButtonAvailable(int winId, KDecoration2::DecorationButtonType buttonType); +private: + explicit XAtomHelper(QObject *parent = nullptr); + + xcb_atom_t registerUKUICsdNetWmSupportAtom(); + void unregisterUKUICsdNetWmSupportAtom(); + + xcb_atom_t m_motifWMHintsAtom = 0; + xcb_atom_t m_unityBorderRadiusAtom = 0; + xcb_atom_t m_ukuiDecorationAtion = 0; +}; + +#endif // XATOMHELPER_H diff --git a/src/scripting/scriptedeffect.cpp b/src/scripting/scriptedeffect.cpp index c26819f03..48677605e 100644 --- a/src/scripting/scriptedeffect.cpp +++ b/src/scripting/scriptedeffect.cpp @@ -141,8 +141,17 @@ ScriptedEffect *ScriptedEffect::create(const KPluginMetaData &effect) qCDebug(KWIN_SCRIPTING) << "X-Plasma-MainScript not set"; return nullptr; } +#if defined(QT_NO_DEBUG) const QString scriptFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String(KWIN_NAME "/effects/") + name + QLatin1String("/contents/") + scriptName); +#else + QString effectName = name; + QString scriptFile = QCoreApplication::applicationDirPath() + QLatin1String("/../../src/effects/") + effectName.remove("kwin4_effect_") + QLatin1String("/package/contents/") + scriptName; + if (!QFile::exists(scriptFile)) + scriptFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, + QLatin1String(KWIN_NAME "/effects/") + name + QLatin1String("/contents/") + scriptName); + qCDebug(KWIN_SCRIPTING) << "Load script file:" << scriptFile; +#endif if (scriptFile.isNull()) { qCDebug(KWIN_SCRIPTING) << "Could not locate the effect script"; return nullptr; @@ -204,7 +213,14 @@ bool ScriptedEffect::init(const QString &effectName, const QString &pathToScript m_scriptFile = pathToScript; // does the effect contain an KConfigXT file? +#if defined(QT_NO_DEBUG) const QString kconfigXTFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String(KWIN_NAME "/effects/") + m_effectName + QLatin1String("/contents/config/main.xml")); +#else + QString kconfigXTFile = QCoreApplication::applicationDirPath() + QLatin1String("/../../src/effects/") + m_effectName.remove("kwin4_effect_") + QLatin1String("/package/contents/config/main.xml"); + if (!QFile::exists(kconfigXTFile)) + kconfigXTFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String(KWIN_NAME "/effects/") + m_effectName + QLatin1String("/contents/config/main.xml")); + qCDebug(KWIN_SCRIPTING) << "Try to load kconfigXTFile file:" << kconfigXTFile; +#endif if (!kconfigXTFile.isNull()) { KConfigGroup cg = QCoreApplication::instance()->property("config").value()->group(QStringLiteral("Effect-%1").arg(m_effectName)); QFile xmlFile(kconfigXTFile); diff --git a/src/session_logind.cpp b/src/session_logind.cpp index 818a96620..77c966240 100644 --- a/src/session_logind.cpp +++ b/src/session_logind.cpp @@ -61,11 +61,19 @@ static const QString s_managerPath = QStringLiteral("/org/freedesktop/login1"); static QString findProcessSessionPath() { +#if defined(QT_NO_DEBUG) const QString sessionId = qEnvironmentVariable("XDG_SESSION_ID", QStringLiteral("auto")); QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, s_managerPath, s_managerInterface, QStringLiteral("GetSession")); message.setArguments({sessionId}); +#else + const quint32 applicationId = (quint32) QCoreApplication::applicationPid(); + QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName, s_managerPath, + s_managerInterface, + QStringLiteral("GetSessionByPID")); + message.setArguments({applicationId}); +#endif const QDBusMessage reply = QDBusConnection::systemBus().call(message); if (reply.type() == QDBusMessage::ErrorMessage) { return QString(); diff --git a/src/useractions.cpp b/src/useractions.cpp index f056de21d..f457d7e01 100644 --- a/src/useractions.cpp +++ b/src/useractions.cpp @@ -1363,6 +1363,9 @@ void Workspace::slotWindowToPrevScreen() */ void Workspace::slotWindowMaximize() { + if(m_bTabletMode) + return; + if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::MaximizeOp); } diff --git a/src/workspace.cpp b/src/workspace.cpp index e4e3e1972..adaf78c98 100644 --- a/src/workspace.cpp +++ b/src/workspace.cpp @@ -127,6 +127,7 @@ Workspace::Workspace() , set_active_client_recursion(0) , block_stacking_updates(0) , m_sessionManager(new SessionManager(this)) + , m_bTabletMode(false) { // If KWin was already running it saved its configuration after loosing the selection -> Reread QFuture reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration); @@ -278,6 +279,28 @@ void Workspace::init() connect(server, &WaylandServer::shellClientRemoved, this, &Workspace::removeShellClient); } + //kwin启动时获取运行模式: 0:PC模式, 1:平板模式 + QDBusMessage message = QDBusMessage::createMethodCall("com.kylin.statusmanager.interface", + "/", + "com.kylin.statusmanager.interface", + "get_current_tabletmode"); + + QDBusMessage response = QDBusConnection::sessionBus().call(message); + + if (response.type() == QDBusMessage::ReplyMessage) { + m_bTabletMode = response.arguments().takeFirst().toBool(); + slotSwitchTabletMode(m_bTabletMode); + } else { + printf("TabletMode qdus invalid\n"); + } + //通过dbus监听PC-平板模式切换 + QDBusConnection::sessionBus().connect("com.kylin.statusmanager.interface", + "/", + "com.kylin.statusmanager.interface", + "mode_change_signal", + this, + SLOT(slotSwitchTabletMode(bool))); + // SELI TODO: This won't work with unreasonable focus policies, // and maybe in rare cases also if the selected client doesn't // want focus @@ -289,6 +312,58 @@ void Workspace::init() // TODO: ungrabXServer() } +void Workspace::slotSwitchTabletMode(bool bSurfaceMode) +{ + if (m_bTabletMode == bSurfaceMode) + return; + m_bTabletMode = bSurfaceMode; + switchTablet(m_bTabletMode); +} + +void Workspace::switchTablet(bool bOn) +{ + bool bTopClientMaximize = true; //顶层窗口最大化标志 + for (int i = stacking_order.count() - 1; i > -1; --i) { + AbstractClient *c = qobject_cast(stacking_order.at(i)); + if (!c || c->isDock() || c->isDesktop()) + continue; + + //m_bTabletMode为true,表示pc切平板 + if (m_bTabletMode) { + if (c->maximizeMode() == MaximizeRestore) { + c->setTabletToPcRestoreFlag(true); //切平板前,如果是还原状态,则记录 + } + + //并且该窗口不是最小化时 + if(!c->isMinimized()) { + if(bTopClientMaximize) { + bTopClientMaximize = false; + c->maximize(MaximizeFull); //序号越大,越在顶层,只需要将既不是任务栏又不是桌面的最顶层次未最小化的窗口最大化 + + if (false == c->isResizable()) { + if (!showingDesktop()) { + // 如果是固定大小的窗口,需要将窗口放到屏幕的正中间 + Placement::self()->placeCentered(c, workspace()->activeOutput()->geometry()); + } + } + } else { + printf("Workspace::switchTablet, minimize unminimized caption:%s\n", c->caption().toStdString().c_str()); + c->minimize(true); + } + } + } else { + //m_bTabletMode为false,表示平板切pc,顶层最大化窗口如果之前是还原状态,则需还原 + if (MaximizeFull == c->maximizeMode() && true == bTopClientMaximize) { + bTopClientMaximize = false; + if (c->getTabletToPcRestoreFlag()) { //如果pc切平板前,记录的之前是还原状态,则当前激活窗口在切回pc后,需还原,否则,不需要处理 + c->maximize(MaximizeRestore); + c->setTabletToPcRestoreFlag(false); //切回pc后,由于当前激活窗口已还原,则以后该窗口无需再做还原处理 + } + } + } + } +} + void Workspace::initializeX11() { if (!kwinApp()->x11Connection()) { @@ -656,6 +731,9 @@ X11Client *Workspace::createClient(xcb_window_t w, bool is_mapped) return nullptr; } addClient(c); + + if(m_bTabletMode) //当是平板模式时,窗口默认最大化,其他窗口最小化/*,并且设置平板标签*/ + c->makeOthersMinimize(); return c; } @@ -2238,6 +2316,17 @@ void Workspace::updateClientArea() } } + //当屏幕旋转时,那些固定大小的窗口需要再次进行一次居中设置(除了任务栏和桌面) + if (m_bTabletMode) { + for (auto it = m_x11Clients.constBegin(); it != m_x11Clients.constEnd(); ++it) { + if (true == (*it)->isResizable() || true == (*it)->isDock() || true == (*it)->isDesktop()) { + continue; + } + + Placement::self()->placeCentered(*it, workspace()->activeOutput()->geometry()); + } + } + if (m_workAreas != workAreas || m_restrictedAreas != restrictedAreas || m_screenAreas != screenAreas) { m_workAreas = workAreas; m_screenAreas = screenAreas; diff --git a/src/workspace.h b/src/workspace.h index 63a240d92..3cd3c588d 100644 --- a/src/workspace.h +++ b/src/workspace.h @@ -416,6 +416,9 @@ public: */ void removeInternalClient(InternalClient *client); + bool isInTabletMode() {return m_bTabletMode;} + void switchTablet(bool bOn); + public Q_SLOTS: void performWindowOperation(KWin::AbstractClient* c, Options::WindowOperation op); // Keybindings @@ -431,6 +434,8 @@ public Q_SLOTS: void slotWindowToPrevScreen(); void slotToggleShowDesktop(); + void slotSwitchTabletMode(bool bSurfaceMode); + void slotWindowMaximize(); void slotWindowMaximizeVertical(); void slotWindowMaximizeHorizontal(); @@ -662,6 +667,7 @@ private: static Workspace* _self; bool workspaceInit; + bool m_bTabletMode; QScopedPointer m_startup; QScopedPointer m_colorMapper; diff --git a/src/x11client.cpp b/src/x11client.cpp index ce275fff1..661eafa94 100644 --- a/src/x11client.cpp +++ b/src/x11client.cpp @@ -1946,6 +1946,50 @@ QStringList X11Client::activities() const return AbstractClient::activities(); } + +//使工作区其他X11窗口最小化 +void X11Client::makeOthersMinimize() +{ + printf("X11Client::makeOthersMinimize, takefocus windowType(0):%d, caption:%s, isModal:%d, isMaximizable:%d\n", windowType(), this->caption().toStdString().c_str(), isModal(), isMaximizable()); + //平板模式下,如果是isModal窗口(如xournal应用); 或者是瞬态的对话框(如归档管理器的新建窗口); 则没必要最小化其他所有未最小化的窗口,或者是新激活的桌面开始菜单和Dock窗口(比如onboard),也不要最小化其他窗口 + if (true == this->isUtility() + || true == this->isModal() + || (true == this->isDialog() && true == this->isTransient()) + || true == this->isDesktop() + || true == this->isDock() + || caption() == "sogouImeService" + ) + { + bUnMakeOthersMinimizeFlag = true; + return; + } + + if (!(this->getSpecialFlags() == static_cast(SpecialFlags::DoNotMaximize))) { + this->maximize(MaximizeFull); + } + + printf("X11Client::makeOthersMinimize, takefocus windowType(1):%d, caption:%s, isdialog:%d, isMaximizable:%d\n", windowType(), this->caption().toStdString().c_str(), isDialog(), isMaximizable()); + + //锁屏时,如果当前是屏保窗口,对其他窗口不要最小化。解决在平板模式打开应用,锁屏后再进入桌面,应用窗口最小化问题 + if (this->resourceClass() == "ukui-screensaver-default") { + return; + } + + for (X11Client *c : workspace()->clientList()) { + bool isIgnoreWindow = (this == c || c->isDock() || skipTaskbar() || c->isDialog() || c->isDesktop()); + bool isTabletBgWindow = (c->isUtility() && c->caption() == "ukui-kwin_tablet_background" && c->keepBelow()); + //除了自己,dock和对话框也没必要最小化 + if (isIgnoreWindow || isTabletBgWindow) { + continue; + } + + if (false == c->isMinimized()){ + printf("X11Client::makeOthersMinimize, minimize unminimized caption:%s\n", c->caption().toStdString().c_str()); + c->minimize(true); + } + } +} + /** * Performs the actual focusing of the window using XSetInputFocus and WM_TAKE_FOCUS */ @@ -1969,6 +2013,21 @@ bool X11Client::takeFocus() } workspace()->setShouldGetFocus(this); + //如果是平板模式,则其他所有窗口均最小化 + if (Workspace::self()->isInTabletMode()){ + // 平板模式下,当窗口获得焦点时,如果是固定大小的窗口(除了任务栏),需要将窗口放到屏幕的正中间 + if (false == this->isResizable() && false == isDock() && false == isDesktop()) { + Placement::self()->placeCentered(this, workspace()->activeOutput()->geometry()); + } + makeOthersMinimize(); + } else { + //如果pc切平板前,该窗口是还原状态,平板切pc后的窗口激活时,需将该窗口从最大化切换还原 + if (getTabletToPcRestoreFlag()) { + setTabletToPcRestoreFlag(false); + this->maximize(MaximizeRestore); + } + } + bool breakShowingDesktop = !keepAbove(); if (breakShowingDesktop) { const auto members = group()->members(); diff --git a/src/x11client.h b/src/x11client.h index b523044d3..7b789a864 100644 --- a/src/x11client.h +++ b/src/x11client.h @@ -45,6 +45,10 @@ enum class Predicate { InputIdMatch, }; +enum class SpecialFlags { + DoNotMaximize = 0x1 << 0 +}; + /** * @todo Remove when the X11 platform support is dropped. This decoration renderer * will be used if compositing is off. @@ -181,6 +185,7 @@ public: bool isCloseable() const override; ///< May be closed by the user (May have a close button) bool takeFocus() override; + void makeOthersMinimize(); //使工作区其他X11窗口最小化 void invalidateDecoration() override; @@ -302,6 +307,9 @@ public: static void cleanupX11(); + void setSpecialFlags(unsigned int specialFlags) {m_specialFlags = specialFlags;} + unsigned int getSpecialFlags() {return m_specialFlags;} + public Q_SLOTS: void closeWindow() override; void updateCaption() override; @@ -528,6 +536,10 @@ private: QRect m_lastFrameGeometry; QRect m_lastClientGeometry; QScopedPointer m_decorationRenderer; + + bool bUnMakeOthersMinimizeFlag; //不最小化其他窗体标志 + unsigned int m_specialFlags = 0; + }; inline xcb_window_t X11Client::wrapperId() const