From 7e88d15e1dd745bc1a8a1f0f75aaabdd06c8cdc7 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 7 Oct 2026 16:58:40 +0200 Subject: [PATCH] Validate unmanaged notification overlay independently of EWMH --- tools/chatgui/chatgui_doc.md | 6 +- .../tests/test_message_notification_x11.cpp | 63 +++++++++---------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/tools/chatgui/chatgui_doc.md b/tools/chatgui/chatgui_doc.md index c8f8842a..a73aed99 100644 --- a/tools/chatgui/chatgui_doc.md +++ b/tools/chatgui/chatgui_doc.md @@ -89,14 +89,14 @@ Wayland/XWayland в GNOME использует асинхронный `org.gnome X11 idle time в Wayland-сессии не используется: он не гарантирует активность в других приложениях. Если системный механизм недоступен, учитываются только события внутри ChatGUI; закрытие крестиком остаётся доступным. При отсутствии координат трея выбирается угол доступной области экрана; нативный Wayland управляет позицией и порядком окон. -В X11/XWayland уведомление запрашивает показ на всех виртуальных рабочих столах через `_NET_WM_DESKTOP=0xffffffff`. -Подсказка выставляется перед первым показом и повторяется через EWMH при обновлении уже видимого окна. +В X11/XWayland уведомление — отдельное overlay-окно (`override_redirect`) типа notification: +оконный менеджер не привязывает его к одному виртуальному столу, оно остаётся доступным при переключении столов. Уведомление не является дочерним окном главного чата: сворачивание или рабочий стол главного окна не определяют его видимость. На нативном Wayland поверхность пересоздаётся при новом сообщении, чтобы не сохранять прежний рабочий стол; выбор текущего стола и возможность показа поверх других окон остаются политикой композитора. Показ/скрытие и причины ограничений логируются в `general`; запуск отсчёта и первая активность — на уровне `debug`. Проверка: `ctest --test-dir build -R 'test_message_(notification|events)' --output-on-failure`. -При доступном X11/XWayland: `./build/test_message_notification_x11` проверяет all-desktop hint после показа, обновления и скрытия. +При доступном X11/XWayland: `./build/test_message_notification_x11` проверяет overlay после показа, обновления и скрытия. **ChatView** (`src/chatview.h`) — QListView с фоновым изображением и drag-to-select. При движении >5px от точки нажатия стартует выделение сообщений. Ctrl+C копирует: для одного — выделенный текст, для нескольких — `[time] author: text`. Сигнал `hoveredIndexChanged` используется для активации анимаций эмодзи в MessageList. diff --git a/tools/chatgui/tests/test_message_notification_x11.cpp b/tools/chatgui/tests/test_message_notification_x11.cpp index a4a6a037..2637c674 100644 --- a/tools/chatgui/tests/test_message_notification_x11.cpp +++ b/tools/chatgui/tests/test_message_notification_x11.cpp @@ -1,4 +1,4 @@ -/* Ручная проверка EWMH на настоящем X11/XWayland: настройки рабочих столов пользователя не меняются. */ +/* Ручная проверка overlay на X11/XWayland: нет родительского окна и привязки WM к одному рабочему столу. */ #include "../src/message_notification.h" #include "../../../lib/debug_config.h" #include @@ -9,19 +9,36 @@ class NotificationDesktopTest : public QObject { Q_OBJECT Display* m_display = nullptr; - qint64 desktop(Window window, const char* property = "_NET_WM_DESKTOP") { + bool isDesktopOverlay(Window window) { + XWindowAttributes attributes{}; + if (!XGetWindowAttributes(m_display, window, &attributes)) { + DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "notification desktop test: no attributes window=0x%lx", window); + return false; + } + Window root = 0, parent = 0; + Window* children = nullptr; + unsigned int childCount = 0; + if (!XQueryTree(m_display, window, &root, &parent, &children, &childCount)) { + DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "notification desktop test: no parent info window=0x%lx", window); + return false; + } + if (children) XFree(children); Atom type = None; int format = 0; unsigned long count = 0, remaining = 0; unsigned char* data = nullptr; - const Atom atom = XInternAtom(m_display, property, False); - const int status = XGetWindowProperty(m_display, window, atom, 0, 1, False, - XA_CARDINAL, &type, &format, &count, &remaining, &data); - qint64 value = -1; - if (status == Success && type == XA_CARDINAL && format == 32 && count == 1 && data) - value = *reinterpret_cast(data) & 0xffffffffUL; + const Atom atom = XInternAtom(m_display, "_NET_CLIENT_LIST", False); + const int status = XGetWindowProperty(m_display, root, atom, 0, 4096, False, + XA_WINDOW, &type, &format, &count, &remaining, &data); + bool managed = false; + if (status == Success && type == XA_WINDOW && format == 32 && data) { + const auto* windows = reinterpret_cast(data); + for (unsigned long i = 0; i < count; ++i) if (windows[i] == window) { managed = true; break; } + } if (data) XFree(data); - return value; + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "notification desktop test: window=0x%lx mapped=%d override_redirect=%d root_parent=%d managed=%d", + window, attributes.map_state, int(attributes.override_redirect), int(parent == root), int(managed)); + return attributes.map_state == IsViewable && attributes.override_redirect && parent == root && !managed; } private slots: @@ -38,36 +55,18 @@ private slots: notification.showMessage("42", "Alice", "Desktop test", {}); const Window window = Window(notification.winId()); QTest::qWait(200); // Проверяем состояние после обработки MapRequest оконным менеджером. - DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "notification desktop test: mapped desktop=%lld root_current=%lld root_count=%lld", - (long long)desktop(window), (long long)desktop(DefaultRootWindow(m_display), "_NET_CURRENT_DESKTOP"), - (long long)desktop(DefaultRootWindow(m_display), "_NET_NUMBER_OF_DESKTOPS")); - QTRY_COMPARE_WITH_TIMEOUT(desktop(window), qint64(0xffffffffUL), 2000); + QVERIFY(isDesktopOverlay(window)); QVERIFY(!notification.parentWidget()); QVERIFY(notification.windowFlags() & Qt::WindowDoesNotAcceptFocus); - DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "notification desktop test: first show window=0x%lx desktop=all", window); - - // Переносим только своё окно на стол 0; активный стол пользователя может изменяться во время проверки. - const qint64 singleDesktop = 0; - XEvent event{}; - event.xclient.type = ClientMessage; - event.xclient.window = window; - event.xclient.message_type = XInternAtom(m_display, "_NET_WM_DESKTOP", False); - event.xclient.format = 32; - event.xclient.data.l[0] = long(singleDesktop); - event.xclient.data.l[1] = 1; - QVERIFY(XSendEvent(m_display, DefaultRootWindow(m_display), False, - SubstructureRedirectMask | SubstructureNotifyMask, &event)); - XFlush(m_display); - QTRY_COMPARE_WITH_TIMEOUT(desktop(window), singleDesktop, 2000); notification.showMessage("dm:7", "Bob", "Desktop test", {}); - QTRY_COMPARE_WITH_TIMEOUT(desktop(window), qint64(0xffffffffUL), 2000); - DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "notification desktop test: updated visible window desktop=all"); + QCOMPARE(Window(notification.winId()), window); + QVERIFY(isDesktopOverlay(window)); notification.dismiss("test-reshow"); QTest::qWait(50); notification.showMessage("43", "Charlie", "Desktop test", {}); - QTRY_COMPARE_WITH_TIMEOUT(desktop(Window(notification.winId())), qint64(0xffffffffUL), 2000); - DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "notification desktop test: withdrawn window shown again desktop=all"); + QTest::qWait(200); + QVERIFY(isDesktopOverlay(Window(notification.winId()))); notification.dismiss("test-complete"); } };