From d62fae9e886ff07bc9589b972679b68a35694510 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 7 Oct 2026 16:38:33 +0200 Subject: [PATCH] Verify notification desktop scope with a real window manager --- tools/chatgui/CMakeLists.txt | 11 ++- tools/chatgui/chatgui_doc.md | 6 ++ .../tests/test_message_notification_x11.cpp | 80 +++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 tools/chatgui/tests/test_message_notification_x11.cpp diff --git a/tools/chatgui/CMakeLists.txt b/tools/chatgui/CMakeLists.txt index 7e6ad3f0..b57d8967 100644 --- a/tools/chatgui/CMakeLists.txt +++ b/tools/chatgui/CMakeLists.txt @@ -313,7 +313,10 @@ enable_testing() add_executable(test_message_notification tests/test_message_notification.cpp) target_link_libraries(test_message_notification PRIVATE chatgui_notifications ${QT_TEST_LIB} pthread ${CMAKE_DL_LIBS}) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - find_program(NOTIFICATION_DBUS_RUN_SESSION dbus-run-session REQUIRED) + find_program(NOTIFICATION_DBUS_RUN_SESSION NAMES dbus-run-session) + if(NOT NOTIFICATION_DBUS_RUN_SESSION) + message(FATAL_ERROR "dbus-run-session is required for the isolated notification test") + endif() target_compile_definitions(test_message_notification PRIVATE UTUN_TEST_ACTIVITY_DBUS) target_link_libraries(test_message_notification PRIVATE ${QT_DBUS}) add_test(NAME test_message_notification COMMAND ${NOTIFICATION_DBUS_RUN_SESSION} -- $) @@ -327,6 +330,12 @@ target_link_libraries(test_message_events PRIVATE ${QT_LIBS} ${QT_TEST_LIB} utun add_test(NAME test_message_events COMMAND test_message_events) set_tests_properties(test_message_events PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen" TIMEOUT 15) +# Ручной запуск с DISPLAY: проверяет применение desktop hints оконным менеджером, не меняя настройки рабочих столов. +if(TARGET X11::X11) + add_executable(test_message_notification_x11 tests/test_message_notification_x11.cpp) + target_link_libraries(test_message_notification_x11 PRIVATE chatgui_notifications ${QT_TEST_LIB} X11::X11 pthread ${CMAKE_DL_LIBS}) +endif() + add_executable(test_file_drop tests/test_file_drop.cpp src/inputbar.cpp src/audiorecorder.cpp src/audio_device.cpp src/sound_manager.cpp src/voicemessageencoder.cpp src/emojipanel.cpp src/emojitabbar.cpp src/emoji.cpp src/animtimer.cpp src/lottieicon.cpp src/voice_audio_io.cpp transport/miniaudio_impl.c transport/audio_diagnostics.cpp) diff --git a/tools/chatgui/chatgui_doc.md b/tools/chatgui/chatgui_doc.md index bed54f49..c8f8842a 100644 --- a/tools/chatgui/chatgui_doc.md +++ b/tools/chatgui/chatgui_doc.md @@ -89,8 +89,14 @@ Wayland/XWayland в GNOME использует асинхронный `org.gnome X11 idle time в Wayland-сессии не используется: он не гарантирует активность в других приложениях. Если системный механизм недоступен, учитываются только события внутри ChatGUI; закрытие крестиком остаётся доступным. При отсутствии координат трея выбирается угол доступной области экрана; нативный Wayland управляет позицией и порядком окон. +В X11/XWayland уведомление запрашивает показ на всех виртуальных рабочих столах через `_NET_WM_DESKTOP=0xffffffff`. +Подсказка выставляется перед первым показом и повторяется через EWMH при обновлении уже видимого окна. +Уведомление не является дочерним окном главного чата: сворачивание или рабочий стол главного окна не определяют его видимость. +На нативном 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 после показа, обновления и скрытия. **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 new file mode 100644 index 00000000..36443cb9 --- /dev/null +++ b/tools/chatgui/tests/test_message_notification_x11.cpp @@ -0,0 +1,80 @@ +/* Ручная проверка EWMH на настоящем X11/XWayland: настройки рабочих столов пользователя не меняются. */ +#include "../src/message_notification.h" +#include "../../../lib/debug_config.h" +#include +#include +#include + +class NotificationDesktopTest : public QObject { + Q_OBJECT + Display* m_display = nullptr; + + qint64 desktop(Window window, const char* property = "_NET_WM_DESKTOP") { + 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; + if (data) XFree(data); + return value; + } + +private slots: + void initTestCase() { + debug_config_init(); + debug_set_level(DEBUG_LEVEL_DEBUG); + m_display = XOpenDisplay(nullptr); + QVERIFY(m_display); + } + void cleanupTestCase() { if (m_display) XCloseDisplay(m_display); } + + void allDesktopsOnFirstShowUpdateAndReshow() { + MessageNotification notification; + notification.showMessage("42", "Alice", "Desktop test", {}); + const Window window = Window(notification.winId()); + QTRY_COMPARE_WITH_TIMEOUT(desktop(window), qint64(0xffffffffUL), 2000); + QVERIFY(!notification.parentWidget()); + QVERIFY(notification.windowFlags() & Qt::WindowDoesNotAcceptFocus); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "notification desktop test: first show window=0x%lx desktop=all", window); + + // Переносим только собственное тестовое окно на текущий стол, затем проверяем повторный запрос all. + const qint64 current = desktop(DefaultRootWindow(m_display), "_NET_CURRENT_DESKTOP"); + QVERIFY(current >= 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(current); + 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), current, 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"); + + 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"); + notification.dismiss("test-complete"); + } +}; + +int main(int argc, char** argv) { + if (qEnvironmentVariableIsEmpty("DISPLAY")) return 77; + qputenv("QT_QPA_PLATFORM", "xcb"); + QApplication application(argc, argv); + NotificationDesktopTest test; + return QTest::qExec(&test, argc, argv); +} + +#include "test_message_notification_x11.moc"