From a24ab6205084ac94df739c8559f550c6d176a2f3 Mon Sep 17 00:00:00 2001 From: yeshanshan Date: Fri, 11 Sep 2026 17:46:23 +0800 Subject: [PATCH] fix: delegate split menu to window manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Remove direct QPlatformWindow include and add platformFunction-based WM split menu integration 2. Add SUPPORTSPLITMENU, SHOWSPLITMENU, HIDESPLITMENU platform function variables 3. Introduce nativeWindowId and show/hideSplitMenuByWM helper functions 4. Track splitMenuByWM, splitMenuWindowId, and splitMenuByWMPressAndHold states in DTitlebarPrivate 5. Prioritize WM-provided split menu over client-side DSplitScreenWidget when supported 6. Fall back to legacy client-side split menu only for older window managers without support 7. Add immediate hide mode (hideImmediately) for split screen widget when delay is false 8. Guard showSplitScreenWidget against invisible/disabled max button and active WM menu 9. Handle QEvent::Hide and QEvent::Leave by stopping the press-and-hold timer and resetting WM menu state 10. Only start press-and-hold timer on left mouse button press and suppress toggle on release if WM menu was triggered 11. Stop timer and hide split menu immediately when split screen is disabled via setSplitScreenEnabled 12. Fix minor trailing whitespace formatting issues Log: Split screen menu now uses the window manager's native menu when supported, with graceful fallback for older window managers Influence: 1. Verify split menu appears via window manager on systems that support it 2. Test fallback to client-side split menu on older window managers 3. Test press-and-hold on the maximize button to trigger the menu and verify release does not toggle window state 4. Verify left-click on maximize button still toggles maximize/restore normally 5. Test QEvent::Leave and QEvent::Hide behaviors to confirm menu hides and timer stops 6. Test disabling split screen via setSplitScreenEnabled hides the menu immediately 7. Verify behavior when max button is hidden or disabled (menu should not show) 8. Confirm no regression in compositing-disabled or maximize-hint- disabled scenarios fix: 将分屏菜单委托给窗管处理 1. 移除直接包含 QPlatformWindow 头文件,改用 platformFunction 机制集成窗 管分屏菜单 2. 新增 SUPPORTSPLITMENU、SHOWSPLITMENU、HIDESPLITMENU 平台函数变量 3. 引入 nativeWindowId 及 show/hideSplitMenuByWM 辅助函数 4. 在 DTitlebarPrivate 中跟踪 splitMenuByWM、splitMenuWindowId 和 splitMenuByWMPressAndHold 状态 5. 当窗管支持时优先使用窗管提供的分屏菜单而非客户端 DSplitScreenWidget 6. 仅在不支持的老版本窗管下回退到客户端分屏菜单 7. 为分屏控件新增立即隐藏模式(hideImmediately),用于 delay 为 false 的 场景 8. 在最大按钮不可见/禁用或窗管菜单激活时跳过显示分屏控件 9. 处理 QEvent::Hide 和 QEvent::Leave,停止长按定时器并重置窗管菜单状态 10. 仅左键按下时启动长按定时器,若窗管菜单已触发则在释放时抑制窗口状态 切换 11. 通过 setSplitScreenEnabled 禁用分屏时立即停止定时器并隐藏分屏菜单 12. 修复少量行尾空白格式问题 Log: 分屏菜单在支持时改用窗管原生菜单,对老版本窗管提供优雅回退 Influence: 1. 验证在支持窗管分屏菜单的系统上菜单通过窗管弹出 2. 测试在老版本窗管上回退到客户端分屏菜单 3. 测试长按最大化按钮触发菜单,并确认释放时不会切换窗口状态 4. 验证左键单击最大化按钮仍能正常切换最大化/还原 5. 测试 QEvent::Leave 和 QEvent::Hide 行为,确认菜单隐藏且定时器停止 6. 测试通过 setSplitScreenEnabled 禁用分屏时菜单立即隐藏 7. 验证最大化按钮隐藏或禁用时菜单不显示 8. 确认在合成关闭或最大化提示禁用场景下无回归问题 PMS: BUG-370181 --- src/widgets/dtitlebar.cpp | 109 +++++++++++++++++++++++++++++++++----- 1 file changed, 96 insertions(+), 13 deletions(-) diff --git a/src/widgets/dtitlebar.cpp b/src/widgets/dtitlebar.cpp index 0d8954fa5..bb5f1604e 100644 --- a/src/widgets/dtitlebar.cpp +++ b/src/widgets/dtitlebar.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include "dpalettehelper.h" #include "dstyleoption.h" @@ -51,6 +50,36 @@ DWIDGET_BEGIN_NAMESPACE #define CHANGESPLITWINDOW_VAR "_d_splitWindowOnScreen" #define GETSUPPORTSPLITWINDOW_VAR "_d_supportForSplittingWindow" +#define SUPPORTSPLITMENU_VAR "_d_supportSplitMenu" +#define SHOWSPLITMENU_VAR "_d_showSplitMenu" +#define HIDESPLITMENU_VAR "_d_hideSplitMenu" + +static WId nativeWindowId(const QWidget *widget) +{ + if (!widget || !widget->windowHandle() || !widget->windowHandle()->handle()) + return 0; + + return widget->windowHandle()->winId(); +} + +static bool showSplitMenuByWM(WId wid, const QRect &buttonRect) +{ + const auto support = reinterpret_cast(qApp->platformFunction(SUPPORTSPLITMENU_VAR)); + const auto show = reinterpret_cast(qApp->platformFunction(SHOWSPLITMENU_VAR)); + if (!wid || !support || !show || !qApp->platformFunction(HIDESPLITMENU_VAR) || !support(wid)) + return false; + + show(wid, buttonRect); + return true; +} + +static void hideSplitMenuByWM(WId wid, bool delay) +{ + QFunctionPointer function = qApp->platformFunction(HIDESPLITMENU_VAR); + if (function) { + reinterpret_cast(function)(wid, delay); + } +} static inline int DefaultIconHeight() { return DSizeModeHelper::element(24, 32); } static inline int DefaultExpandButtonHeight() { return DSizeModeHelper::element(48, 48); } @@ -95,7 +124,7 @@ class DTitlebarPrivate : public DTK_CORE_NAMESPACE::DObjectPrivate void setIconVisible(bool visible); void updateTabOrder(); void showSplitScreenWidget(); - void hideSplitScreenWidget(); + void hideSplitScreenWidget(bool delay = true); void updateTitleBarSize() { if (optionButton) @@ -173,6 +202,9 @@ class DTitlebarPrivate : public DTK_CORE_NAMESPACE::DObjectPrivate bool autoHideOnFullscreen = false; bool fullScreenButtonVisible = true; bool splitScreenWidgetEnable = true; + bool splitMenuByWM = false; + WId splitMenuWindowId = 0; + bool splitMenuByWMPressAndHold = false; QTimer *maxButtonPressAndHoldTimer = nullptr; QWidget *sidebarBackgroundWidget = nullptr; DTitlebarSettingsImpl *titlebarSettingsImpl = nullptr; @@ -324,8 +356,12 @@ void DTitlebarPrivate::init() iconLabel->update(); }); q->connect(maxButtonPressAndHoldTimer, &QTimer::timeout, q, [this]() { - showSplitScreenWidget(); - if (splitWidget && splitWidget->isVisible()) + if (!splitMenuByWM) + showSplitScreenWidget(); + + if (splitMenuByWM) + splitMenuByWMPressAndHold = true; + else if (splitWidget && splitWidget->isVisible()) splitWidget->isMaxButtonPressAndHold = true; }); if (isUpdated) { @@ -582,6 +618,11 @@ void DTitlebarPrivate::handleParentWindowIdChange() void DTitlebarPrivate::_q_toggleWindowState() { + if (splitMenuByWMPressAndHold) { + splitMenuByWMPressAndHold = false; + return; + } + if (splitWidget && splitWidget->isMaxButtonPressAndHold) { splitWidget->isMaxButtonPressAndHold = false; return; @@ -818,7 +859,7 @@ void DTitlebarPrivate::setIconVisible(bool visible) if (visible) { if (dynamic_cast(leftLayout->itemAt(0))) delete leftLayout->takeAt(0); - + leftLayout->insertSpacing(0, 10); leftLayout->insertWidget(1, iconLabel, 0, Qt::AlignLeading | Qt::AlignVCenter); iconLabel->show(); @@ -873,14 +914,31 @@ void DTitlebarPrivate::showSplitScreenWidget() if (disableFlags.testFlag(Qt::WindowMaximizeButtonHint)) return; + if (!maxButton->isVisible() || !maxButton->isEnabled()) + return; + + if (splitMenuByWM) + return; + // 应产品要求 2D 模式下不对窗口分屏做任何显示 if (auto wmHelper = DWindowManagerHelper::instance()) { if (!wmHelper->hasComposite()) return; } - // 窗管不支持分屏时,不显示分屏菜单 - if (!Q_LIKELY(DSplitScreenWidget::supportSplitScreenByWM(q->window()))) + QRect maxBtnRect = QRect(maxButton->mapToGlobal(maxButton->rect().topLeft()), maxButton->rect().size()); + const WId wid = nativeWindowId(targetWindow()); + if (showSplitMenuByWM(wid, maxBtnRect)) { + splitMenuByWM = true; + splitMenuWindowId = wid; + return; + } + + if (splitMenuWindowId) + hideSplitScreenWidget(false); + + // Older window managers use the client-side split menu. + if (!DSplitScreenWidget::supportSplitScreenByWM(q->window())) return; if (!splitWidget) { @@ -890,8 +948,6 @@ void DTitlebarPrivate::showSplitScreenWidget() if (splitWidget->isVisible()) return; - QRect maxBtnRect = QRect(maxButton->mapToGlobal(maxButton->rect().topLeft()), maxButton->rect().size()); - QRect rect; if (QScreen *screen = QGuiApplication::screenAt(QCursor::pos())) { rect = screen->geometry(); @@ -910,20 +966,32 @@ void DTitlebarPrivate::showSplitScreenWidget() if (maxBtnRect.bottom() + splitWidget->height() - rect.y() > rect.height()) { // 超出下边缘 targetY -= maxButton->rect().height() + splitWidget->height(); - } + } splitWidget->show(QPoint(targetX, targetY)); } -void DTitlebarPrivate::hideSplitScreenWidget() +void DTitlebarPrivate::hideSplitScreenWidget(bool delay) { + if (splitMenuWindowId) { + const WId wid = splitMenuWindowId; + hideSplitMenuByWM(wid, delay); + splitMenuByWM = false; + if (!delay) + splitMenuWindowId = 0; + return; + } + if (!splitWidget) return; if (splitWidget->isHidden()) return; - splitWidget->hide(); + if (delay) + splitWidget->hide(); + else + splitWidget->hideImmediately(); } #endif @@ -1182,15 +1250,26 @@ bool DTitlebar::eventFilter(QObject *obj, QEvent *event) break; } case QEvent::Leave: { + d->maxButtonPressAndHoldTimer->stop(); d->hideSplitScreenWidget(); break; } + case QEvent::Hide: + d->maxButtonPressAndHoldTimer->stop(); + d->splitMenuByWM = false; + d->splitMenuWindowId = 0; + break; case QEvent::MouseButtonPress: { - d->maxButtonPressAndHoldTimer->start(300); + if (static_cast(event)->button() == Qt::LeftButton) { + d->splitMenuByWMPressAndHold = false; + d->maxButtonPressAndHoldTimer->start(300); + } break; } case QEvent::MouseButtonRelease: { d->maxButtonPressAndHoldTimer->stop(); + if (d->splitMenuWindowId && !d->splitMenuByWMPressAndHold) + d->hideSplitScreenWidget(false); break; } default: @@ -1759,6 +1838,10 @@ void DTitlebar::setSplitScreenEnabled(bool enabled) { D_D(DTitlebar); d->splitScreenWidgetEnable = enabled; + if (!enabled) { + d->maxButtonPressAndHoldTimer->stop(); + d->hideSplitScreenWidget(false); + } } bool DTitlebar::splitScreenIsEnabled() const