From fb9d9f59ca8443c2141d7c6074bcfc21a68eb8b5 Mon Sep 17 00:00:00 2001 From: TsicLiu Date: Tue, 19 Apr 2022 11:08:57 +0800 Subject: [PATCH] fix: fix wine systray can't interact with dde-dock use XTest to send mouse button event, but wine do not support XTest extension, so use XEvent to deal with wine. Log: fix wine systray can't interact with Influence: tray Issue: https://github.com/linuxdeepin/developer-center/issues/2262 https://github.com/linuxdeepin/developer-center/issues/4508 Bug: https://pms.uniontech.com/bug-view-125181.html Co-authored-by: hudeng --- plugins/tray/xembedtraywidget.cpp | 143 +++++++++++++++++++++++------- plugins/tray/xembedtraywidget.h | 8 ++ 2 files changed, 119 insertions(+), 32 deletions(-) diff --git a/plugins/tray/xembedtraywidget.cpp b/plugins/tray/xembedtraywidget.cpp index 07a4be5ad..fbbe89914 100644 --- a/plugins/tray/xembedtraywidget.cpp +++ b/plugins/tray/xembedtraywidget.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -31,7 +32,7 @@ #define WINE_WINDOW_PROP_NAME "__wine_prefix" #define IS_WINE_WINDOW_BY_WM_CLASS "explorer.exe" -static const qreal iconSize = PLUGIN_ICON_MAX_SIZE; +static const uint16_t iconDefaultSize = PLUGIN_ICON_MAX_SIZE; // this static var hold all suffix of tray widget keys. // that is in order to fix can not show multiple trays provide by one application, @@ -71,6 +72,7 @@ XEmbedTrayWidget::XEmbedTrayWidget(quint32 winId, xcb_connection_t *cnn, Display , m_valid(true) , m_xcbCnn(cnn) , m_display(disp) + , m_injectMode(Direct) { wrapWindow(); setOwnerPID(getWindowPID(winId)); @@ -171,15 +173,16 @@ void XEmbedTrayWidget::wrapWindow() } auto cookie = xcb_get_geometry(c, m_windowId); - xcb_get_geometry_reply_t *clientGeom(xcb_get_geometry_reply(c, cookie, Q_NULLPTR)); + QScopedPointer clientGeom(xcb_get_geometry_reply(c, cookie, nullptr)); if (!clientGeom) { m_valid = false; return; } - free(clientGeom); //create a container window + //创建托盘window,并使背景透明化 const auto ratio = devicePixelRatioF(); + uint16_t iconSize = iconDefaultSize * ratio; auto screen = xcb_setup_roots_iterator (xcb_get_setup (c)).data; m_containerWid = xcb_generate_id(c); uint32_t values[2]; @@ -191,7 +194,7 @@ void XEmbedTrayWidget::wrapWindow() m_containerWid, /* window Id */ screen->root, /* parent window */ 0, 0, /* x, y */ - iconSize * ratio, iconSize * ratio, /* width, height */ + iconSize, iconSize, /* width, height */ 0, /* border_width */ XCB_WINDOW_CLASS_INPUT_OUTPUT,/* class */ screen->root_visual, /* visual */ @@ -254,31 +257,45 @@ void XEmbedTrayWidget::wrapWindow() // xembed_message_send(m_windowId, XEMBED_EMBEDDED_NOTIFY, m_containerWid, 0, 0); //move window we're embedding - /* const uint32_t windowMoveConfigVals[2] = { 0, 0 }; xcb_configure_window(c, m_windowId, - XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, - windowMoveCentially quitting the application. Returns onfigVals); - */ + XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, windowMoveConfigVals); - //if the window is a clearly stupid size resize to be something sensible - //this is needed as chormium and such when resized just fill the icon with transparent space and only draw in the middle - //however spotify does need this as by default the window size is 900px wide. - //use an artbitrary heuristic to make sure icons are always sensible -// if (clientGeom->width > iconSize || clientGeom->height > iconSize ) - { - const uint32_t windowMoveConfigVals[2] = { uint32_t(iconSize * ratio), uint32_t(iconSize * ratio) }; - xcb_configure_window(c, m_windowId, - XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, - windowMoveConfigVals); + // 判断托盘的大小是否超出iconSize + QSize clientWindowSize; + if (clientGeom) { + clientWindowSize = QSize(clientGeom->width, clientGeom->height); + } + + if (clientWindowSize.isEmpty() || clientWindowSize.width() > iconSize || clientWindowSize.height() > iconSize ) { + + uint16_t widthNormalized = std::min(clientGeom->width, iconSize); + uint16_t heighNormalized = std::min(clientGeom->height, iconSize); + + const uint32_t windowSizeConfigVals[2] = {widthNormalized, heighNormalized}; + xcb_configure_window(c, m_windowId, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, windowSizeConfigVals); + + xcb_flush(c); + clientWindowSize = QSize(iconSize, iconSize); } //show the embedded window otherwise nothing happens xcb_map_window(c, m_windowId); + xcb_clear_area(c, 0, m_windowId, 0, 0, clientWindowSize.width(), clientWindowSize.height()); + // xcb_clear_area(c, 0, m_windowId, 0, 0, qMin(clientGeom->width, iconSize), qMin(clientGeom->height, iconSize)); xcb_flush(c); + + // 通过xcb获取window属性,判断该window是否处理button press事件 + // 当window不关注button press等事件时,使用xtest extension + auto windowAttributesCookie = xcb_get_window_attributes(c, m_windowId); + QScopedPointer windowAttributes(xcb_get_window_attributes_reply(c, windowAttributesCookie, nullptr)); + if (windowAttributes && !(windowAttributes->all_event_masks & XCB_EVENT_MASK_BUTTON_PRESS)) { + m_injectMode = XTest; + } + // setWindowOnTop(false); setWindowOnTop(true); setX11PassMouseEvent(true); @@ -290,15 +307,37 @@ void XEmbedTrayWidget::sendHoverEvent() return; } - // fake enter event const QPoint p(rawXPosition(QCursor::pos())); configContainerPosition(); setX11PassMouseEvent(false); setWindowOnTop(true); Display *display = IS_WAYLAND_DISPLAY ? m_display : QX11Info::display(); if (display) { - XTestFakeMotionEvent(display, 0, p.x(), p.y(), CurrentTime); - XFlush(display); + if (m_injectMode == XTest) { + // fake enter event + XTestFakeMotionEvent(display, 0, p.x(), p.y(), CurrentTime); + XFlush(display); + } else { + // 发送 montion notify event到client,实现hover事件 + auto c = IS_WAYLAND_DISPLAY ? m_xcbCnn : QX11Info::connection(); + if (!c) { + qWarning() << "QX11Info::connection() is " << c; + return; + } + xcb_motion_notify_event_t* event = new xcb_motion_notify_event_t; + memset(event, 0x00, sizeof(xcb_motion_notify_event_t)); + event->response_type = XCB_MOTION_NOTIFY; + event->event = m_windowId; + event->same_screen = 1; + event->root = QX11Info::appRootWindow(); + event->time = 0; + event->root_x = p.x(); + event->root_y = p.y(); + event->child = 0; + event->state = 0; + xcb_send_event(c, false, m_windowId, XCB_EVENT_MASK_POINTER_MOTION, (char*)event); + delete event; + } } QTimer::singleShot(100, this, [=] { setX11PassMouseEvent(true); }); @@ -333,19 +372,59 @@ void XEmbedTrayWidget::sendClick(uint8_t mouseButton, int x, int y) return; m_sendHoverEvent->stop(); - + auto c = IS_WAYLAND_DISPLAY ? m_xcbCnn : QX11Info::connection(); + if (!c) { + qWarning() << "QX11Info::connection() is " << c; + return; + } const QPoint p(rawXPosition(QPoint(x, y))); configContainerPosition(); setX11PassMouseEvent(false); setWindowOnTop(true); Display *display = IS_WAYLAND_DISPLAY ? m_display : QX11Info::display(); - XTestFakeMotionEvent(display, 0, p.x(), p.y(), CurrentTime); - XFlush(display); - XTestFakeButtonEvent(display, mouseButton, true, CurrentTime); - XFlush(display); - XTestFakeButtonEvent(display, mouseButton, false, CurrentTime); - XFlush(display); + + if (m_injectMode == XTest) { + XTestFakeMotionEvent(display, 0, p.x(), p.y(), CurrentTime); + XFlush(display); + XTestFakeButtonEvent(display, mouseButton, true, CurrentTime); + XFlush(display); + XTestFakeButtonEvent(display, mouseButton, false, CurrentTime); + XFlush(display); + } else { + // press event + xcb_button_press_event_t *pressEvent = new xcb_button_press_event_t; + memset(pressEvent, 0x00, sizeof(xcb_button_press_event_t)); + pressEvent->response_type = XCB_BUTTON_PRESS; + pressEvent->event = m_windowId; + pressEvent->same_screen = 1; + pressEvent->root = QX11Info::appRootWindow(); + pressEvent->time = 0; + pressEvent->root_x = p.x(); + pressEvent->root_y = p.y(); + pressEvent->child = 0; + pressEvent->state = 0; + pressEvent->detail = mouseButton; + xcb_send_event(c, false, m_windowId, XCB_EVENT_MASK_BUTTON_PRESS, (char*)pressEvent); + delete pressEvent; + + // release event + xcb_button_release_event_t *releaseEvent = new xcb_button_release_event_t; + memset(releaseEvent, 0x00, sizeof(xcb_button_release_event_t)); + releaseEvent->response_type = XCB_BUTTON_RELEASE; + releaseEvent->event = m_windowId; + releaseEvent->same_screen = 1; + releaseEvent->root = QX11Info::appRootWindow(); + releaseEvent->time = QX11Info::getTimestamp(); + releaseEvent->root_x = p.x(); + releaseEvent->root_y = p.y(); + releaseEvent->child = 0; + releaseEvent->state = 0; + releaseEvent->detail = mouseButton; + xcb_send_event(c, false, m_windowId, XCB_EVENT_MASK_BUTTON_RELEASE, (char*)releaseEvent); + delete releaseEvent; + } + QTimer::singleShot(100, this, [=] { setX11PassMouseEvent(true); }); } @@ -418,8 +497,8 @@ void XEmbedTrayWidget::refershIconImage() expose.window = m_containerWid; expose.x = 0; expose.y = 0; - expose.width = iconSize * ratio; - expose.height = iconSize * ratio; + expose.width = iconDefaultSize * ratio; + expose.height = iconDefaultSize * ratio; xcb_send_event_checked(c, false, m_containerWid, XCB_EVENT_MASK_VISIBILITY_CHANGE, reinterpret_cast(&expose)); xcb_flush(c); @@ -435,7 +514,7 @@ void XEmbedTrayWidget::refershIconImage() return; } - m_image = qimage.scaled(iconSize * ratio, iconSize * ratio, Qt::KeepAspectRatio, Qt::SmoothTransformation); + m_image = qimage.scaled(iconDefaultSize * ratio, iconDefaultSize * ratio, Qt::KeepAspectRatio, Qt::SmoothTransformation); m_image.setDevicePixelRatio(ratio); update(); @@ -587,4 +666,4 @@ uint XEmbedTrayWidget::getWindowPID(uint winId) XCloseDisplay(display); return pid; -} \ No newline at end of file +} diff --git a/plugins/tray/xembedtraywidget.h b/plugins/tray/xembedtraywidget.h index 9e27c65ea..3d2d41540 100644 --- a/plugins/tray/xembedtraywidget.h +++ b/plugins/tray/xembedtraywidget.h @@ -51,6 +51,13 @@ private slots: bool isBadWindow(); private: + // Direct client关注xevent,使用xevent来处理button事件等 + // XTest client不关注xevent,使用xtest extension处理 + enum InjectMode { + Direct, + XTest, + }; + bool m_active = false; WId m_windowId; WId m_containerWid; @@ -62,6 +69,7 @@ private slots: bool m_valid; xcb_connection_t *m_xcbCnn; Display* m_display; + InjectMode m_injectMode; }; #endif // XEMBEDTRAYWIDGET_H