|
|
|
|
@ -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(); |
|
|
|
|
|