Browse Source

Validate unmanaged notification overlay independently of EWMH

master
evgeny 4 days ago
parent
commit
7e88d15e1d
  1. 6
      tools/chatgui/chatgui_doc.md
  2. 63
      tools/chatgui/tests/test_message_notification_x11.cpp

6
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.

63
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 <QtTest>
@ -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<const unsigned long*>(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<const Window*>(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");
}
};

Loading…
Cancel
Save