Show Linux message notifications on all X11 desktops
This commit is contained in:
1 parent
1108c773dc
commit
247479e411
3 files changed
+75
-1
No files matched your search
@@ -13,12 +13,18 @@
|
||||
#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");
|
||||
setAttribute(Qt::WA_ShowWithoutActivating);
|
||||
setAttribute(Qt::WA_X11NetWmWindowTypeNotification);
|
||||
#ifdef Q_OS_MACOS
|
||||
setAttribute(Qt::WA_MacAlwaysShowToolWindow);
|
||||
#endif
|
||||
@@ -58,7 +64,7 @@ MessageNotification::MessageNotification()
|
||||
GUI_DEBUG("notification: initialized platform=%s hide_after_activity_ms=%d",
|
||||
qPrintable(QGuiApplication::platformName()), m_hideTimer.interval());
|
||||
if (QGuiApplication::platformName().startsWith("wayland"))
|
||||
GUI_WARN("notification: native Wayland compositor controls placement and stacking; position near tray is best effort");
|
||||
GUI_WARN("notification: native Wayland controls desktop, placement and stacking; remapping on current desktop is best effort");
|
||||
}
|
||||
|
||||
MessageNotification::~MessageNotification() {
|
||||
@@ -79,6 +85,11 @@ void MessageNotification::showMessage(const QString& target, const QString& send
|
||||
}
|
||||
const QRect area = screen->availableGeometry();
|
||||
const bool replacing = isVisible();
|
||||
if (QGuiApplication::platformName().startsWith("wayland") && internalWinId()) {
|
||||
hide();
|
||||
destroy();
|
||||
GUI_DEBUG("notification: recreated Wayland surface for current desktop target=%s", qPrintable(target));
|
||||
}
|
||||
m_hideTimer.stop();
|
||||
m_target = target;
|
||||
m_body->setText(QCoreApplication::translate("ChatGui", "New message from %1").arg(sender) + '\n' + channel);
|
||||
@@ -92,12 +103,70 @@ 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();
|
||||
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();
|
||||
|
||||
Reference in new issue
Block a user