From 4977ed76ae6794fda314ddc36bd19388fbcdf3df Mon Sep 17 00:00:00 2001 From: zhaoyingzhen Date: Tue, 1 Sep 2026 14:16:46 +0800 Subject: [PATCH] fix: prevent dock tray popup flicker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calculate the tray popup position synchronously when its anchor changes so the first frame is placed at the tray item instead of the default origin. Wait for the Wayland surface to commit content, render one hidden frame, and only then reveal the shared popup window. This prevents the transparent window background from being visible before the client texture is ready. 在锚点变化时同步计算托盘弹窗位置,避免首帧使用默认原点坐标而先显示在错误位置。 等待 Wayland surface 提交内容后,以隐藏状态完成一帧渲染,再显示共享弹窗窗口,避免客户端纹理就绪前出现透明窗口背景。 Log: prevent dock tray popup flicker --- frame/qml/PanelPopup.qml | 45 ++++++++++++++++++++++- panels/dock/ShellSurfaceItemProxy.qml | 1 + panels/dock/dockpositioner.cpp | 7 +++- panels/dock/tray/TrayItemSurfacePopup.qml | 2 + 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/frame/qml/PanelPopup.qml b/frame/qml/PanelPopup.qml index c417f0532..33b132533 100644 --- a/frame/qml/PanelPopup.qml +++ b/frame/qml/PanelPopup.qml @@ -16,6 +16,10 @@ Item { property int popupY: 0 property bool readyBinding: false property bool openPending: false + property bool contentReady: true + property bool contentOpenPending: false + property bool revealAfterRender: false + property bool revealPending: false property bool grabInactivePending: false property int grabInactiveTimeout: 200 // WM_NAME, used for kwin. @@ -54,6 +58,12 @@ Item { if (!popupWindow) return + if (!contentReady) { + contentOpenPending = true + return + } + contentOpenPending = false + // The popup is being displayed. If you click on other plugin at this time, // the popup content of the previous plugin will be displayed first, // and the wrong popup size will cause the window size to change and flicker. @@ -84,11 +94,16 @@ Item { function close() { openPending = false + contentOpenPending = false + revealPending = false grabInactivePending = false grabInactiveTimer.stop() if (!popupWindow) return + if (popupWindow.currentItem === control) + popupWindow.opacity = 1 + // avoid to closing window by other PanelPopup. if (!readyBinding) return @@ -97,6 +112,16 @@ Item { popupWindow.currentItem = null } + onContentReadyChanged: { + if (!contentReady || !contentOpenPending) + return + + Qt.callLater(function () { + if (contentReady && contentOpenPending) + control.open() + }) + } + function finalizeOpen() { if (!popupWindow || !openPending || !readyBinding || popupWindow.currentItem !== control) @@ -104,8 +129,16 @@ Item { openPending = false popupWindow.title = windowTitle + revealPending = revealAfterRender + // popupWindow is shared by all PanelPopup instances. Always initialize + // its opacity so a popup cannot inherit an interrupted reveal state. + popupWindow.opacity = revealPending ? 0 : 1 popupWindow.show() - popupWindow.requestActivate() + if (revealPending) { + popupWindow.update() + } else { + popupWindow.requestActivate() + } } Timer { @@ -156,6 +189,16 @@ Item { control.finalizeOpen() } + function onFrameSwapped() + { + if (!control.revealPending || !popupWindow || popupWindow.currentItem !== control) + return + + control.revealPending = false + popupWindow.opacity = 1 + popupWindow.requestActivate() + } + function onX11FocusOutByGrab() { if (!popupWindow || !readyBinding || !popup.visible || popupWindow.currentItem !== control) { diff --git a/panels/dock/ShellSurfaceItemProxy.qml b/panels/dock/ShellSurfaceItemProxy.qml index e55eaea63..f73180a75 100644 --- a/panels/dock/ShellSurfaceItemProxy.qml +++ b/panels/dock/ShellSurfaceItemProxy.qml @@ -18,6 +18,7 @@ Item { property bool pressed: tapHandler.pressed property int cursorShape: Qt.ArrowCursor property alias shellSurfaceItem: impl + readonly property bool hasSurfaceContent: impl.surface ? impl.surface.hasContent : false implicitWidth: shellSurface ? shellSurface.width : 16 implicitHeight: shellSurface ? shellSurface.height : 16 diff --git a/panels/dock/dockpositioner.cpp b/panels/dock/dockpositioner.cpp index 231a881e6..e1b8bc5d4 100644 --- a/panels/dock/dockpositioner.cpp +++ b/panels/dock/dockpositioner.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2024-2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -36,7 +36,10 @@ DockPositioner::DockPositioner(DPanel *panel, QObject *parent) Q_ASSERT(m_panel); connect(m_panel, SIGNAL(positionChanged(Position)), this, SLOT(update())); connect(m_panel, SIGNAL(geometryChanged(QRect)), this, SLOT(update())); - connect(this, &DockPositioner::boundingChanged, this, &DockPositioner::update); + // The popup may be opened in the same event-loop turn as its anchor is + // assigned. Calculate anchor changes immediately so its first frame does + // not use the default (0, 0) position. + connect(this, &DockPositioner::boundingChanged, this, &DockPositioner::updatePosition); } DockPositioner::~DockPositioner() diff --git a/panels/dock/tray/TrayItemSurfacePopup.qml b/panels/dock/tray/TrayItemSurfacePopup.qml index c3cfbcdc2..4c5616650 100644 --- a/panels/dock/tray/TrayItemSurfacePopup.qml +++ b/panels/dock/tray/TrayItemSurfacePopup.qml @@ -33,6 +33,8 @@ Item { height: popupContent.height popupX: DockPanelPositioner.x popupY: DockPanelPositioner.y + contentReady: popupContent.hasSurfaceContent + revealAfterRender: true Item { anchors.fill: parent