From b2067b3a0839394715ee71b61373727e6dc3562e Mon Sep 17 00:00:00 2001 From: luhongxu Date: Wed, 16 Sep 2026 10:16:53 +0800 Subject: [PATCH] feat(appearance): implement appearance protocol split for 0.6.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement treeland_appearance_unstable_v1 and treeland_appearance_manager_unstable_v1 protocols for treeland-protocols 0.6.0. Move appearance code to src/modules/appearance/. Freeze old personalization module with DEPRECATED comments. Remove personalization tests, add appearance tests (8 tests). 为 treeland-protocols 0.6.0 实现 appearance 和 appearance-manager 两个新协议,将代码迁移到 src/modules/appearance/ 目录。冻结旧 personalization 模块并添加 DEPRECATED 注释。删除 personalization 测试, 新增 appearance 单元测试(8 项全部通过)。 Log: 实现新协议拆分,适配 treeland-protocols 0.6.0 Influence: 新增 2 个 appearance 协议实现,旧 personalization 协议冻结保留,不影响现有功能。 PMS: TASK-395091 --- examples/CMakeLists.txt | 1 - .../test_window_appearance/CMakeLists.txt | 10 +- .../appearance_manager.cpp | 73 +++++ .../appearance_manager.h | 66 ++++ examples/test_window_appearance/main.cpp | 280 +++++++++++------ .../personalization_manager.cpp | 98 ------ .../personalization_manager.h | 92 ------ examples/test_window_cursor/CMakeLists.txt | 24 -- examples/test_window_cursor/main.cpp | 103 ------- .../personalization_manager.cpp | 44 --- .../personalization_manager.h | 48 --- .../dconfig/org.deepin.dde.treeland.user.json | 14 +- src/CMakeLists.txt | 1 + src/common/windowdecorations.h | 35 +++ src/core/shellhandler.cpp | 9 +- src/modules/CMakeLists.txt | 1 + src/modules/appearance/CMakeLists.txt | 36 +++ .../appearance/appearanceinterfacev1.cpp | 284 ++++++++++++++++++ .../appearance/appearanceinterfacev1.h | 51 ++++ .../appearancemanagerinterfacev1.cpp | 245 +++++++++++++++ .../appearance/appearancemanagerinterfacev1.h | 44 +++ src/modules/personalization/CMakeLists.txt | 16 + .../personalizationmanagerinterfacev1.cpp | 34 ++- .../personalizationmanagerinterfacev1.h | 21 +- src/seat/helper.cpp | 22 +- src/seat/helper.h | 6 +- tests/CMakeLists.txt | 2 +- tests/protocols/INDEX.md | 6 +- .../setup.cpp | 8 +- tests/test_protocol_appearance/CMakeLists.txt | 19 ++ tests/test_protocol_appearance/main.cpp | 81 +++++ .../CMakeLists.txt | 19 -- tests/test_protocol_personalization/main.cpp | 49 --- 33 files changed, 1216 insertions(+), 626 deletions(-) create mode 100644 examples/test_window_appearance/appearance_manager.cpp create mode 100644 examples/test_window_appearance/appearance_manager.h delete mode 100644 examples/test_window_appearance/personalization_manager.cpp delete mode 100644 examples/test_window_appearance/personalization_manager.h delete mode 100644 examples/test_window_cursor/CMakeLists.txt delete mode 100644 examples/test_window_cursor/main.cpp delete mode 100644 examples/test_window_cursor/personalization_manager.cpp delete mode 100644 examples/test_window_cursor/personalization_manager.h create mode 100644 src/common/windowdecorations.h create mode 100644 src/modules/appearance/CMakeLists.txt create mode 100644 src/modules/appearance/appearanceinterfacev1.cpp create mode 100644 src/modules/appearance/appearanceinterfacev1.h create mode 100644 src/modules/appearance/appearancemanagerinterfacev1.cpp create mode 100644 src/modules/appearance/appearancemanagerinterfacev1.h create mode 100644 tests/test_protocol_appearance/CMakeLists.txt create mode 100644 tests/test_protocol_appearance/main.cpp delete mode 100644 tests/test_protocol_personalization/CMakeLists.txt delete mode 100644 tests/test_protocol_personalization/main.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index df9803fc92..5bc12fe73b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -3,7 +3,6 @@ set(MODULE_PATH ${PROJECT_SOURCE_DIR}/src/modules) add_subdirectory(test_window_bg) add_subdirectory(test_wallpaper_color) add_subdirectory(test_show_desktop) -add_subdirectory(test_window_cursor) add_subdirectory(test_xdgport_wallpaper) add_subdirectory(test_virtual_output) add_subdirectory(test_pinch_handler) diff --git a/examples/test_window_appearance/CMakeLists.txt b/examples/test_window_appearance/CMakeLists.txt index 197173e1a2..71a008587f 100644 --- a/examples/test_window_appearance/CMakeLists.txt +++ b/examples/test_window_appearance/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +# SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + find_package(Qt6 REQUIRED COMPONENTS WaylandClient Gui Widgets) find_package(TreelandProtocols REQUIRED) @@ -5,13 +8,14 @@ set(BIN_NAME test-window-appearance) qt_add_executable(${BIN_NAME} main.cpp - personalization_manager.h - personalization_manager.cpp + appearance_manager.h + appearance_manager.cpp ) qt_generate_wayland_protocol_client_sources(${BIN_NAME} FILES - ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-personalization-manager-v1.xml + ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-appearance-unstable-v1.xml + ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-appearance-manager-unstable-v1.xml ) target_link_libraries(${BIN_NAME} diff --git a/examples/test_window_appearance/appearance_manager.cpp b/examples/test_window_appearance/appearance_manager.cpp new file mode 100644 index 0000000000..2ab965024a --- /dev/null +++ b/examples/test_window_appearance/appearance_manager.cpp @@ -0,0 +1,73 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "appearance_manager.h" + +QT_BEGIN_NAMESPACE + +AppearanceObserver::AppearanceObserver() + : QWaylandClientExtensionTemplate(1) +{ +} + +void AppearanceObserver::treeland_appearance_v1_cursor_theme(const QString &name) +{ + Q_EMIT cursorThemeChanged(name); +} + +void AppearanceObserver::treeland_appearance_v1_cursor_size(uint32_t width, uint32_t height) +{ + Q_EMIT cursorSizeChanged(width, height); +} + +void AppearanceObserver::treeland_appearance_v1_font(const QString &font_name) +{ + Q_EMIT fontChanged(font_name); +} + +void AppearanceObserver::treeland_appearance_v1_monospace_font(const QString &font_name) +{ + Q_EMIT monospaceFontChanged(font_name); +} + +void AppearanceObserver::treeland_appearance_v1_font_size(uint32_t size) +{ + Q_EMIT fontSizeChanged(size); +} + +void AppearanceObserver::treeland_appearance_v1_icon_theme(const QString &theme_name) +{ + Q_EMIT iconThemeChanged(theme_name); +} + +void AppearanceObserver::treeland_appearance_v1_accent_color(uint32_t r, uint32_t g, uint32_t b) +{ + Q_EMIT accentColorChanged(r, g, b); +} + +void AppearanceObserver::treeland_appearance_v1_window_opacity(wl_fixed_t opacity) +{ + Q_EMIT windowOpacityChanged(opacity); +} + +void AppearanceObserver::treeland_appearance_v1_color_scheme(uint32_t scheme) +{ + Q_EMIT colorSchemeChanged(scheme); +} + +void AppearanceObserver::treeland_appearance_v1_titlebar_height(uint32_t height) +{ + Q_EMIT titlebarHeightChanged(height); +} + +void AppearanceObserver::treeland_appearance_v1_corner_radius(uint32_t radius) +{ + Q_EMIT cornerRadiusChanged(radius); +} + +AppearanceManager::AppearanceManager() + : QWaylandClientExtensionTemplate(1) +{ +} + +QT_END_NAMESPACE diff --git a/examples/test_window_appearance/appearance_manager.h b/examples/test_window_appearance/appearance_manager.h new file mode 100644 index 0000000000..1fcf133744 --- /dev/null +++ b/examples/test_window_appearance/appearance_manager.h @@ -0,0 +1,66 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef APPEARANCE_MANAGER_H +#define APPEARANCE_MANAGER_H + +#include "qwayland-treeland-appearance-unstable-v1.h" +#include "qwayland-treeland-appearance-manager-unstable-v1.h" + +#include + +QT_BEGIN_NAMESPACE + +// Read-only observer for user-level appearance settings. Any client may bind +// this global; the compositor pushes the current value of every setting +// immediately after bind and broadcasts an event whenever a setting changes. +class AppearanceObserver + : public QWaylandClientExtensionTemplate + , public QtWayland::treeland_appearance_v1 +{ + Q_OBJECT +public: + explicit AppearanceObserver(); + +Q_SIGNALS: + void cursorThemeChanged(const QString &name); + void cursorSizeChanged(uint32_t width, uint32_t height); + void fontChanged(const QString &font_name); + void monospaceFontChanged(const QString &font_name); + void fontSizeChanged(uint32_t size); + void iconThemeChanged(const QString &theme_name); + void accentColorChanged(uint32_t r, uint32_t g, uint32_t b); + void windowOpacityChanged(wl_fixed_t opacity); + void colorSchemeChanged(uint32_t scheme); + void titlebarHeightChanged(uint32_t height); + void cornerRadiusChanged(uint32_t radius); + +protected: + void treeland_appearance_v1_cursor_theme(const QString &name) override; + void treeland_appearance_v1_cursor_size(uint32_t width, uint32_t height) override; + void treeland_appearance_v1_font(const QString &font_name) override; + void treeland_appearance_v1_monospace_font(const QString &font_name) override; + void treeland_appearance_v1_font_size(uint32_t size) override; + void treeland_appearance_v1_icon_theme(const QString &theme_name) override; + void treeland_appearance_v1_accent_color(uint32_t r, uint32_t g, uint32_t b) override; + void treeland_appearance_v1_window_opacity(wl_fixed_t opacity) override; + void treeland_appearance_v1_color_scheme(uint32_t scheme) override; + void treeland_appearance_v1_titlebar_height(uint32_t height) override; + void treeland_appearance_v1_corner_radius(uint32_t radius) override; +}; + +// Privileged manager for modifying user-level appearance settings. Binding +// without compositor-level authorization may result in requests being +// silently ignored or the connection being terminated. +class AppearanceManager + : public QWaylandClientExtensionTemplate + , public QtWayland::treeland_appearance_manager_v1 +{ + Q_OBJECT +public: + explicit AppearanceManager(); +}; + +QT_END_NAMESPACE + +#endif // APPEARANCE_MANAGER_H diff --git a/examples/test_window_appearance/main.cpp b/examples/test_window_appearance/main.cpp index 103dd062ac..d436948b4f 100644 --- a/examples/test_window_appearance/main.cpp +++ b/examples/test_window_appearance/main.cpp @@ -1,116 +1,214 @@ -// Copyright (C) 2023-2026 Dingyuan Zhang . +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. // SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include "personalization_manager.h" +#include "appearance_manager.h" #include #include #include #include #include -#include -#include #include +#include + class TestWindow : public QWidget { Q_OBJECT public: TestWindow() - : m_manager(new PersonalizationManager) + : m_observer(new AppearanceObserver) + , m_manager(new AppearanceManager) { - setWindowTitle("Setting Appearance Client"); - - QVBoxLayout *mainLayout = new QVBoxLayout; - - QHBoxLayout *group1Layout = new QHBoxLayout; - QPushButton *button1 = new QPushButton("set window light theme"); - QPushButton *button2 = new QPushButton("set window dark theme"); - group1Layout->addWidget(button1); - group1Layout->addWidget(button2); - mainLayout->addLayout(group1Layout); - connect(button1, &QPushButton::clicked, this, [this] { - context->set_window_theme_type( - TREELAND_PERSONALIZATION_APPEARANCE_CONTEXT_V1_THEME_TYPE_LIGHT); - }); - connect(button2, &QPushButton::clicked, this, [this] { - context->set_window_theme_type( - TREELAND_PERSONALIZATION_APPEARANCE_CONTEXT_V1_THEME_TYPE_DARK); - }); - - QLabel *currentWindowTheme = new QLabel; - mainLayout->addWidget(currentWindowTheme); - - QLabel *titlebarLabel = new QLabel("titlebar height:"); - QLineEdit *titlebarEdit = new QLineEdit; - QHBoxLayout *group2Layout = new QHBoxLayout; - QPushButton *button3 = new QPushButton("set titlebar height"); - group2Layout->addWidget(titlebarLabel); - group2Layout->addWidget(titlebarEdit); - group2Layout->addWidget(button3); - mainLayout->addLayout(group2Layout); - - connect(button3, &QPushButton::clicked, this, [this, titlebarEdit] { - context->set_window_titlebar_height(titlebarEdit->text().toUInt()); - }); - - mainLayout->addStretch(); - - connect(m_manager, - &PersonalizationManager::activeChanged, - this, - [this, currentWindowTheme, titlebarEdit] { - qDebug() << "personalization manager active changed"; - - if (m_manager->isActive()) { - context = new Appearance(m_manager->get_appearance_context()); - cursor = new Cursor(m_manager->get_cursor_context()); - font = new Font(m_manager->get_font_context()); - - connect(context, - &Appearance::windowThemeTypeChanged, - this, - [currentWindowTheme](uint32_t type) { - currentWindowTheme->setText( - QString("current window theme type: %1").arg(type)); - }); - context->get_window_theme_type(); - - connect(context, - &Appearance::windowTitlebarHeightChanged, - this, - [titlebarEdit](uint32_t height) { - titlebarEdit->blockSignals(true); - titlebarEdit->setText(QString::number(height)); - titlebarEdit->blockSignals(false); - qDebug() << "titlebar height changed: " << height; - }); - - context->get_window_titlebar_height(); - font->get_font(); - font->get_font_size(); - font->get_monospace_font(); - } - }); - - setLayout(mainLayout); - - setMinimumSize(400, 300); + setWindowTitle("Appearance Client (0.6.0)"); + + m_mainLayout = new QVBoxLayout; + + // ---- Color scheme ---- + auto *scheme = addSettingRow("color scheme", {}, {}); + scheme->edit->hide(); + auto *lightBtn = new QPushButton("light"); + auto *darkBtn = new QPushButton("dark"); + for (auto *button : { lightBtn, darkBtn }) + scheme->layout->addWidget(button); + connect(lightBtn, &QPushButton::clicked, this, &TestWindow::setLightScheme); + connect(darkBtn, &QPushButton::clicked, this, &TestWindow::setDarkScheme); + connect(m_observer, &AppearanceObserver::colorSchemeChanged, this, [this, scheme](uint32_t value) { + updateRow(*scheme, value == QtWayland::treeland_appearance_v1::color_scheme_dark + ? "dark" : "light"); + }); + + // ---- Titlebar height ---- + auto *titlebar = addSettingRow( + "titlebar height", "titlebar height in pixels", [this](const QString &text) { + m_manager->set_titlebar_height(text.toUInt()); + }); + connect(m_observer, &AppearanceObserver::titlebarHeightChanged, this, [this, titlebar](uint32_t height) { + updateRow(*titlebar, QString::number(height)); + }); + + // ---- Corner radius ---- + auto *radius = addSettingRow("corner radius", "corner radius in pixels", [this](const QString &text) { + m_manager->set_corner_radius(text.toUInt()); + }); + connect(m_observer, &AppearanceObserver::cornerRadiusChanged, this, [this, radius](uint32_t value) { + updateRow(*radius, QString::number(value)); + }); + + // ---- Cursor theme ---- + auto *cursorTheme = addSettingRow("cursor theme", "cursor theme name", [this](const QString &text) { + m_manager->set_cursor_theme(text); + }); + connect(m_observer, &AppearanceObserver::cursorThemeChanged, this, [this, cursorTheme](const QString &name) { + updateRow(*cursorTheme, name); + }); + + // ---- Cursor size ---- + auto *cursorSize = addSettingRow("cursor size", "cursor size in pixels", [this](const QString &text) { + const uint32_t size = text.toUInt(); + m_manager->set_cursor_size(size, size); + }); + connect(m_observer, &AppearanceObserver::cursorSizeChanged, this, [this, cursorSize](uint32_t width, uint32_t height) { + updateRow(*cursorSize, QString("%1x%2").arg(width).arg(height)); + }); + + // ---- Font ---- + auto *font = addSettingRow("font", "font family name", [this](const QString &text) { + m_manager->set_font(text); + }); + connect(m_observer, &AppearanceObserver::fontChanged, this, [this, font](const QString &name) { + updateRow(*font, name); + }); + + // ---- Monospace font ---- + auto *monoFont = addSettingRow("monospace font", "monospace font family name", [this](const QString &text) { + m_manager->set_monospace_font(text); + }); + connect(m_observer, &AppearanceObserver::monospaceFontChanged, this, [this, monoFont](const QString &name) { + updateRow(*monoFont, name); + }); + + // ---- Font size ---- + auto *fontSize = addSettingRow("font size", "font size in points", [this](const QString &text) { + m_manager->set_font_size(text.toUInt()); + }); + connect(m_observer, &AppearanceObserver::fontSizeChanged, this, [this, fontSize](uint32_t size) { + updateRow(*fontSize, QString::number(size)); + }); + + // ---- Icon theme ---- + auto *iconTheme = addSettingRow("icon theme", "icon theme name", [this](const QString &text) { + m_manager->set_icon_theme(text); + }); + connect(m_observer, &AppearanceObserver::iconThemeChanged, this, [this, iconTheme](const QString &name) { + updateRow(*iconTheme, name); + }); + + // ---- Accent color ---- + auto *accentColor = addSettingRow( + "accent color", "r,g,b (each 0-255), e.g. 44,119,222", + [this](const QString &text) { + const auto parts = text.split(','); + if (parts.size() != 3) { + qWarning() << "accent color must be 3 comma-separated values"; + return; + } + m_manager->set_accent_color(parts[0].toUInt(), parts[1].toUInt(), + parts[2].toUInt()); + }); + connect(m_observer, &AppearanceObserver::accentColorChanged, this, [this, accentColor](uint32_t r, uint32_t g, uint32_t b) { + updateRow(*accentColor, QString("%1,%2,%3").arg(r).arg(g).arg(b)); + }); + + // ---- Window opacity ---- + auto *opacity = addSettingRow( + "window opacity", "opacity [0.0, 1.0], e.g. 0.8", [this](const QString &text) { + bool ok = false; + const double value = text.toDouble(&ok); + if (!ok || value < 0.0 || value > 1.0) { + qWarning() << "window opacity must be in [0.0, 1.0]:" << text; + return; + } + m_manager->set_window_opacity(wl_fixed_from_double(value)); + }); + connect(m_observer, &AppearanceObserver::windowOpacityChanged, this, [this, opacity](wl_fixed_t value) { + updateRow(*opacity, QString::number(wl_fixed_to_double(value), 'f', 2)); + }); + + m_mainLayout->addStretch(); + + qDebug() << "appearance observer active:" << m_observer->isActive() + << "manager active:" << m_manager->isActive(); + + setLayout(m_mainLayout); + setMinimumSize(480, 560); } - ~TestWindow() +private: + struct SettingRow + { + QLabel *label = nullptr; + QLineEdit *edit = nullptr; + QHBoxLayout *layout = nullptr; + QString title; + }; + + // Creates a row of "title: " plus an editable field and an + // apply button wired to `apply`. The returned row's label/edit is kept in + // sync by the corresponding observer-signal connection at the call site. + SettingRow *addSettingRow(const QString &title, const QString &placeholder, + const std::function &apply) { - if (m_manager != nullptr) { - delete m_manager; - m_manager = nullptr; + auto *label = new QLabel(QString("%1: -").arg(title)); + auto *edit = new QLineEdit; + edit->setPlaceholderText(placeholder); + auto *button = new QPushButton(QString("set %1").arg(title)); + + auto *rowLayout = new QHBoxLayout; + rowLayout->addWidget(label, 1); + rowLayout->addWidget(edit, 2); + + // Rows are owned by unique_ptr so that their addresses stay stable + // as more rows are added; observer connections capture the pointer. + auto row = std::make_unique( + SettingRow { label, edit, rowLayout, title }); + auto *rowPtr = row.get(); + m_rows.push_back(std::move(row)); + rowLayout->addWidget(button); + + m_mainLayout->addLayout(rowLayout); + + if (apply) { + connect(button, &QPushButton::clicked, this, [this, edit, apply] { apply(edit->text()); }); + } else { + button->hide(); } + + return rowPtr; } -private: - PersonalizationManager *m_manager = nullptr; - Appearance *context = nullptr; - Font *font = nullptr; - Cursor *cursor = nullptr; + void updateRow(SettingRow &row, const QString &value) + { + row.label->setText(QString("%1: %2").arg(row.title, value)); + row.edit->blockSignals(true); + row.edit->setText(value); + row.edit->blockSignals(false); + } + + void setLightScheme() + { + m_manager->set_color_scheme(QtWayland::treeland_appearance_manager_v1::color_scheme_light); + } + + void setDarkScheme() + { + m_manager->set_color_scheme(QtWayland::treeland_appearance_manager_v1::color_scheme_dark); + } + + std::vector> m_rows; + QVBoxLayout *m_mainLayout = nullptr; + AppearanceObserver *m_observer = nullptr; + AppearanceManager *m_manager = nullptr; }; int main(int argc, char **argv) diff --git a/examples/test_window_appearance/personalization_manager.cpp b/examples/test_window_appearance/personalization_manager.cpp deleted file mode 100644 index 5e80be2af4..0000000000 --- a/examples/test_window_appearance/personalization_manager.cpp +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (C) 2024 justforlxz . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "personalization_manager.h" - -#include - -QT_BEGIN_NAMESPACE - -PersonalizationManager::PersonalizationManager() - : QWaylandClientExtensionTemplate(1) -{ -} - -Cursor::Cursor(struct ::treeland_personalization_cursor_context_v1 *object) - : QWaylandClientExtensionTemplate(1) - , QtWayland::treeland_personalization_cursor_context_v1(object) - -{ -} - -void Cursor::treeland_personalization_cursor_context_v1_verfity(int32_t success) -{ - Q_EMIT verifyChanged(success); -} - -void Cursor::treeland_personalization_cursor_context_v1_theme(const QString &name) -{ - Q_EMIT themeChanged(name); -} - -void Cursor::treeland_personalization_cursor_context_v1_size(uint32_t size) -{ - Q_EMIT sizeChanged(size); -} - -Font::Font(struct ::treeland_personalization_font_context_v1 *object) - : QWaylandClientExtensionTemplate(1) - , QtWayland::treeland_personalization_font_context_v1(object) - -{ -} - -void Font::treeland_personalization_font_context_v1_font(const QString &font_name) -{ - Q_EMIT fontChanged(font_name); -} - -void Font::treeland_personalization_font_context_v1_monospace_font(const QString &font_name) -{ - Q_EMIT monoFontChanged(font_name); -} - -void Font::treeland_personalization_font_context_v1_font_size(uint32_t font_size) -{ - Q_EMIT fontSizeChanged(font_size); -} - -Appearance::Appearance(struct ::treeland_personalization_appearance_context_v1 *object) - : QWaylandClientExtensionTemplate(1) - , QtWayland::treeland_personalization_appearance_context_v1(object) -{ -} - -void Appearance::treeland_personalization_appearance_context_v1_round_corner_radius(int32_t radius) -{ - Q_EMIT radiusChanged(radius); -} - -void Appearance::treeland_personalization_appearance_context_v1_icon_theme( - const QString &theme_name) -{ - Q_EMIT iconThemeChanged(theme_name); -} - -void Appearance::treeland_personalization_appearance_context_v1_active_color( - const QString &active_color) -{ - Q_EMIT activeColorChanged(active_color); -} - -void Appearance::treeland_personalization_appearance_context_v1_window_opacity(uint32_t opacity) -{ - Q_EMIT windowOpacityChanged(opacity); -} - -void Appearance::treeland_personalization_appearance_context_v1_window_theme_type(uint32_t type) -{ - Q_EMIT windowThemeTypeChanged(type); -} - -void Appearance::treeland_personalization_appearance_context_v1_window_titlebar_height( - uint32_t height) -{ - Q_EMIT windowTitlebarHeightChanged(height); -} - -QT_END_NAMESPACE diff --git a/examples/test_window_appearance/personalization_manager.h b/examples/test_window_appearance/personalization_manager.h deleted file mode 100644 index 3babe9560c..0000000000 --- a/examples/test_window_appearance/personalization_manager.h +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (C) 2024 justforlxz . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef CUSTOMEXTENSION_H -#define CUSTOMEXTENSION_H - -#include "qwayland-treeland-personalization-manager-v1.h" - -#include -#include - -QT_BEGIN_NAMESPACE - -class PersonalizationManager - : public QWaylandClientExtensionTemplate - , public QtWayland::treeland_personalization_manager_v1 -{ - Q_OBJECT -public: - explicit PersonalizationManager(); -}; - -class Cursor - : public QWaylandClientExtensionTemplate - , public QtWayland::treeland_personalization_cursor_context_v1 -{ - Q_OBJECT -public: - explicit Cursor(struct ::treeland_personalization_cursor_context_v1 *object); - -Q_SIGNALS: - void verifyChanged(int32_t success); - void themeChanged(const QString &name); - void sizeChanged(uint32_t size); - -protected: - void treeland_personalization_cursor_context_v1_verfity(int32_t success) override; - void treeland_personalization_cursor_context_v1_theme(const QString &name) override; - void treeland_personalization_cursor_context_v1_size(uint32_t size) override; -}; - -class Font - : public QWaylandClientExtensionTemplate - , public QtWayland::treeland_personalization_font_context_v1 -{ - Q_OBJECT -public: - explicit Font(struct ::treeland_personalization_font_context_v1 *object); - -Q_SIGNALS: - void fontChanged(const QString &font_name); - void monoFontChanged(const QString &font_name); - void fontSizeChanged(uint32_t font_size); - -protected: - void treeland_personalization_font_context_v1_font(const QString &font_name) override; - void treeland_personalization_font_context_v1_monospace_font(const QString &font_name) override; - void treeland_personalization_font_context_v1_font_size(uint32_t font_size) override; -}; - -class Appearance - : public QWaylandClientExtensionTemplate - , public QtWayland::treeland_personalization_appearance_context_v1 -{ - Q_OBJECT -public: - explicit Appearance(struct ::treeland_personalization_appearance_context_v1 *object); - -Q_SIGNALS: - void radiusChanged(int32_t radius); - void iconThemeChanged(const QString &name); - void activeColorChanged(const QString &color); - void windowOpacityChanged(uint32_t opacity); - void windowThemeTypeChanged(uint32_t type); - void windowTitlebarHeightChanged(uint32_t height); - -protected: - void treeland_personalization_appearance_context_v1_round_corner_radius( - int32_t radius) override; - void treeland_personalization_appearance_context_v1_icon_theme( - const QString &theme_name) override; - void treeland_personalization_appearance_context_v1_active_color( - const QString &active_color) override; - void treeland_personalization_appearance_context_v1_window_opacity(uint32_t opacity) override; - void treeland_personalization_appearance_context_v1_window_theme_type(uint32_t type) override; - void treeland_personalization_appearance_context_v1_window_titlebar_height( - uint32_t height) override; -}; - -QT_END_NAMESPACE - -#endif // CUSTOMEXTENSION_H diff --git a/examples/test_window_cursor/CMakeLists.txt b/examples/test_window_cursor/CMakeLists.txt deleted file mode 100644 index 52ad1ccf61..0000000000 --- a/examples/test_window_cursor/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -find_package(Qt6 REQUIRED COMPONENTS WaylandClient Gui Widgets) -find_package(TreelandProtocols REQUIRED) - -set(BIN_NAME test-window-cursor) - -qt_add_executable(${BIN_NAME} - main.cpp - personalization_manager.h - personalization_manager.cpp -) - -qt_generate_wayland_protocol_client_sources(${BIN_NAME} - FILES - ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-personalization-manager-v1.xml -) - -target_link_libraries(${BIN_NAME} - PRIVATE - Qt6::Gui - Qt6::Widgets - Qt6::WaylandClient -) - -install(TARGETS ${BIN_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") diff --git a/examples/test_window_cursor/main.cpp b/examples/test_window_cursor/main.cpp deleted file mode 100644 index 688c8121c5..0000000000 --- a/examples/test_window_cursor/main.cpp +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (C) 2024 rewine . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "personalization_manager.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -class TestWindow : public QWidget -{ - Q_OBJECT -public: - TestWindow() - : m_manager(new PersonalizationManager) - { - setWindowTitle("Setting crusor Client"); - - connect(m_manager, &PersonalizationManager::activeChanged, this, [this] { - qDebug() << "personalization manager active changed"; - - if (m_manager->isActive()) { - cursor_context = new PersonalizationCursor(m_manager->get_cursor_context()); - } - }); - - QVBoxLayout *mainLayout = new QVBoxLayout; - - QHBoxLayout *group1Layout = new QHBoxLayout; - QPushButton *button1 = new QPushButton("set cursor theme"); - QLineEdit *lineEdit1 = new QLineEdit; - lineEdit1->setPlaceholderText("input theme name"); - group1Layout->addWidget(button1); - group1Layout->addWidget(lineEdit1); - mainLayout->addLayout(group1Layout); - QObject::connect(button1, &QPushButton::clicked, this, [this, lineEdit1] { - QString theme = lineEdit1->text(); - qDebug() << "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" << theme; - cursor_context->set_theme(theme); - cursor_context->commit(); - }); - - QHBoxLayout *group2Layout = new QHBoxLayout; - QPushButton *button2 = new QPushButton("set cursor size"); - QLineEdit *lineEdit2 = new QLineEdit; - lineEdit2->setPlaceholderText("input cursor size"); - group2Layout->addWidget(button2); - group2Layout->addWidget(lineEdit2); - mainLayout->addLayout(group2Layout); - QObject::connect(button2, &QPushButton::clicked, this, [this, lineEdit2] { - int cursor_size = lineEdit2->text().toInt(); - qDebug() << "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" << cursor_size; - cursor_context->set_size(cursor_size); - cursor_context->commit(); - }); - - QHBoxLayout *group3Layout = new QHBoxLayout; - QPushButton *button3 = new QPushButton("get cursor theme"); - QPushButton *button4 = new QPushButton("get cursor size(see log)"); - group3Layout->addWidget(button3); - group3Layout->addWidget(button4); - mainLayout->addLayout(group3Layout); - QObject::connect(button3, &QPushButton::clicked, this, [this] { - cursor_context->get_theme(); - }); - QObject::connect(button4, &QPushButton::clicked, this, [this] { - cursor_context->get_size(); - }); - - setLayout(mainLayout); - - setMinimumSize(400, 300); - } - - ~TestWindow() - { - if (m_manager != nullptr) { - delete m_manager; - m_manager = nullptr; - } - } - -private: - PersonalizationManager *m_manager = nullptr; - PersonalizationCursor *cursor_context = nullptr; -}; - -int main(int argc, char **argv) -{ - QApplication app(argc, argv); - - TestWindow window; - window.show(); - - return app.exec(); -} - -#include "main.moc" diff --git a/examples/test_window_cursor/personalization_manager.cpp b/examples/test_window_cursor/personalization_manager.cpp deleted file mode 100644 index 27919ebc1a..0000000000 --- a/examples/test_window_cursor/personalization_manager.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2024 rewine . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "personalization_manager.h" - -#include - -QT_BEGIN_NAMESPACE - -PersonalizationManager::PersonalizationManager() - : QWaylandClientExtensionTemplate(1) -{ -} - -PersonalizationWindow::PersonalizationWindow( - struct ::treeland_personalization_window_context_v1 *object) - : QWaylandClientExtensionTemplate(1) - , QtWayland::treeland_personalization_window_context_v1(object) -{ -} - -PersonalizationCursor::PersonalizationCursor( - struct ::treeland_personalization_cursor_context_v1 *object) - : QWaylandClientExtensionTemplate(1) - , QtWayland::treeland_personalization_cursor_context_v1(object) -{ -} - -void PersonalizationCursor::treeland_personalization_cursor_context_v1_verfity(int32_t success) -{ - qDebug() << "=========================================== verfity" << success; -} - -void PersonalizationCursor::treeland_personalization_cursor_context_v1_theme(const QString &name) -{ - qDebug() << "=========================================== theme" << name; -} - -void PersonalizationCursor::treeland_personalization_cursor_context_v1_size(uint32_t size) -{ - qDebug() << "=========================================== size" << size; -} - -QT_END_NAMESPACE diff --git a/examples/test_window_cursor/personalization_manager.h b/examples/test_window_cursor/personalization_manager.h deleted file mode 100644 index da599decf2..0000000000 --- a/examples/test_window_cursor/personalization_manager.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2024 rewine . -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef CUSTOMEXTENSION_H -#define CUSTOMEXTENSION_H - -#include "qwayland-treeland-personalization-manager-v1.h" - -#include -#include - -QT_BEGIN_NAMESPACE - -class PersonalizationManager - : public QWaylandClientExtensionTemplate - , public QtWayland::treeland_personalization_manager_v1 -{ - Q_OBJECT -public: - explicit PersonalizationManager(); -}; - -class PersonalizationWindow - : public QWaylandClientExtensionTemplate - , public QtWayland::treeland_personalization_window_context_v1 -{ - Q_OBJECT -public: - explicit PersonalizationWindow(struct ::treeland_personalization_window_context_v1 *object); -}; - -class PersonalizationCursor - : public QWaylandClientExtensionTemplate - , public QtWayland::treeland_personalization_cursor_context_v1 -{ - Q_OBJECT -public: - explicit PersonalizationCursor(struct ::treeland_personalization_cursor_context_v1 *object); - -protected: - void treeland_personalization_cursor_context_v1_verfity(int32_t success) override; - void treeland_personalization_cursor_context_v1_theme(const QString &name) override; - void treeland_personalization_cursor_context_v1_size(uint32_t size) override; -}; - -QT_END_NAMESPACE - -#endif // CUSTOMEXTENSION_H diff --git a/misc/dconfig/org.deepin.dde.treeland.user.json b/misc/dconfig/org.deepin.dde.treeland.user.json index 4908a3a3c2..62ce9be289 100644 --- a/misc/dconfig/org.deepin.dde.treeland.user.json +++ b/misc/dconfig/org.deepin.dde.treeland.user.json @@ -233,14 +233,14 @@ "permissions": "readwrite", "visibility": "public" }, - "windowThemeType": { - "value": 1, - "serial": 0, + "windowColorScheme": { + "value": 0, + "serial": 1, "flags": [], - "name": "Global Window Theme Type", - "name[zh_CN]": "全局窗口主题类型", - "description": "System theme type: 1 light, 2 dark", - "description[zh_CN]": "系统主题类型:1 亮色,2 暗色", + "name": "Global Window Color Scheme", + "name[zh_CN]": "全局窗口配色", + "description": "Color scheme hint: 0 light, 1 dark", + "description[zh_CN]": "配色偏好:0 亮色,1 暗色", "permissions": "readwrite", "visibility": "public" }, diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1eaf0d2c23..03213fc24d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -130,6 +130,7 @@ qt_add_qml_module(libtreeland ${SEAT_USER_CONFIG} common/treelandlogging.cpp common/treelandlogging.h + common/windowdecorations.h core/layersurfacecontainer.cpp core/layersurfacecontainer.h core/qmlengine.cpp diff --git a/src/common/windowdecorations.h b/src/common/windowdecorations.h new file mode 100644 index 0000000000..e86c7b31b4 --- /dev/null +++ b/src/common/windowdecorations.h @@ -0,0 +1,35 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include +#include + +/** + * @brief Drop-shadow parameters for server-side window decoration. + * + * Shared between the deprecated treeland-personalization-manager-v1 + * protocol and the new treeland-decoration-unstable-v1 protocol so that + * both can describe the same physical concept without code duplication. + */ +struct Shadow +{ + int32_t radius; + QPoint offset; + QColor color; + bool operator==(const Shadow &other) const = default; +}; + +/** + * @brief Border parameters for server-side window decoration. + * + * Shared between the deprecated treeland-personalization-manager-v1 + * protocol and the new treeland-decoration-unstable-v1 protocol. + */ +struct Border +{ + int32_t width; + QColor color; + bool operator==(const Border &other) const = default; +}; diff --git a/src/core/shellhandler.cpp b/src/core/shellhandler.cpp index 9294780ef6..1115d7e78c 100644 --- a/src/core/shellhandler.cpp +++ b/src/core/shellhandler.cpp @@ -310,9 +310,12 @@ void ShellHandler::createPrelaunchSplash(const QString &appId, } m_pendingPrelaunchAppIds.remove(appId); - const qlonglong effectiveType = - splashThemeType == 0 ? Helper::instance()->config()->windowThemeType() : splashThemeType; - const QColor splashColor = effectiveType == 1 ? QColor(lightPalette) : QColor(darkPalette); + // splashThemeType: 0 = follow system, 1 = light, 2 = dark + // windowColorScheme: 0 = light, 1 = dark + const bool dark = splashThemeType == 0 + ? Helper::instance()->config()->windowColorScheme() == 1 + : splashThemeType == 2; + const QColor splashColor = dark ? QColor(darkPalette) : QColor(lightPalette); auto *wrapper = new SurfaceWrapper(Helper::instance()->qmlEngine(), nullptr, diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt index 3b9dba87bf..c86b07e726 100644 --- a/src/modules/CMakeLists.txt +++ b/src/modules/CMakeLists.txt @@ -27,6 +27,7 @@ link_libraries( add_subdirectory(foreign-toplevel) add_subdirectory(output-manager) add_subdirectory(personalization) +add_subdirectory(appearance) add_subdirectory(shortcut) add_subdirectory(wallpaper-color) add_subdirectory(show-desktop) diff --git a/src/modules/appearance/CMakeLists.txt b/src/modules/appearance/CMakeLists.txt new file mode 100644 index 0000000000..951da5483e --- /dev/null +++ b/src/modules/appearance/CMakeLists.txt @@ -0,0 +1,36 @@ +find_package(TreelandProtocols 0.6.0 REQUIRED) + +# --- New protocols (treeland-protocols 0.6.0) --- +waylib_generate_qtwayland_server_protocol(libtreeland + PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-appearance-unstable-v1.xml + BASENAME treeland-appearance-unstable-v1 +) + +waylib_generate_qtwayland_server_protocol(libtreeland + PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-appearance-manager-unstable-v1.xml + BASENAME treeland-appearance-manager-unstable-v1 +) + +set(PUBLIC_HEADERS + ${CMAKE_SOURCE_DIR}/src/modules/appearance/appearanceinterfacev1.h + ${CMAKE_SOURCE_DIR}/src/modules/appearance/appearancemanagerinterfacev1.h +) + +set(SRCS + ${CMAKE_SOURCE_DIR}/src/modules/appearance/appearanceinterfacev1.cpp + ${CMAKE_SOURCE_DIR}/src/modules/appearance/appearancemanagerinterfacev1.cpp +) + +impl_treeland( + NAME + module_appearance + SOURCE + ${PUBLIC_HEADERS} + ${SRCS} + INCLUDE + $ +) + +if(TREELAND_INSTALL_DEV) + install(FILES ${PUBLIC_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/treeland/modules/appearance") +endif() diff --git a/src/modules/appearance/appearanceinterfacev1.cpp b/src/modules/appearance/appearanceinterfacev1.cpp new file mode 100644 index 0000000000..d81c5e3967 --- /dev/null +++ b/src/modules/appearance/appearanceinterfacev1.cpp @@ -0,0 +1,284 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "appearanceinterfacev1.h" + +#include "qwayland-server-treeland-appearance-unstable-v1.h" + +#include "common/treelandlogging.h" +#include "seat/helper.h" +#include "treelanduserconfig.hpp" + +#include + +#include + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +namespace { + +/// DConfig windowColorScheme (0=light, 1=dark) → protocol color_scheme (0=light, 1=dark) +/// Values are aligned, so this only validates and forwards. +uint32_t dconfigToColorScheme(int32_t type) +{ + switch (type) { + case TREELAND_APPEARANCE_V1_COLOR_SCHEME_LIGHT: + case TREELAND_APPEARANCE_V1_COLOR_SCHEME_DARK: + return static_cast(type); + default: + qCWarning(lcTlConfig) << "Unknown dconfig windowColorScheme:" << type + << ", fallback to light."; + return TREELAND_APPEARANCE_V1_COLOR_SCHEME_LIGHT; + } +} + +/// Parse "#RRGGBB" hex string to RGB components. +struct Rgb { uint32_t r, g, b; }; +Rgb hexToRgb(const QString &hex) +{ + QColor c(hex); + if (!c.isValid()) { + qCWarning(lcTlConfig) << "Invalid color string:" << hex; + return {0, 0, 0}; + } + return {static_cast(c.red()), + static_cast(c.green()), + static_cast(c.blue())}; +} + +} // namespace + +// --------------------------------------------------------------------------- +// AppearanceInterfaceV1Private +// --------------------------------------------------------------------------- + +class AppearanceInterfaceV1Private + : public QtWaylandServer::treeland_appearance_v1 +{ +public: + explicit AppearanceInterfaceV1Private(AppearanceInterfaceV1 *_q); + ~AppearanceInterfaceV1Private() override = default; + + wl_global *global() const { return m_global; } + + AppearanceInterfaceV1 *q = nullptr; + +protected: + void destroy(Resource *resource) override; + void bind_resource(Resource *resource) override; +}; + +AppearanceInterfaceV1Private::AppearanceInterfaceV1Private(AppearanceInterfaceV1 *_q) + : QtWaylandServer::treeland_appearance_v1() + , q(_q) +{ +} + +void AppearanceInterfaceV1Private::destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +void AppearanceInterfaceV1Private::bind_resource(Resource *resource) +{ + // Push model: send the current value of every setting immediately on bind. + // If async config initialization hasn't finished (or failed), the getters + // return DConfig default values, so push those instead of skipping. + q->sendAll(resource->handle); +} + +// --------------------------------------------------------------------------- +// AppearanceInterfaceV1 +// --------------------------------------------------------------------------- + +struct AppearanceInterfaceV1::Private +{ + std::unique_ptr server; + QList configConnections; +}; + +AppearanceInterfaceV1::AppearanceInterfaceV1(QObject *parent) + : QObject(parent) + , WServerInterface() + , d(std::make_unique()) +{ + d->server = std::make_unique(this); + + // In production this interface is attached inside Helper::init(), so + // Helper::instance() is always valid here. Unit tests, however, attach it + // to a bare WServer with no Helper singleton; guard against a null + // Helper to avoid passing a nullptr to QObject::connect and dereferencing + // it inside setupConfigConnections(). + auto *helper = Helper::instance(); + if (!helper) + return; + + // The TreelandUserConfig object is replaced in Helper::init when the + // current user changes. Reconnect to the new config's signals whenever + // that happens, and push the new values to all bound clients. + connect(helper, &Helper::configChanged, this, &AppearanceInterfaceV1::onConfigChanged); + + // Also set up connections for the initial config (available in the + // Helper constructor, though it may be replaced later). + setupConfigConnections(); +} + +AppearanceInterfaceV1::~AppearanceInterfaceV1() +{ + for (const auto &conn : std::as_const(d->configConnections)) + QObject::disconnect(conn); +} + +void AppearanceInterfaceV1::onConfigChanged() +{ + // FIXME(rewine): Treeland is a multi-user compositor. Helper::configChanged fires + // on user switching, and the config instance it exposes afterwards + // belongs to the NEW user. Ideally only the new user's clients should + // receive the new user's values here; clients still connected under other + // users currently get values they are not entitled to. We must fix this + // once per-user resource tracking is available (e.g. track resources per + // client credentials / security context and push only to those). + // + // Current behavior: push to ALL bound clients unconditionally. If the new + // config hasn't finished async initialization, getters return DConfig + // default values, which is still better than stale values from the old + // config; later change signals will push actual values. + setupConfigConnections(); + for (const auto &resource : d->server->resourceMap()) + sendAll(resource->handle); +} + +void AppearanceInterfaceV1::setupConfigConnections() +{ + // Disconnect from the previous config object's signals. + for (const auto &conn : std::as_const(d->configConnections)) + QObject::disconnect(conn); + d->configConnections.clear(); + + auto *config = Helper::instance()->config(); + if (!config) + return; + + // Settings change signals. Initialization completion is not watched: + // bind_resource() and onConfigChanged() push values unconditionally, and + // uninitialized getters return DConfig defaults. + d->configConnections.append( + connect(config, &TreelandUserConfig::cursorThemeNameChanged, this, [this] { + const auto name = Helper::instance()->config()->cursorThemeName(); + for (const auto &resource : d->server->resourceMap()) + d->server->send_cursor_theme(resource->handle, name); + })); + d->configConnections.append( + connect(config, &TreelandUserConfig::cursorSizeChanged, this, [this] { + const auto size = Helper::instance()->config()->cursorSize(); + for (const auto &resource : d->server->resourceMap()) + d->server->send_cursor_size(resource->handle, size, size); + })); + d->configConnections.append( + connect(config, &TreelandUserConfig::fontChanged, this, [this] { + const auto font = Helper::instance()->config()->font(); + for (const auto &resource : d->server->resourceMap()) + d->server->send_font(resource->handle, font); + })); + d->configConnections.append( + connect(config, &TreelandUserConfig::monoFontChanged, this, [this] { + const auto font = Helper::instance()->config()->monoFont(); + for (const auto &resource : d->server->resourceMap()) + d->server->send_monospace_font(resource->handle, font); + })); + d->configConnections.append( + connect(config, &TreelandUserConfig::fontSizeChanged, this, [this] { + const auto size = static_cast(Helper::instance()->config()->fontSize()); + for (const auto &resource : d->server->resourceMap()) + d->server->send_font_size(resource->handle, size); + })); + d->configConnections.append( + connect(config, &TreelandUserConfig::iconThemeNameChanged, this, [this] { + const auto theme = Helper::instance()->config()->iconThemeName(); + for (const auto &resource : d->server->resourceMap()) + d->server->send_icon_theme(resource->handle, theme); + })); + d->configConnections.append( + connect(config, &TreelandUserConfig::activeColorChanged, this, [this] { + const auto rgb = hexToRgb(Helper::instance()->config()->activeColor()); + for (const auto &resource : d->server->resourceMap()) + d->server->send_accent_color(resource->handle, rgb.r, rgb.g, rgb.b); + })); + d->configConnections.append( + connect(config, &TreelandUserConfig::windowOpacityChanged, this, [this] { + const auto opacity = wl_fixed_from_double( + Helper::instance()->config()->windowOpacity() / 100.0); + for (const auto &resource : d->server->resourceMap()) + d->server->send_window_opacity(resource->handle, opacity); + })); + d->configConnections.append( + connect(config, &TreelandUserConfig::windowColorSchemeChanged, this, [this] { + const auto scheme = dconfigToColorScheme( + Helper::instance()->config()->windowColorScheme()); + for (const auto &resource : d->server->resourceMap()) + d->server->send_color_scheme(resource->handle, scheme); + })); + d->configConnections.append( + connect(config, &TreelandUserConfig::windowTitlebarHeightChanged, this, [this] { + const auto height = static_cast( + Helper::instance()->config()->windowTitlebarHeight()); + for (const auto &resource : d->server->resourceMap()) + d->server->send_titlebar_height(resource->handle, height); + })); + d->configConnections.append( + connect(config, &TreelandUserConfig::windowRadiusChanged, this, [this] { + const auto radius = static_cast( + Helper::instance()->config()->windowRadius()); + for (const auto &resource : d->server->resourceMap()) + d->server->send_corner_radius(resource->handle, radius); + })); +} + +void AppearanceInterfaceV1::sendAll(wl_resource *resource) +{ + auto *config = Helper::instance()->config(); + if (!config) + return; + + d->server->send_cursor_theme(resource, config->cursorThemeName()); + const int cursorSize = config->cursorSize(); + d->server->send_cursor_size(resource, cursorSize, cursorSize); + d->server->send_font(resource, config->font()); + d->server->send_monospace_font(resource, config->monoFont()); + d->server->send_font_size(resource, static_cast(config->fontSize())); + d->server->send_icon_theme(resource, config->iconThemeName()); + + const auto rgb = hexToRgb(config->activeColor()); + d->server->send_accent_color(resource, rgb.r, rgb.g, rgb.b); + + d->server->send_window_opacity(resource, + wl_fixed_from_double(config->windowOpacity() / 100.0)); + + d->server->send_color_scheme(resource, dconfigToColorScheme(config->windowColorScheme())); + d->server->send_titlebar_height(resource, + static_cast(config->windowTitlebarHeight())); + d->server->send_corner_radius(resource, + static_cast(config->windowRadius())); +} + +QByteArrayView AppearanceInterfaceV1::interfaceName() const +{ + return d->server->interfaceName(); +} + +void AppearanceInterfaceV1::create(WServer *server) +{ + d->server->init(server->handle(), InterfaceVersion); +} + +void AppearanceInterfaceV1::destroy([[maybe_unused]] WServer *server) +{ + d->server->globalRemove(); +} + +wl_global *AppearanceInterfaceV1::global() const +{ + return d->server->global(); +} diff --git a/src/modules/appearance/appearanceinterfacev1.h b/src/modules/appearance/appearanceinterfacev1.h new file mode 100644 index 0000000000..76e355e477 --- /dev/null +++ b/src/modules/appearance/appearanceinterfacev1.h @@ -0,0 +1,51 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include + +#include +#include + +class TreelandUserConfig; + +WAYLIB_SERVER_USE_NAMESPACE + +/** + * @brief Server implementation of treeland_appearance_unstable_v1. + * + * Read-only global that pushes user-level appearance settings to all + * bound clients. On bind the current value of every setting is sent + * immediately; afterwards events are broadcast whenever a setting + * changes (either via the privileged AppearanceManagerInterfaceV1 or + * via DConfig). + */ +class AppearanceInterfaceV1 : public QObject, public WServerInterface +{ + Q_OBJECT + QML_ANONYMOUS + +public: + explicit AppearanceInterfaceV1(QObject *parent = nullptr); + ~AppearanceInterfaceV1() override; + + QByteArrayView interfaceName() const override; + + /// Push the current value of every setting to @p resource. + void sendAll(wl_resource *resource); + + static constexpr int InterfaceVersion = 1; + +protected: + void create(WServer *server) override; + void destroy(WServer *server) override; + wl_global *global() const override; + +private: + void setupConfigConnections(); + void onConfigChanged(); + + struct Private; + std::unique_ptr d; +}; diff --git a/src/modules/appearance/appearancemanagerinterfacev1.cpp b/src/modules/appearance/appearancemanagerinterfacev1.cpp new file mode 100644 index 0000000000..61ca339393 --- /dev/null +++ b/src/modules/appearance/appearancemanagerinterfacev1.cpp @@ -0,0 +1,245 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "appearancemanagerinterfacev1.h" + +#include "qwayland-server-treeland-appearance-manager-unstable-v1.h" + +#include "common/treelandlogging.h" +#include "seat/helper.h" +#include "treelanduserconfig.hpp" + +#include + +#include +#include + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +namespace { + +/// Protocol color_scheme (0=light, 1=dark) → DConfig windowColorScheme (0=light, 1=dark) +/// Values are aligned, so this only validates and forwards. +int32_t colorSchemeToDConfig(uint32_t scheme) +{ + switch (scheme) { + case TREELAND_APPEARANCE_MANAGER_V1_COLOR_SCHEME_LIGHT: + case TREELAND_APPEARANCE_MANAGER_V1_COLOR_SCHEME_DARK: + return static_cast(scheme); + default: + qCWarning(lcTlConfig) << "Unknown color_scheme:" << scheme + << ", ignoring."; + return -1; // caller should check + } +} + +/// Convert RGB components to "#RRGGBB" hex string for DConfig. +QString rgbToHex(uint32_t r, uint32_t g, uint32_t b) +{ + QColor c(static_cast(r), static_cast(g), static_cast(b)); + return c.name(QColor::HexRgb); // #RRGGBB +} + +/// Check whether the user config has completed asynchronous initialization. +bool isUserConfigInitialized(TreelandUserConfig *config) +{ + if (!config) + return false; + return config->isInitializeSucceeded(); +} + +/// Guard against null Helper, null config, or incomplete async config init. +/// +/// FIXME(rewine): Treeland is a multi-user compositor. Helper::config() exposes the +/// CURRENT user's config instance; this manager currently has no per-user +/// resource tracking, so a request from one user's client could mutate another +/// user's config once per-user client credentials are available. Binding must be +/// restricted to the privileged control center of the active user, and set_* +/// requests must be validated against the requester's user identity. +/// +/// During the async init window (before configInitializeSucceed), setters are +/// dropped silently to avoid writing before DConfig is ready; log once per drop +/// so the failure is observable. +#define ENSURE_CONFIG() \ + do { \ + if (!Helper::instance() || !Helper::instance()->config()) \ + return; \ + if (!isUserConfigInitialized(Helper::instance()->config())) { \ + qCWarning(lcTlConfig) << "Appearance set_* request dropped:" \ + << "user config not initialized yet"; \ + return; \ + } \ + } while (false) + +} // namespace + +// --------------------------------------------------------------------------- +// AppearanceManagerInterfaceV1Private +// --------------------------------------------------------------------------- + +class AppearanceManagerInterfaceV1Private + : public QtWaylandServer::treeland_appearance_manager_v1 +{ +public: + explicit AppearanceManagerInterfaceV1Private(AppearanceManagerInterfaceV1 *_q); + ~AppearanceManagerInterfaceV1Private() override = default; + + wl_global *global() const { return m_global; } + + AppearanceManagerInterfaceV1 *q = nullptr; + +protected: + void destroy(Resource *resource) override; + void set_cursor_theme(Resource *resource, const QString &name) override; + void set_cursor_size(Resource *resource, uint32_t width, uint32_t height) override; + void set_font(Resource *resource, const QString &font_name) override; + void set_monospace_font(Resource *resource, const QString &font_name) override; + void set_font_size(Resource *resource, uint32_t size) override; + void set_icon_theme(Resource *resource, const QString &theme_name) override; + void set_accent_color(Resource *resource, uint32_t r, uint32_t g, uint32_t b) override; + void set_window_opacity(Resource *resource, wl_fixed_t opacity) override; + void set_color_scheme(Resource *resource, uint32_t scheme) override; + void set_titlebar_height(Resource *resource, uint32_t height) override; + void set_corner_radius(Resource *resource, uint32_t radius) override; +}; + +AppearanceManagerInterfaceV1Private::AppearanceManagerInterfaceV1Private(AppearanceManagerInterfaceV1 *_q) + : QtWaylandServer::treeland_appearance_manager_v1() + , q(_q) +{ +} + +void AppearanceManagerInterfaceV1Private::destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +void AppearanceManagerInterfaceV1Private::set_cursor_theme([[maybe_unused]] Resource *resource, + const QString &name) +{ + ENSURE_CONFIG(); + Helper::instance()->config()->setCursorThemeName(name); +} + +void AppearanceManagerInterfaceV1Private::set_cursor_size([[maybe_unused]] Resource *resource, + uint32_t width, uint32_t height) +{ + ENSURE_CONFIG(); + if (width != height) + qCWarning(lcTlConfig) << "Non-square cursor size requested:" << width << "x" << height + << ", height is ignored."; + Helper::instance()->config()->setCursorSize(static_cast(width)); +} + +void AppearanceManagerInterfaceV1Private::set_font([[maybe_unused]] Resource *resource, + const QString &font_name) +{ + ENSURE_CONFIG(); + Helper::instance()->config()->setFont(font_name); +} + +void AppearanceManagerInterfaceV1Private::set_monospace_font([[maybe_unused]] Resource *resource, + const QString &font_name) +{ + ENSURE_CONFIG(); + Helper::instance()->config()->setMonoFont(font_name); +} + +void AppearanceManagerInterfaceV1Private::set_font_size([[maybe_unused]] Resource *resource, + uint32_t size) +{ + ENSURE_CONFIG(); + Helper::instance()->config()->setFontSize(static_cast(size)); +} + +void AppearanceManagerInterfaceV1Private::set_icon_theme([[maybe_unused]] Resource *resource, + const QString &theme_name) +{ + ENSURE_CONFIG(); + Helper::instance()->config()->setIconThemeName(theme_name); +} + +void AppearanceManagerInterfaceV1Private::set_accent_color([[maybe_unused]] Resource *resource, + uint32_t r, uint32_t g, uint32_t b) +{ + ENSURE_CONFIG(); + Helper::instance()->config()->setActiveColor(rgbToHex(r, g, b)); +} + +void AppearanceManagerInterfaceV1Private::set_window_opacity([[maybe_unused]] Resource *resource, + wl_fixed_t opacity) +{ + ENSURE_CONFIG(); + const double value = wl_fixed_to_double(opacity); + if (value < 0.0 || value > 1.0) { + qCWarning(lcTlConfig) << "window_opacity out of range [0.0, 1.0]:" << value; + return; + } + Helper::instance()->config()->setWindowOpacity(static_cast(std::lround(value * 100.0))); +} + +void AppearanceManagerInterfaceV1Private::set_color_scheme([[maybe_unused]] Resource *resource, + uint32_t scheme) +{ + ENSURE_CONFIG(); + const auto dconfigType = colorSchemeToDConfig(scheme); + if (dconfigType < 0) + return; // invalid scheme, silently ignored per protocol spec + Helper::instance()->config()->setWindowColorScheme(dconfigType); + Helper::syncPaletteTypeWithWindowColorScheme(dconfigType); +} + +void AppearanceManagerInterfaceV1Private::set_titlebar_height([[maybe_unused]] Resource *resource, + uint32_t height) +{ + ENSURE_CONFIG(); + Helper::instance()->config()->setWindowTitlebarHeight(static_cast(height)); +} + +void AppearanceManagerInterfaceV1Private::set_corner_radius([[maybe_unused]] Resource *resource, + uint32_t radius) +{ + ENSURE_CONFIG(); + Helper::instance()->config()->setWindowRadius(static_cast(radius)); +} + +// --------------------------------------------------------------------------- +// AppearanceManagerInterfaceV1 +// --------------------------------------------------------------------------- + +struct AppearanceManagerInterfaceV1::Private +{ + std::unique_ptr server; +}; + +AppearanceManagerInterfaceV1::AppearanceManagerInterfaceV1(QObject *parent) + : QObject(parent) + , WServerInterface() + , d(std::make_unique()) +{ + d->server = std::make_unique(this); +} + +AppearanceManagerInterfaceV1::~AppearanceManagerInterfaceV1() = default; + +QByteArrayView AppearanceManagerInterfaceV1::interfaceName() const +{ + return d->server->interfaceName(); +} + +void AppearanceManagerInterfaceV1::create(WServer *server) +{ + d->server->init(server->handle(), InterfaceVersion); +} + +void AppearanceManagerInterfaceV1::destroy([[maybe_unused]] WServer *server) +{ + d->server->globalRemove(); +} + +wl_global *AppearanceManagerInterfaceV1::global() const +{ + return d->server->global(); +} diff --git a/src/modules/appearance/appearancemanagerinterfacev1.h b/src/modules/appearance/appearancemanagerinterfacev1.h new file mode 100644 index 0000000000..0446951d2b --- /dev/null +++ b/src/modules/appearance/appearancemanagerinterfacev1.h @@ -0,0 +1,44 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include + +#include +#include + +class TreelandUserConfig; + +WAYLIB_SERVER_USE_NAMESPACE + +/** + * @brief Server implementation of treeland_appearance_manager_unstable_v1. + * + * Privileged global that allows the control center / system settings + * to modify user-level appearance settings. Each set_* request applies + * the change immediately to DConfig and broadcasts the new value to all + * clients bound to AppearanceInterfaceV1. + */ +class AppearanceManagerInterfaceV1 : public QObject, public WServerInterface +{ + Q_OBJECT + QML_ANONYMOUS + +public: + explicit AppearanceManagerInterfaceV1(QObject *parent = nullptr); + ~AppearanceManagerInterfaceV1() override; + + QByteArrayView interfaceName() const override; + + static constexpr int InterfaceVersion = 1; + +protected: + void create(WServer *server) override; + void destroy(WServer *server) override; + wl_global *global() const override; + +private: + struct Private; + std::unique_ptr d; +}; diff --git a/src/modules/personalization/CMakeLists.txt b/src/modules/personalization/CMakeLists.txt index 931a8e91a2..54fb7f3c6d 100644 --- a/src/modules/personalization/CMakeLists.txt +++ b/src/modules/personalization/CMakeLists.txt @@ -1,5 +1,19 @@ +# ============================================================================ +# DEPRECATED: treeland-personalization-manager-v1 +# +# This module is frozen. The old treeland-personalization-manager-v1 protocol +# has been split into separate protocols in treeland-protocols 0.6.0: +# - treeland-appearance-unstable-v1 → src/modules/appearance/ +# - treeland-appearance-manager-unstable-v1 → src/modules/appearance/ +# - treeland-decoration-unstable-v1 → (not yet implemented) +# +# No new features will be added here. New code should use the new modules. +# This module is kept only for backward compatibility during the transition. +# ============================================================================ + find_package(TreelandProtocols REQUIRED) +# --- Deprecated protocol (kept for backward compatibility) --- waylib_generate_qtwayland_server_protocol(libtreeland PROTOCOL ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-personalization-manager-v1.xml BASENAME treeland-personalization-manager-v1 @@ -33,4 +47,6 @@ qt_add_resources(libtreeland "default_background" if(TREELAND_INSTALL_DEV) install(FILES ${PUBLIC_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/treeland/modules/personalization") + install(FILES ${CMAKE_SOURCE_DIR}/src/common/windowdecorations.h + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/treeland/common") endif() diff --git a/src/modules/personalization/personalizationmanagerinterfacev1.cpp b/src/modules/personalization/personalizationmanagerinterfacev1.cpp index 31d8f9b4ec..aace76fa31 100644 --- a/src/modules/personalization/personalizationmanagerinterfacev1.cpp +++ b/src/modules/personalization/personalizationmanagerinterfacev1.cpp @@ -2,6 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "personalizationmanagerinterfacev1.h" + +// DEPRECATED: This module is frozen. Use treeland-appearance-unstable-v1 and +// treeland-appearance-manager-unstable-v1 (src/modules/appearance/) instead. +// No new features will be added to this protocol. #include "surfacewrapper.h" #include "qwayland-server-treeland-personalization-manager-v1.h" @@ -35,13 +39,14 @@ static QList s_cursorContexts; static QList s_fontContexts; static QList s_appearanceContexts; -static std::optional protocolWindowThemeTypeToDConfig(uint32_t type) +// Protocol theme_type (auto=1, light=2, dark=4) → DConfig windowColorScheme (0=light, 1=dark) +static std::optional protocolThemeTypeToDConfig(uint32_t type) { switch (type) { case TREELAND_PERSONALIZATION_APPEARANCE_CONTEXT_V1_THEME_TYPE_LIGHT: - return 1; + return 0; case TREELAND_PERSONALIZATION_APPEARANCE_CONTEXT_V1_THEME_TYPE_DARK: - return 2; + return 1; case TREELAND_PERSONALIZATION_APPEARANCE_CONTEXT_V1_THEME_TYPE_AUTO: qCCritical(lcTlConfig) << "Protocol window theme type AUTO is not supported by dconfig."; @@ -52,16 +57,17 @@ static std::optional protocolWindowThemeTypeToDConfig(uint32_t type) } } -static uint32_t dconfigWindowThemeTypeToProtocol(int32_t type) +// DConfig windowColorScheme (0=light, 1=dark) → protocol theme_type (auto=1, light=2, dark=4) +static uint32_t dconfigToProtocolThemeType(int32_t type) { switch (type) { - case 1: + case 0: return TREELAND_PERSONALIZATION_APPEARANCE_CONTEXT_V1_THEME_TYPE_LIGHT; - case 2: + case 1: return TREELAND_PERSONALIZATION_APPEARANCE_CONTEXT_V1_THEME_TYPE_DARK; default: qCWarning(lcTlConfig) - << "Unknown dconfig windowThemeType:" << type << ", fallback to light."; + << "Unknown dconfig windowColorScheme:" << type << ", fallback to light."; return TREELAND_PERSONALIZATION_APPEARANCE_CONTEXT_V1_THEME_TYPE_LIGHT; } } @@ -856,10 +862,10 @@ void PersonalizationManagerInterfaceV1::onAppearanceContextCreated(Personalizati } }); connect(context, &PersonalizationAppearanceContextV1::windowThemeTypeChanged, this, [](uint32_t type) { - const auto dconfigType = protocolWindowThemeTypeToDConfig(type); + const auto dconfigType = protocolThemeTypeToDConfig(type); if (dconfigType.has_value()) { - Helper::instance()->config()->setWindowThemeType(*dconfigType); - Helper::syncPaletteTypeWithWindowThemeType(*dconfigType); + Helper::instance()->config()->setWindowColorScheme(*dconfigType); + Helper::syncPaletteTypeWithWindowColorScheme(*dconfigType); } for (auto *c : std::as_const(s_appearanceContexts)) { c->sendWindowThemeType(type); @@ -889,8 +895,8 @@ void PersonalizationManagerInterfaceV1::onAppearanceContextCreated(Personalizati }); connect(context, &PersonalizationAppearanceContextV1::requestWindowThemeType, context, [context] { - const auto protocolType = dconfigWindowThemeTypeToProtocol( - Helper::instance()->config()->windowThemeType()); + const auto protocolType = dconfigToProtocolThemeType( + Helper::instance()->config()->windowColorScheme()); context->sendWindowThemeType(protocolType); }); @@ -902,8 +908,8 @@ void PersonalizationManagerInterfaceV1::onAppearanceContextCreated(Personalizati context->sendIconTheme(Helper::instance()->config()->iconThemeName()); context->sendActiveColor(Helper::instance()->config()->activeColor()); context->sendWindowOpacity(Helper::instance()->config()->windowOpacity()); - context->sendWindowThemeType(dconfigWindowThemeTypeToProtocol( - Helper::instance()->config()->windowThemeType())); + context->sendWindowThemeType(dconfigToProtocolThemeType( + Helper::instance()->config()->windowColorScheme())); context->sendWindowTitlebarHeight(Helper::instance()->config()->windowTitlebarHeight()); } diff --git a/src/modules/personalization/personalizationmanagerinterfacev1.h b/src/modules/personalization/personalizationmanagerinterfacev1.h index 399a303c5b..a330d8c0e2 100644 --- a/src/modules/personalization/personalizationmanagerinterfacev1.h +++ b/src/modules/personalization/personalizationmanagerinterfacev1.h @@ -3,6 +3,10 @@ #pragma once +// DEPRECATED: This module is frozen. Use treeland-appearance-unstable-v1 and +// treeland-appearance-manager-unstable-v1 (src/modules/appearance/) instead. +// No new features will be added to this protocol. + #include #include @@ -11,26 +15,13 @@ #include #include +#include "common/windowdecorations.h" + class SurfaceWrapper; class PersonalizationManagerInterfaceV1; WAYLIB_SERVER_USE_NAMESPACE -struct Shadow -{ - int32_t radius; - QPoint offset; - QColor color; - bool operator==(const Shadow &other) const = default; -}; - -struct Border -{ - int32_t width; - QColor color; - bool operator==(const Border &other) const = default; -}; - class PersonalizationManagerInterfaceV1Private; class PersonalizationWindowContextV1Private; class PersonalizationWindowContextV1 : public QObject diff --git a/src/seat/helper.cpp b/src/seat/helper.cpp index 4b40dea43e..4c5119de01 100644 --- a/src/seat/helper.cpp +++ b/src/seat/helper.cpp @@ -44,6 +44,8 @@ #include "modules/keyboard-state-notify/keyboardstatenotifymanagerinterfacev1.h" #include "modules/output-manager/outputmanagement.h" #include "modules/personalization/personalizationmanagerinterfacev1.h" +#include "modules/appearance/appearanceinterfacev1.h" +#include "modules/appearance/appearancemanagerinterfacev1.h" #include "modules/resource/treelandremotesource.h" #include "modules/screensaver/screensaverinterfacev2.h" #include "modules/shortcut/shortcutcontroller.h" @@ -382,26 +384,26 @@ TreelandConfig *Helper::globalConfig() return m_globalConfig.get(); } -void Helper::syncPaletteTypeWithWindowThemeType(int32_t themeType) +void Helper::syncPaletteTypeWithWindowColorScheme(int32_t colorScheme) { auto *guiHelper = DTK_GUI_NAMESPACE::DGuiApplicationHelper::instance(); if (!guiHelper) { qCCritical(lcTlConfig) << "DGuiApplicationHelper instance not available, cannot sync " - "palette type with window theme type."; + "palette type with window color scheme."; return; } - qCDebug(lcTlConfig) << "Syncing palette type with window theme type:" << themeType; + qCDebug(lcTlConfig) << "Syncing palette type with window color scheme:" << colorScheme; - switch (themeType) { - case 2: + switch (colorScheme) { + case 1: guiHelper->setPaletteType(Dtk::Gui::DGuiApplicationHelper::DarkType); break; - case 1: + case 0: guiHelper->setPaletteType(Dtk::Gui::DGuiApplicationHelper::LightType); break; default: - qCWarning(lcTlConfig) << "Unknown windowThemeType:" << themeType + qCWarning(lcTlConfig) << "Unknown windowColorScheme:" << colorScheme << ", fallback to light."; guiHelper->setPaletteType(Dtk::Gui::DGuiApplicationHelper::LightType); break; @@ -1950,6 +1952,10 @@ void Helper::init(Treeland::Treeland *treeland) }); m_personalizationInterfaceV1 = m_server->attach(); + // New protocols (treeland-protocols 0.6.0) + m_appearanceInterfaceV1 = m_server->attach(); + m_appearanceManagerInterfaceV1 = m_server->attach(); + auto updateCurrentUser = [this] { m_config.reset(TreelandUserConfig::createByName("org.deepin.dde.treeland.user", "org.deepin.dde.treeland", @@ -1975,7 +1981,7 @@ void Helper::init(Treeland::Treeland *treeland) m_inputManager->setupSeatUserConfig(m_userModel->currentUserName()); auto onConfigInitialized = [this] { m_sessionManager->syncActiveSessionCursorSettings(); - syncPaletteTypeWithWindowThemeType(m_config->windowThemeType()); + syncPaletteTypeWithWindowColorScheme(m_config->windowColorScheme()); m_wallpaperManager->updateWallpaperConfig(); tryInitRemoteSource(); //TODO: Isolate workspaces for different users to prevent them from sharing the same one. diff --git a/src/seat/helper.h b/src/seat/helper.h index 8c4cf104a5..56dce00c28 100644 --- a/src/seat/helper.h +++ b/src/seat/helper.h @@ -107,6 +107,8 @@ class Output; class OutputManager; class OutputManagerV1; class PersonalizationManagerInterfaceV1; +class AppearanceInterfaceV1; +class AppearanceManagerInterfaceV1; class RootSurfaceContainer; class ScreensaverInterfaceV2; class SessionManager; @@ -159,7 +161,7 @@ class Helper : public WSeatEventFilter, public WAYLIB_SERVER_NAMESPACE::WObject explicit Helper(QObject *parent = nullptr); ~Helper() override; - static void syncPaletteTypeWithWindowThemeType(int32_t themeType); + static void syncPaletteTypeWithWindowColorScheme(int32_t colorScheme); enum class OutputMode { @@ -467,6 +469,8 @@ private Q_SLOTS: PointerConstraintsManager *m_pointerConstraintsManager = nullptr; ShortcutManagerV2 *m_shortcutManager = nullptr; PersonalizationManagerInterfaceV1 *m_personalizationInterfaceV1 = nullptr; + AppearanceInterfaceV1 *m_appearanceInterfaceV1 = nullptr; + AppearanceManagerInterfaceV1 *m_appearanceManagerInterfaceV1 = nullptr; WallpaperColorInterfaceV1 *m_wallpaperColorV1 = nullptr; WOutputManagerV1 *m_outputManager = nullptr; WXdgOutputManager *m_xwaylandOutputManager = nullptr; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bb4fb08e76..d1bba49d1e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,7 +4,7 @@ if(BUILD_TREELAND_PROTOCOL_TESTS) add_subdirectory(protocols) endif() -add_subdirectory(test_protocol_personalization) +add_subdirectory(test_protocol_appearance) add_subdirectory(test_protocol_primary-output) add_subdirectory(test_protocol_shortcut) add_subdirectory(test_protocol_virtual-output) diff --git a/tests/protocols/INDEX.md b/tests/protocols/INDEX.md index 67e49d9d27..d2747da6dd 100644 --- a/tests/protocols/INDEX.md +++ b/tests/protocols/INDEX.md @@ -33,6 +33,7 @@ Wayland 线上请求与事件;本文档规定发出请求后,测试必须观 | [keyboard-state-notify-unstable-v1](treeland-keyboard-state-notify-unstable-v1/README.md) | P | watcher 配置与空键盘/空 modifier 边界 | | [output-manager-v1](treeland-output-manager-v1/README.md) | I / P | 真实 `wl_output` 的 primary-output 链路 | | [personalization-manager-v1](treeland-personalization-manager-v1/README.md) | E / I | 个性化状态挂接到真实 wrapper;font/appearance 配置的 setter/getter 生产回读与恢复 | +| appearance-v1(冒烟测试) | I | appearance/manager 接口创建与名称校验(`tests/test_protocol_appearance`);无 C-client 协议测试 | 推送事件 payload、setter 请求、DConfig 广播与配置替换 | | [prelaunch-splash-v2](treeland-prelaunch-splash-v2/README.md) | I / E | splash 请求/关闭信号;生产 splash wrapper 创建、加入 workspace 与销毁 | | [screensaver-v1](treeland-screensaver-v1/README.md) | E / P | 真实 ext-idle 抑制生命周期 | | [shortcut-manager-v2](treeland-shortcut-manager-v2/README.md) | E / P | 聚焦窗口捕获与快捷键激活 | @@ -101,11 +102,14 @@ request stub 算作 request 覆盖;生成的 client-protocol 文件本身不 ### 未纳入上述覆盖率的 XML -以下 3 个 XML 仍由协议包提供,但当前没有对应的已注册生产测试 target,故不混入 185 的 +以下 5 个 XML 仍由协议包提供,但当前没有对应的已注册生产测试 target,故不混入 148 的 分母,也不能被视为“已覆盖”: | XML | request / event | 当前状态与缺口 | | --- | --- | --- | +| `treeland-appearance-unstable-v1` | — | 新增 0.6.0 协议;仅有冒烟测试(接口创建/名称校验),无 C-client 覆盖 | +| `treeland-appearance-manager-unstable-v1` | — | 新增 0.6.0 协议;仅有冒烟测试,无 C-client 覆盖 | +| `treeland-decoration-unstable-v1` | — | 0.6.0 新增协议;生产实现尚未注册,无测试 | | `treeland-prelaunch-splash-v1` | 2 / 0 | 已由 v2 取代;未验证 v1 compatibility global 或迁移策略 | | `treeland-shortcut-manager-v1` | 3 / 1 | 已由 v2 取代;未验证 v1 compatibility global、`shortcut` event | | `treeland-remote-subsurface-unstable-v1` | 8 / 3 | 无测试目录;export token、remote subsurface 创建、位置/堆叠、错误 event 与真实 scene 结果均未覆盖 | diff --git a/tests/protocols/treeland-personalization-manager-v1/setup.cpp b/tests/protocols/treeland-personalization-manager-v1/setup.cpp index 363beb76bb..b9bb3db2eb 100644 --- a/tests/protocols/treeland-personalization-manager-v1/setup.cpp +++ b/tests/protocols/treeland-personalization-manager-v1/setup.cpp @@ -25,7 +25,7 @@ struct ConfigSnapshot { QString iconTheme; QString activeColor; qlonglong windowOpacity; - qlonglong windowThemeType; + qlonglong windowColorScheme; qlonglong windowTitlebarHeight; }; @@ -89,7 +89,7 @@ extern "C" void personalization_snapshot_config(void *data) .iconTheme = config->iconThemeName(), .activeColor = config->activeColor(), .windowOpacity = config->windowOpacity(), - .windowThemeType = config->windowThemeType(), + .windowColorScheme = config->windowColorScheme(), .windowTitlebarHeight = config->windowTitlebarHeight(), }; g_configSnapshotValid = true; @@ -114,8 +114,8 @@ extern "C" void personalization_restore_config(void *) config->setIconThemeName(g_configSnapshot.iconTheme); config->setActiveColor(g_configSnapshot.activeColor); config->setWindowOpacity(g_configSnapshot.windowOpacity); - config->setWindowThemeType(g_configSnapshot.windowThemeType); + config->setWindowColorScheme(g_configSnapshot.windowColorScheme); config->setWindowTitlebarHeight(g_configSnapshot.windowTitlebarHeight); - Helper::syncPaletteTypeWithWindowThemeType(static_cast(g_configSnapshot.windowThemeType)); + Helper::syncPaletteTypeWithWindowColorScheme(static_cast(g_configSnapshot.windowColorScheme)); g_configSnapshotValid = false; } diff --git a/tests/test_protocol_appearance/CMakeLists.txt b/tests/test_protocol_appearance/CMakeLists.txt new file mode 100644 index 0000000000..115c7eab0c --- /dev/null +++ b/tests/test_protocol_appearance/CMakeLists.txt @@ -0,0 +1,19 @@ +find_package(Qt6 REQUIRED COMPONENTS Test) + +add_executable(test_protocol_appearance main.cpp) + +target_link_libraries(test_protocol_appearance + PRIVATE + libtreeland + Qt::Test +) + +add_test(NAME test_protocol_appearance COMMAND test_protocol_appearance) + +set_property(TEST test_protocol_appearance PROPERTY + ENVIRONMENT "QT_QPA_PLATFORM=offscreen" +) + +set_property(TEST test_protocol_appearance PROPERTY + TIMEOUT 3 +) diff --git a/tests/test_protocol_appearance/main.cpp b/tests/test_protocol_appearance/main.cpp new file mode 100644 index 0000000000..8fb089fe88 --- /dev/null +++ b/tests/test_protocol_appearance/main.cpp @@ -0,0 +1,81 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "modules/appearance/appearanceinterfacev1.h" +#include "modules/appearance/appearancemanagerinterfacev1.h" + +#include + +#include +#include + +class AppearanceTest : public QObject +{ + Q_OBJECT + + WAYLIB_SERVER_NAMESPACE::WServer *m_server = nullptr; + +public: + AppearanceTest(QObject *parent = nullptr) + : QObject(parent) + { + } + +private Q_SLOTS: + + void initTestCase() + { + m_server = new WAYLIB_SERVER_NAMESPACE::WServer(); + } + + // --- AppearanceInterfaceV1 (read-only push) --- + + void testCreateAppearance() + { + QVERIFY(m_server->attach(m_server) != nullptr); + } + + void verifyAppearance() + { + auto protocol = m_server->findChild(); + QVERIFY(protocol != nullptr); + } + + // --- AppearanceManagerInterfaceV1 (privileged setter) --- + + void testCreateAppearanceManager() + { + QVERIFY(m_server->attach(m_server) != nullptr); + } + + void verifyAppearanceManager() + { + auto protocol = m_server->findChild(); + QVERIFY(protocol != nullptr); + } + + // --- interfaceName sanity --- + + void testAppearanceInterfaceName() + { + auto *appearance = m_server->findChild(); + QVERIFY(appearance != nullptr); + QVERIFY(!appearance->interfaceName().isEmpty()); + } + + void testAppearanceManagerInterfaceName() + { + auto *manager = m_server->findChild(); + QVERIFY(manager != nullptr); + QVERIFY(!manager->interfaceName().isEmpty()); + } + + void cleanupTestCase() + { + m_server->deleteLater(); + m_server = nullptr; + } +}; + +QTEST_MAIN(AppearanceTest) +#include "main.moc" diff --git a/tests/test_protocol_personalization/CMakeLists.txt b/tests/test_protocol_personalization/CMakeLists.txt deleted file mode 100644 index bbd1a8d6b0..0000000000 --- a/tests/test_protocol_personalization/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -find_package(Qt6 REQUIRED COMPONENTS Test) - -add_executable(test_protocol_personalization main.cpp) - -target_link_libraries(test_protocol_personalization - PRIVATE - libtreeland - Qt::Test -) - -add_test(NAME test_protocol_personalization COMMAND test_protocol_personalization) - -set_property(TEST test_protocol_personalization PROPERTY - ENVIRONMENT "QT_QPA_PLATFORM=offscreen" -) - -set_property(TEST test_protocol_personalization PROPERTY - TIMEOUT 3 -) diff --git a/tests/test_protocol_personalization/main.cpp b/tests/test_protocol_personalization/main.cpp deleted file mode 100644 index b36223c849..0000000000 --- a/tests/test_protocol_personalization/main.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (C) 2024-2026 UnionTech Software Technology Co., Ltd. -// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "modules/personalization/personalizationmanagerinterfacev1.h" - -#include - -#include -#include - -class PersonalizationTest : public QObject -{ - Q_OBJECT - - WAYLIB_SERVER_NAMESPACE::WServer *m_server = nullptr; - -public: - PersonalizationTest(QObject *parent = nullptr) - : QObject(parent) - { - } - -private Q_SLOTS: - - void initTestCase() - { - m_server = new WAYLIB_SERVER_NAMESPACE::WServer(); - } - - void testCreate() - { - QVERIFY(m_server->attach(m_server) != nullptr); - } - - void verifyPersonalization() - { - auto protocol = m_server->findChild(); - QVERIFY(protocol != nullptr); - } - - void cleanupTestCase() - { - m_server->deleteLater(); - m_server = nullptr; - } -}; - -QTEST_MAIN(PersonalizationTest) -#include "main.moc"