From 213a3822714d786b8347317608235428da467634 Mon Sep 17 00:00:00 2001 From: zhangkun Date: Mon, 7 Sep 2026 18:57:33 +0800 Subject: [PATCH] feat(dde-blackwidget): support nodbus overlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Port the V20 black widget with DTK-managed single-instance startup, nodbus mode, and multi-screen X11 coverage. 2. Load the Plymouth logo and shutdown text from one smart-pointer-owned DConfig instance, with logo display enabled by default. 3. Install a Type=exec nodbus user service that remains active after graphical-session.target stops. 4. Use DTK platform detection for X11-specific window flags and input grabs while retaining explicit Treeland extension points. 5. Remove the obsolete gravity-rotate path and redundant platform-readiness wrappers. 6. Add REUSE metadata for DConfig JSON files and normalize new-file copyright years. Log: Add a nodbus shutdown overlay that displays Plymouth branding until handoff. Influence: Reduce the blank interval during shutdown. feat(dde-blackwidget): 支持 nodbus 关机遮罩 1. 迁移 V20 黑屏组件,使用 DTK 管理单实例启动,并支持 nodbus 模式和 X11 多屏覆盖。 2. 通过智能指针管理的单一 DConfig 实例加载 Plymouth logo 和关机文字,并默认显示 logo。 3. 安装 Type=exec nodbus 用户服务,并在 graphical-session.target 停止后继续运行。 4. 使用 DTK 平台检测处理 X11 窗口标志和输入抓取,同时保留明确的 Treeland 扩展入口。 5. 移除废弃的 gravity-rotate 路径和重复的平台就绪包装逻辑。 6. 为 DConfig JSON 文件补充 REUSE 元数据并规范新增文件版权年份。 Log: 新增 nodbus 关机遮罩,在 Plymouth 接管前显示品牌 logo。 PMS: TASK-395319 Influence: 减少关机过程中的纯黑空档。 Change-Id: I85a52a82648f9588a424e8e63f641712117dfaa0 --- REUSE.toml | 6 + common/CMakeLists.txt | 4 +- configs/org.deepin.dde.session.ui.json | 36 ++ dde-blackwidget/CMakeLists.txt | 16 +- .../user/dde-blackwidget-nodbus.service | 9 + dde-blackwidget/src/main.cpp | 85 +--- dde-blackwidget/src/window.cpp | 464 ++++++++++++++++-- dde-blackwidget/src/window.h | 40 +- 8 files changed, 539 insertions(+), 121 deletions(-) create mode 100644 configs/org.deepin.dde.session.ui.json create mode 100644 dde-blackwidget/misc/systemd/user/dde-blackwidget-nodbus.service diff --git a/REUSE.toml b/REUSE.toml index 3b37d850..5a1588a3 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -81,6 +81,12 @@ precedence = "aggregate" SPDX-FileCopyrightText = "UnionTech Software Technology Co., Ltd." SPDX-License-Identifier = "GPL-3.0-or-later" +[[annotations]] +path = "configs/*.json" +precedence = "aggregate" +SPDX-FileCopyrightText = "None" +SPDX-License-Identifier = "CC0-1.0" + [[annotations]] path = ["dde-osd/files/dde-osd.desktop", "deepin-login-reminder/deepin-login-reminder.desktop", "deepin-login-reminder/dconf/org.deepin.login-reminder.json", "misc/applications/*.desktop"] precedence = "aggregate" diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index ad231600..1b3ba8ee 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +# SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd. # # SPDX-License-Identifier: GPL-3.0-or-later @@ -8,7 +8,7 @@ set(Common_SRCS ) add_library(session-ui-common-shared OBJECT - ${Widgets_SRCS} + ${Common_SRCS} ) target_include_directories(session-ui-common-shared diff --git a/configs/org.deepin.dde.session.ui.json b/configs/org.deepin.dde.session.ui.json new file mode 100644 index 00000000..7c089d29 --- /dev/null +++ b/configs/org.deepin.dde.session.ui.json @@ -0,0 +1,36 @@ +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "blackWidgetShutdownText": { + "value": "", + "serial": 0, + "flags": ["global"], + "name": "blackWidgetShutdownText", + "name[zh_CN]": "nodbus关机模式时,显示的关机文字", + "description": "nodbus关机模式时,显示的关机文字", + "permissions": "readonly", + "visibility": "private" + }, + "blackWidgetLogoVisible": { + "value": true, + "serial": 0, + "flags": ["global"], + "name": "blackWidgetLogoVisible", + "name[zh_CN]": "关机黑屏是否显示logo", + "description": "关机黑屏是否显示logo", + "permissions": "readwrite", + "visibility": "private" + }, + "blackWidgetQuitInterval": { + "value": 10, + "serial": 0, + "flags": ["global"], + "name": "blackWidgetQuitInterval", + "name[zh_CN]": "自动退出的时间,单位秒", + "description": "自动退出的时间,单位秒", + "permissions": "readwrite", + "visibility": "private" + } + } +} diff --git a/dde-blackwidget/CMakeLists.txt b/dde-blackwidget/CMakeLists.txt index f697a15a..1314f5f8 100644 --- a/dde-blackwidget/CMakeLists.txt +++ b/dde-blackwidget/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +# SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. # # SPDX-License-Identifier: GPL-3.0-or-later @@ -8,15 +8,22 @@ file(GLOB_RECURSE Blackwidget_SRCS "src/*.cpp" ) +pkg_check_modules(X11 REQUIRED x11) +find_package(Dtk${DTK_VERSION_MAJOR}DConfig REQUIRED) + add_executable(${Blackwidget_Name} ${Blackwidget_SRCS} ) set(Blackwidget_Includes - ${GSETTINGS_INCLUDE_DIRS} + ${GLIB_INCLUDE_DIRS} + ${GIO_INCLUDE_DIRS} ) set(Blackwidget_Libraries - ${GSETTINGS_LIBRARIES} + ${GLIB_LIBRARIES} + ${GIO_LIBRARIES} + ${X11_LIBRARIES} Dtk${DTK_VERSION_MAJOR}::Widget + Dtk${DTK_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::DBus ) @@ -34,3 +41,6 @@ install(TARGETS ${Blackwidget_Name} DESTINATION lib/deepin-daemon) ## service install(FILES misc/dbus-services/org.deepin.dde.BlackScreen1.service DESTINATION share/dbus-1/services) install(FILES misc/systemd/user/dde-blackwidget.service DESTINATION ${SYSTEMD_USER_UNIT_DIR}) +install(FILES misc/systemd/user/dde-blackwidget-nodbus.service DESTINATION ${SYSTEMD_USER_UNIT_DIR}) +## dconfig +dtk_add_config_meta_files(APPID org.deepin.dde.session.ui FILES ${CMAKE_SOURCE_DIR}/configs/org.deepin.dde.session.ui.json) diff --git a/dde-blackwidget/misc/systemd/user/dde-blackwidget-nodbus.service b/dde-blackwidget/misc/systemd/user/dde-blackwidget-nodbus.service new file mode 100644 index 00000000..abc90ae9 --- /dev/null +++ b/dde-blackwidget/misc/systemd/user/dde-blackwidget-nodbus.service @@ -0,0 +1,9 @@ +[Unit] +Description=Deepin Black Widget No-DBus Mode +After=graphical-session.target + +[Service] +Type=exec +ExecStart=/usr/lib/deepin-daemon/dde-blackwidget nodbus +KillSignal=SIGTERM +TimeoutStopSec=3s diff --git a/dde-blackwidget/src/main.cpp b/dde-blackwidget/src/main.cpp index 7b1f55d6..96efdf46 100644 --- a/dde-blackwidget/src/main.cpp +++ b/dde-blackwidget/src/main.cpp @@ -4,98 +4,46 @@ #include "window.h" -#include #include +#include #include #include #include -#include #include #include #include -#include const QString DBUS_SERV = "org.deepin.dde.BlackScreen1"; const QString DBUS_PATH = "/org/deepin/dde/BlackScreen1"; -const QString DBUS_IFCE = "org.deepin.dde.BlackScreen1"; -const QString KWIN_INTERFACE_NAME = "org.kde.KWin"; - -bool onPreparingForShutdown() { - QDBusInterface iface( - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - QDBusConnection::systemBus() - ); - QVariant preparingForShutdown = iface.property("PreparingForShutdown"); - - if (preparingForShutdown.isValid()) { - bool isPreparing = preparingForShutdown.toBool(); - qDebug() << "Preparing for shutdown property value:" << isPreparing; - return isPreparing; - } else { - qWarning() << "Failed to retrieve preparing for shutdown property, the property is invalid"; - } - return false; -} - -void handleSIGTERM(int signal) { - if (QCoreApplication::closingDown()) - return; - qInfo() << "handleSIGTERM: " << signal; - // 信号处理会阻塞进程,dbus调用也会被阻塞,因此等待一会自动退出吧 - // 这个等待时间已经够长了,足够显示关机界面了,如果还有显示问题,应该从其他地方查找问题来优化。 - QTimer::singleShot(2500, qApp, SLOT(quit())); -} int main(int argc, char *argv[]) { - // dde-blackwidet should not try reconnect when kwin_wayland crash - qunsetenv("QT_WAYLAND_RECONNECT"); - - if (!qgetenv("WAYLAND_DISPLAY").isEmpty()) { - auto connection = QDBusConnection::sessionBus(); - // should not start when kwin offline - if (!connection.interface()->isServiceRegistered(KWIN_INTERFACE_NAME)) { - qDebug() << "blackwidget return 0, for dbus not registered: " << KWIN_INTERFACE_NAME; - return 0; - } - } - QApplication a(argc, argv); - a.setOrganizationName("deepin"); - a.setApplicationName("dde-blackwidget"); - a.setDesktopFileName(QStringLiteral("org.deepin.dde.blackwidget")); - + if (!Dtk::Gui::DGuiApplicationHelper::setSingleInstance(QStringLiteral("dde-blackwidget"))) { + qInfo() << "dde-blackwidget already running, quit."; + return 0; + } +#ifdef QT_DEBUG DCORE_USE_NAMESPACE::Dtk::Core::DLogManager::registerConsoleAppender(); - - // 默认日志路径是 ~/.cache/dde-blackwidget/dde-blackwidget.log - DCORE_USE_NAMESPACE::Dtk::Core::DLogManager::registerFileAppender(); +#endif DCORE_USE_NAMESPACE::Dtk::Core::DLogManager::registerJournalAppender(); - signal(SIGTERM, handleSIGTERM); - bool useDBus = true; QCommandLineParser parser; parser.addHelpOption(); - parser.process(a); - int size = parser.positionalArguments().size(); - for (int i = 0; i < size; i++) { - QString argument = parser.positionalArguments()[i]; - if (argument == "nodbus") { - useDBus = false; - break; - } - } + parser.process(a); + const bool useDBus = !parser.positionalArguments().contains(QStringLiteral("nodbus")); Window w; - w.setTimer(); qInfo() << " Black screen use dbus : " << useDBus; if (!useDBus) { qInfo() << "Direct to launch black widget."; + w.setLogoVisible(true); w.raiseWindow(); - return a.exec(); + int ret = a.exec(); + w.cleanupBeforeExit(); + return ret; } QDBusConnection sessionDBus = QDBusConnection::sessionBus(); @@ -107,12 +55,15 @@ int main(int argc, char *argv[]) BlackWidgetAdaptor adaptor(&w); if (result) { - qInfo() << "Session register object : " << sessionDBus.registerObject("/org/deepin/dde/BlackScreen1", &w); + qInfo() << "Session register object : " << sessionDBus.registerObject(DBUS_PATH, &w); QObject::connect(sessionDBus.interface(), &QDBusConnectionInterface::serviceUnregistered, &w, &Window::onNameLost); } else { qInfo() << "End black widget."; return -1; } - return a.exec(); + int ret = a.exec(); + adaptor.quitDBusService(); + w.cleanupBeforeExit(); + return ret; } diff --git a/dde-blackwidget/src/window.cpp b/dde-blackwidget/src/window.cpp index d508566a..e55c910c 100644 --- a/dde-blackwidget/src/window.cpp +++ b/dde-blackwidget/src/window.cpp @@ -1,94 +1,372 @@ -// SPDX-FileCopyrightText: 2015 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2015 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later #include "window.h" #include +#include +#include #include +#include +#include +#include +#include #include #include #include #include +#include #include +#define Window X11Window +#include +#undef Window +#include + #include +#include +#include + +namespace { + +const QString defaultLogoPath = QStringLiteral("/usr/share/deepin/uos_logo.svg"); + +const QString dsgQuitInterval = "blackWidgetQuitInterval"; + +const QString dsgShutdownText = "blackWidgetShutdownText"; + +const QString dsgBlackWidgetLogoVisible = "blackWidgetLogoVisible"; + +const QString dsgPath = "org.deepin.dde.session.ui"; + +const int shutdownTextBottomMargin = 260; + +bool isValidThemeName(const QString &theme) +{ + return !theme.isEmpty() + && !theme.contains(QStringLiteral("..")) + && !theme.contains(QLatin1Char('/')) + && !theme.contains(QLatin1Char('\\')); +} + +QString plymouthLogoPath() +{ + const QString configPath = QStringLiteral("/etc/plymouth/plymouthd.conf"); + if (!QFileInfo(configPath).isFile()) { + qWarning() << "plymouth config is missing, use default logo:" << configPath; + return defaultLogoPath; + } + + QSettings settings(configPath, QSettings::IniFormat); + QString theme = settings.value(QStringLiteral("Daemon/Theme")).toString().trimmed(); + if (theme.isEmpty()) { + theme = settings.value(QStringLiteral("Theme")).toString().trimmed(); + } + if (theme.isEmpty()) { + qWarning() << "plymouth theme is empty, use default logo"; + return defaultLogoPath; + } + if (!isValidThemeName(theme)) { + qWarning() << "plymouth theme is invalid, use default logo:" << theme; + return defaultLogoPath; + } + + const QString themePath = QStringLiteral("/usr/share/plymouth/themes/%1").arg(theme); + const QString logoPath = themePath + QStringLiteral("/logo.png"); + if (!QFileInfo(themePath).isDir() || !QFileInfo(logoPath).isFile()) { + qWarning() << "plymouth logo is missing, use default logo:" << logoPath; + return defaultLogoPath; + } + + return logoPath; +} + +Display *x11Display() +{ + auto *x11App = qGuiApp->nativeInterface(); + return x11App ? x11App->display() : nullptr; +} + +bool isX11Platform() +{ + static const bool isX11 = Dtk::Gui::DGuiApplicationHelper::isXWindowPlatform(); + return isX11; +} + +} Window::Window(QWidget *parent) : QWidget(parent) , m_clickCount(0) , m_timer(new QTimer(this)) - , m_gravityRotateBlackEnabled(false) + , m_dConfig(Dtk::Core::DConfig::create(dsgPath, dsgPath, QString(), nullptr)) + , m_logo(nullptr) + , m_shutdownTextLabel(nullptr) + , m_logoVisible(false) + , m_platformWindowInitialized(false) + , m_raisePending(false) + , m_nativeKeyboardGrabbed(false) { setAccessibleName("Window"); move(0, 0); + Qt::WindowFlags flags = Qt::WindowStaysOnTopHint | Qt::Tool | Qt::FramelessWindowHint; + if (isX11Platform()) { + flags |= Qt::X11BypassWindowManagerHint; + } else { + // Reserved for Treeland-specific window flags. + } + setWindowFlags(flags); + setFocusPolicy(Qt::StrongFocus); + + setAutoFillBackground(true); + QPalette pal(palette()); + pal.setColor(QPalette::Window, Qt::black); + setPalette(pal); + QCursor cursor(Qt::BlankCursor); + setCursor(cursor); +} - // set window flags as dde-lock, so we can easily cover it. - setWindowFlags(Qt::WindowStaysOnTopHint | Qt::Tool | Qt::WindowDoesNotAcceptFocus | Qt::FramelessWindowHint); +Window::~Window() +{ + qDeleteAll(m_screenWindows); +} - if (QGuiApplication::platformName().startsWith("wayland", Qt::CaseInsensitive)) { - setAttribute(Qt::WA_NativeWindow); - // 使用最高层级 - windowHandle()->setProperty("_d_dwayland_window-type", "override"); +QVariant Window::configValue(const QString &key, const QVariant &defaultValue) const +{ + return m_dConfig ? m_dConfig->value(key, defaultValue) : defaultValue; +} + +void Window::setLogoVisible(bool visible) +{ + const bool logoVisible = configValue(dsgBlackWidgetLogoVisible, false).toBool(); + if (!logoVisible) { + qInfo() << "dsg of blackWidgetLogoVisible is false"; + return; + } + m_logoVisible = visible; + if (visible && !m_logo) { + m_logo = new QLabel(this); + m_logo->setAccessibleName("BlackWidgetLogo"); + m_logo->setVisible(false); + QPixmap logoPixmap(plymouthLogoPath()); + if (logoPixmap.isNull()) { + qWarning() << "blackwidget logo pixmap is null"; + } else { + m_logo->setPixmap(logoPixmap); + } } - QCursor cursor(Qt::BlankCursor); - this->setCursor(cursor); - DTK_CORE_NAMESPACE::DConfig *dsgConfig = DTK_CORE_NAMESPACE::DConfig::create("org.deepin.dde.daemon", QString("org.deepin.Display")); - if (dsgConfig && dsgConfig->isValid() && dsgConfig->keyList().contains("gravity-rotate-black-enabled")) { - m_gravityRotateBlackEnabled = dsgConfig->value("gravity-rotate-black-enabled").toBool(); - dsgConfig->deleteLater(); + const bool hasLogo = m_logo && !m_logo->pixmap().isNull(); + if (m_logo) { + m_logo->setVisible(visible && hasLogo); + } + if (!isHidden()) { + setupSize(); } } -Window::~Window() +void Window::setupSize() { + updateScreenWindows(); + if (!m_logoVisible || !m_logo || m_logo->pixmap().isNull()) { + return; + } + const QPixmap pixmap = m_logo->pixmap(); + m_logo->setFixedSize(pixmap.size()); + m_logo->move(rect().center() - m_logo->rect().center()); + m_logo->raise(); + + const QString text = configValue(dsgShutdownText, QString()).toString(); + qInfo() << "blackwidget shutdown text:" << text; + if (text.isEmpty()) { + return; + } + if (!m_shutdownTextLabel) { + m_shutdownTextLabel = new QLabel(this); + m_shutdownTextLabel->setAccessibleName("BlackWidgetShutdownText"); + QPalette textPal = m_shutdownTextLabel->palette(); + textPal.setColor(QPalette::WindowText, QColor("#E6E6E6")); + m_shutdownTextLabel->setPalette(textPal); + } + m_shutdownTextLabel->setText(text); + m_shutdownTextLabel->adjustSize(); + const QPoint logoCenter = m_logo->geometry().center(); + m_shutdownTextLabel->move(logoCenter.x() - m_shutdownTextLabel->width() / 2, m_logo->y() + m_logo->height() + shutdownTextBottomMargin); + m_shutdownTextLabel->setVisible(true); + m_shutdownTextLabel->raise(); } -void Window::paintBackground(bool show) +void Window::updateScreenWindows() { - if (!show) + const QList screens = qApp->screens(); + if (screens.isEmpty()) { return; + } - if (m_gravityRotateBlackEnabled) { - setFixedSize(10000, 10000); - setVisible(true); - } else { - setupSize(); + QScreen *currentScreen = qApp->primaryScreen(); + if (!currentScreen) { + currentScreen = screens.first(); + qWarning() << "primary screen is null, use first screen:" << currentScreen; + } + + const QRect currentGeometry = currentScreen->geometry(); + if (geometry().topLeft() != currentGeometry.topLeft()) { + move(currentGeometry.topLeft()); + } + if (size() != currentGeometry.size()) { + setFixedSize(currentGeometry.size()); + } + + const int coverWindowCount = screens.size() - 1; + if (coverWindowCount <= 0) { + while (!m_screenWindows.isEmpty()) { + delete m_screenWindows.takeLast(); + } + return; + } + while (m_screenWindows.size() < coverWindowCount) { + QWidget *screenWindow = new QWidget(nullptr, Qt::WindowStaysOnTopHint | Qt::Tool | Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus); + screenWindow->setAccessibleName("BlackWidgetScreenWindow"); + screenWindow->setAttribute(Qt::WA_ShowWithoutActivating); + screenWindow->setAutoFillBackground(true); + QPalette screenPal(screenWindow->palette()); + screenPal.setColor(QPalette::Window, Qt::black); + screenWindow->setPalette(screenPal); + screenWindow->setCursor(QCursor(Qt::BlankCursor)); + Dtk::Widget::DPlatformWindowHandle handle(screenWindow, screenWindow); + handle.setWindowRadius(-1); + m_screenWindows.append(screenWindow); } - setStyleSheet("Window { background: black }"); + while (m_screenWindows.size() > coverWindowCount) { + delete m_screenWindows.takeLast(); + } + + int coverIndex = 0; + for (QScreen *screen : screens) { + if (screen == currentScreen) { + continue; + } + + QWidget *screenWindow = m_screenWindows.at(coverIndex++); + screenWindow->setGeometry(screen->geometry()); + screenWindow->setVisible(isVisible()); + if (!screenWindow->isHidden()) { + screenWindow->raise(); + } + } } -void Window::setupSize() +void Window::setScreenWindowsVisible(bool visible) +{ + for (QWidget *screenWindow : qAsConst(m_screenWindows)) { + screenWindow->setVisible(visible); + if (visible) { + screenWindow->raise(); + } + } +} + +bool Window::ensurePlatformReady() { - int totalWidth = 0; - int totalHeight = 0; - for (const QScreen *screen : qApp->screens()) { - totalWidth = qMax(totalWidth, screen->geometry().x() + screen->geometry().width()); - totalHeight = qMax(totalHeight, screen->geometry().y() + screen->geometry().height()); + if (qApp->screens().isEmpty()) { + return false; } - qInfo() << "Window setup size, totalWidth: "<< totalWidth << ", totalHeight: " << totalHeight; - setFixedSize(totalWidth, totalHeight); - setVisible(true); + if (isX11Platform() && !x11Display()) { + return false; + } + + return true; +} + +void Window::initializePlatformWindow() +{ + if (m_platformWindowInitialized) { + return; + } + + Dtk::Widget::DPlatformWindowHandle handle(this, this); + handle.setWindowRadius(-1); + m_platformWindowInitialized = true; +} + +void Window::scheduleRaiseWindow(int delay) +{ + if (m_raisePending) { + return; + } + + m_raisePending = true; + QTimer::singleShot(delay, this, [this] { + m_raisePending = false; + raiseWindow(); + }); } void Window::raiseWindow() { - paintBackground(); - raise(); - activateWindow(); - grabMouse(); - grabKeyboard(); + if (!ensurePlatformReady()) { + scheduleRaiseWindow(); + return; + } + + initializePlatformWindow(); + setupSize(); if (!isVisible()) { setVisible(true); } + raise(); + setScreenWindowsVisible(true); + activateWindow(); + setFocus(Qt::ActiveWindowFocusReason); + QTimer::singleShot(0, this, [this] { + if (!isVisible() || !ensurePlatformReady()) { + return; + } + + grabNativeKeyboard(); + grabMouse(); + grabKeyboard(); + }); +} + +bool Window::event(QEvent *event) +{ + switch (event->type()) { + case QEvent::Show: + if (isVisible()) { + setupSize(); + setScreenWindowsVisible(true); + grabNativeKeyboard(); + } + break; + case QEvent::WindowActivate: + if (isVisible()) { + grabNativeKeyboard(); + } + break; + case QEvent::Hide: + setScreenWindowsVisible(false); + break; + case QEvent::WindowDeactivate: + if (!isHidden()) { + scheduleRaiseWindow(30); + } + break; + default: + break; + } + + return QWidget::event(event); } -void Window::setTimer(int interval) +void Window::setTimer() { + const int interval = configValue(dsgQuitInterval, quitInterval / 1000).toInt() * 1000; + qInfo() << "blackwidget setTimer interval :" << interval; m_timer->setSingleShot(true); m_timer->start(interval); connect(m_timer, &QTimer::timeout, this, [] { @@ -96,10 +374,89 @@ void Window::setTimer(int interval) }); } +void Window::grabNativeKeyboard() +{ + if (m_nativeKeyboardGrabbed || isHidden()) { + return; + } + if (!isX11Platform()) { + // Reserved for Treeland input inhibition. + return; + } + + Display *display = x11Display(); + if (!display) { + return; + } + + const X11Window window = static_cast(winId()); + if (window == 0) { + return; + } + + XSync(display, False); + const int result = XGrabKeyboard(display, window, True, GrabModeAsync, GrabModeAsync, CurrentTime); + if (result == GrabSuccess) { + m_nativeKeyboardGrabbed = true; + XSync(display, False); + } +} + +void Window::releaseNativeKeyboard() +{ + if (!m_nativeKeyboardGrabbed) { + return; + } + + m_nativeKeyboardGrabbed = false; + if (!isX11Platform()) { + // Reserved for Treeland input release. + return; + } + + Display *display = x11Display(); + if (!display) { + return; + } + + XUngrabKeyboard(display, CurrentTime); + XSync(display, False); +} + +void Window::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + event->accept(); + return; + } + + QWidget::mousePressEvent(event); +} + +void Window::mouseMoveEvent(QMouseEvent *event) +{ + if (event->buttons() & Qt::LeftButton) { + event->accept(); + return; + } + + QWidget::mouseMoveEvent(event); +} + +void Window::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + event->accept(); + return; + } + + QWidget::mouseReleaseEvent(event); +} + void Window::mouseDoubleClickEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { - m_clickCount++; + ++m_clickCount; if (m_clickCount == 5) { setVisible(false); m_clickCount = 0; @@ -115,15 +472,35 @@ void Window::onNameLost(QString name) } } +void Window::cleanupBeforeExit() +{ + if (m_timer && m_timer->isActive()) { + m_timer->stop(); + } + + if (mouseGrabber() == this) { + releaseMouse(); + } + + if (keyboardGrabber() == this) { + releaseKeyboard(); + } + + releaseNativeKeyboard(); + setScreenWindowsVisible(false); + + if (isVisible()) { + hide(); + } +} + BlackWidgetAdaptor::BlackWidgetAdaptor(Window * parent) : QDBusAbstractAdaptor(parent) { - } BlackWidgetAdaptor::~BlackWidgetAdaptor() { - } Window *BlackWidgetAdaptor::parent() const @@ -160,7 +537,7 @@ void BlackWidgetAdaptor::setActive(bool visible) void BlackWidgetAdaptor::quitDBusService() { qInfo() << "Quit DBus service"; - QDBusConnection::sessionBus().unregisterObject("org.deepin.dde.BlackScreen1"); + QDBusConnection::sessionBus().unregisterObject("/org/deepin/dde/BlackScreen1"); QDBusConnection::sessionBus().unregisterService("org.deepin.dde.BlackScreen1"); } @@ -177,7 +554,7 @@ void BlackWidgetAdaptor::releaseGrabDevicesHideBlack() if (w) { releaseGrabDevices(); if (w->isVisible()) - w->setVisible(false); + w->setVisible(false); } } @@ -188,5 +565,6 @@ void BlackWidgetAdaptor::releaseGrabDevices() if (w) { w->releaseMouse(); w->releaseKeyboard(); + w->releaseNativeKeyboard(); } } diff --git a/dde-blackwidget/src/window.h b/dde-blackwidget/src/window.h index 7d3d4e47..3380e55d 100644 --- a/dde-blackwidget/src/window.h +++ b/dde-blackwidget/src/window.h @@ -1,17 +1,25 @@ -// SPDX-FileCopyrightText: 2015 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2015 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later #ifndef WINDOW_H #define WINDOW_H +#include + #include #include +#include +#include #include #include +#include + const int quitInterval = 10000; +class QLabel; + class Window : public QWidget { Q_OBJECT @@ -20,22 +28,42 @@ class Window : public QWidget explicit Window(QWidget *parent = nullptr); ~Window(); - void paintBackground(bool show = true); void setupSize(); - void setupUI(); + bool ensurePlatformReady(); + void initializePlatformWindow(); void raiseWindow(); - void setTimer(int interval = quitInterval); + void scheduleRaiseWindow(int delay = 50); + void setTimer(); + void setLogoVisible(bool visible); + void cleanupBeforeExit(); + void grabNativeKeyboard(); + void releaseNativeKeyboard(); protected: - void mouseDoubleClickEvent(QMouseEvent *event); + bool event(QEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; public Q_SLOTS: void onNameLost(QString name); private: + void updateScreenWindows(); + void setScreenWindowsVisible(bool visible); + QVariant configValue(const QString &key, const QVariant &defaultValue) const; + int m_clickCount; QTimer *m_timer; - bool m_gravityRotateBlackEnabled; + std::unique_ptr m_dConfig; + QLabel *m_logo; + QLabel *m_shutdownTextLabel; + bool m_logoVisible; + bool m_platformWindowInitialized; + bool m_raisePending; + bool m_nativeKeyboardGrabbed; + QList m_screenWindows; }; class BlackWidgetAdaptor : public QDBusAbstractAdaptor {