Browse Source

Use independent X11 notification overlay across desktops

master
evgeny 4 days ago
parent
commit
4a7f37b920
  1. 1
      tools/chatgui/CMakeLists.txt
  2. 70
      tools/chatgui/src/message_notification.cpp
  3. 1
      tools/chatgui/src/message_notification.h

1
tools/chatgui/CMakeLists.txt

@ -267,7 +267,6 @@ elseif(UNIX AND NOT APPLE)
endif()
if(TARGET X11::X11)
target_compile_definitions(chatgui_notifications PRIVATE UTUN_NOTIFICATION_X11)
target_link_libraries(chatgui_notifications PRIVATE X11::X11)
endif()
if(TARGET X11::Xss)

70
tools/chatgui/src/message_notification.cpp

@ -13,16 +13,13 @@
#include <QHideEvent>
#include <QCloseEvent>
#ifdef UTUN_NOTIFICATION_X11
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#endif
MessageNotification::MessageNotification()
: QWidget(nullptr, Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::WindowDoesNotAcceptFocus)
, m_activity(new UserActivityMonitor(this))
{
setObjectName("messageNotification");
// X11/XWayland: отдельное overlay-окно не принадлежит рабочему столу главного окна и не забирает фокус.
if (QGuiApplication::platformName() == "xcb") setWindowFlag(Qt::X11BypassWindowManagerHint);
setAttribute(Qt::WA_ShowWithoutActivating);
setAttribute(Qt::WA_X11NetWmWindowTypeNotification);
#ifdef Q_OS_MACOS
@ -61,8 +58,8 @@ MessageNotification::MessageNotification()
GUI_DEBUG("notification: first activity target=%s hide_in_ms=%d", qPrintable(m_target), m_hideTimer.interval());
m_hideTimer.start();
});
GUI_DEBUG("notification: initialized platform=%s hide_after_activity_ms=%d",
qPrintable(QGuiApplication::platformName()), m_hideTimer.interval());
GUI_DEBUG("notification: initialized platform=%s hide_after_activity_ms=%d all_desktop_overlay=%d",
qPrintable(QGuiApplication::platformName()), m_hideTimer.interval(), int(QGuiApplication::platformName() == "xcb"));
if (QGuiApplication::platformName().startsWith("wayland"))
GUI_WARN("notification: native Wayland controls desktop, placement and stacking; remapping on current desktop is best effort");
}
@ -104,71 +101,12 @@ void MessageNotification::showMessage(const QString& target, const QString& send
x = qBound(area.left(), x, qMax(area.left(), area.right() - width() + 1));
y = qBound(area.top(), y, qMax(area.top(), area.bottom() - height() + 1));
move(x, y);
setDesktopScope();
show();
setDesktopScope(); // Подтверждаем область после MapRequest, если WM заменил предварительную подсказку.
m_activity->start();
GUI_INFO("notification: %s target=%s screen=%s position=%d,%d tray_anchor=%d waiting_for_activity",
replacing ? "updated" : "shown", qPrintable(target), qPrintable(screen->name()), x, y, int(!trayGeometry.isEmpty()));
}
void MessageNotification::setDesktopScope() {
#ifdef UTUN_NOTIFICATION_X11
if (QGuiApplication::platformName() != "xcb") return;
Display* display = nullptr;
bool ownDisplay = false;
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
#if QT_CONFIG(xcb)
if (auto* native = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()) display = native->display();
#endif
#endif
if (!display) { display = XOpenDisplay(nullptr); ownDisplay = true; }
if (!display) { GUI_ERROR("notification: cannot open X11 display to select all desktops"); return; }
const Window window = Window(winId());
QGuiApplication::sync(); // Окно Qt должно существовать на сервере до Xlib-запросов.
XWindowAttributes attributes{};
if (!XGetWindowAttributes(display, window, &attributes)) {
GUI_ERROR("notification: cannot read X11 window attributes window=0x%lx", window);
if (ownDisplay) XCloseDisplay(display);
return;
}
const Atom desktopAtom = XInternAtom(display, "_NET_WM_DESKTOP", False);
const Atom supportedAtom = XInternAtom(display, "_NET_SUPPORTED", False);
Atom type = None;
int format = 0;
unsigned long count = 0, remaining = 0;
unsigned char* data = nullptr;
const int result = XGetWindowProperty(display, attributes.root, supportedAtom, 0, 4096, False,
XA_ATOM, &type, &format, &count, &remaining, &data);
bool supported = false;
if (result == Success && type == XA_ATOM && format == 32 && data) {
const auto* atoms = reinterpret_cast<const Atom*>(data);
for (unsigned long i = 0; i < count; ++i) if (atoms[i] == desktopAtom) { supported = true; break; }
}
if (data) XFree(data);
if (!supported) GUI_WARN("notification: window manager does not advertise desktop selection; all-desktop hint is best effort");
const unsigned long allDesktops = 0xffffffffUL;
if (!isVisible()) {
XChangeProperty(display, window, desktopAtom, XA_CARDINAL, 32, PropModeReplace,
reinterpret_cast<const unsigned char*>(&allDesktops), 1);
} else {
XEvent event{};
event.xclient.type = ClientMessage;
event.xclient.window = window;
event.xclient.message_type = desktopAtom;
event.xclient.format = 32;
event.xclient.data.l[0] = long(allDesktops);
event.xclient.data.l[1] = 1; // Запрос обычного приложения, не pager.
if (!XSendEvent(display, attributes.root, False, SubstructureRedirectMask | SubstructureNotifyMask, &event))
GUI_ERROR("notification: cannot request all desktops window=0x%lx", window);
}
XSync(display, False);
GUI_DEBUG("notification: all-desktop scope requested window=0x%lx mapped=%d wm_supported=%d",
window, int(isVisible()), int(supported));
if (ownDisplay) XCloseDisplay(display);
#endif
}
void MessageNotification::dismiss(const char* reason) {
if (isVisible()) GUI_INFO("notification: hidden target=%s reason=%s", qPrintable(m_target), reason);
m_hideTimer.stop();

1
tools/chatgui/src/message_notification.h

@ -27,7 +27,6 @@ protected:
void closeEvent(QCloseEvent* event) override;
private:
void setDesktopScope();
QString m_target;
QLabel* m_body = nullptr;
UserActivityMonitor* m_activity = nullptr;

Loading…
Cancel
Save