1212#include < QX11Info>
1313#include < QDebug>
1414#include < QMouseEvent>
15+ #include < QScopedPointer>
1516#include < QProcess>
1617#include < QThread>
1718#include < QApplication>
3132#define WINE_WINDOW_PROP_NAME " __wine_prefix"
3233#define IS_WINE_WINDOW_BY_WM_CLASS " explorer.exe"
3334
34- static const qreal iconSize = PLUGIN_ICON_MAX_SIZE;
35+ static const uint16_t iconDefaultSize = PLUGIN_ICON_MAX_SIZE;
3536
3637// this static var hold all suffix of tray widget keys.
3738// 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
7172 , m_valid(true )
7273 , m_xcbCnn(cnn)
7374 , m_display(disp)
75+ , m_injectMode(Direct)
7476{
7577 wrapWindow ();
7678 setOwnerPID (getWindowPID (winId));
@@ -171,15 +173,16 @@ void XEmbedTrayWidget::wrapWindow()
171173 }
172174
173175 auto cookie = xcb_get_geometry (c, m_windowId);
174- xcb_get_geometry_reply_t * clientGeom (xcb_get_geometry_reply (c, cookie, Q_NULLPTR ));
176+ QScopedPointer< xcb_get_geometry_reply_t , QScopedPointerPodDeleter> clientGeom (xcb_get_geometry_reply (c, cookie, nullptr ));
175177 if (!clientGeom) {
176178 m_valid = false ;
177179 return ;
178180 }
179- free (clientGeom);
180181
181182 // create a container window
183+ // 创建托盘window,并使背景透明化
182184 const auto ratio = devicePixelRatioF ();
185+ uint16_t iconSize = iconDefaultSize * ratio;
183186 auto screen = xcb_setup_roots_iterator (xcb_get_setup (c)).data ;
184187 m_containerWid = xcb_generate_id (c);
185188 uint32_t values[2 ];
@@ -191,7 +194,7 @@ void XEmbedTrayWidget::wrapWindow()
191194 m_containerWid, /* window Id */
192195 screen->root , /* parent window */
193196 0 , 0 , /* x, y */
194- iconSize * ratio , iconSize * ratio , /* width, height */
197+ iconSize, iconSize, /* width, height */
195198 0 , /* border_width */
196199 XCB_WINDOW_CLASS_INPUT_OUTPUT,/* class */
197200 screen->root_visual , /* visual */
@@ -254,31 +257,46 @@ void XEmbedTrayWidget::wrapWindow()
254257 // xembed_message_send(m_windowId, XEMBED_EMBEDDED_NOTIFY, m_containerWid, 0, 0);
255258
256259 // move window we're embedding
257- /*
258260 const uint32_t windowMoveConfigVals[2 ] = { 0 , 0 };
259261 xcb_configure_window (c, m_windowId,
260- XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y,
261- windowMoveCentially quitting the application. Returns onfigVals);
262- */
262+ XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, windowMoveConfigVals);
263263
264- // if the window is a clearly stupid size resize to be something sensible
265- // this is needed as chormium and such when resized just fill the icon with transparent space and only draw in the middle
266- // however spotify does need this as by default the window size is 900px wide.
267- // use an artbitrary heuristic to make sure icons are always sensible
268- // if (clientGeom->width > iconSize || clientGeom->height > iconSize )
269- {
270- const uint32_t windowMoveConfigVals[2 ] = { uint32_t (iconSize * ratio), uint32_t (iconSize * ratio) };
271- xcb_configure_window (c, m_windowId,
272- XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
273- windowMoveConfigVals);
264+ // 判断托盘的大小是否超出iconSize
265+ QSize clientWindowSize;
266+ if (clientGeom) {
267+ clientWindowSize = QSize (clientGeom->width , clientGeom->height );
268+ }
269+
270+ if (clientWindowSize.isEmpty () || clientWindowSize.width () > iconSize || clientWindowSize.height () > iconSize ) {
271+
272+ uint16_t widthNormalized = std::min (clientGeom->width , iconSize);
273+ uint16_t heighNormalized = std::min (clientGeom->height , iconSize);
274+
275+ const uint32_t windowSizeConfigVals[2 ] = {widthNormalized, heighNormalized};
276+ xcb_configure_window (c, m_windowId, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, windowSizeConfigVals);
277+
278+ xcb_flush (c);
279+ clientWindowSize = QSize (iconSize, iconSize);
274280 }
275281
276282 // show the embedded window otherwise nothing happens
277283 xcb_map_window (c, m_windowId);
278284
285+ xcb_clear_area (c, 0 , m_windowId, 0 , 0 , clientWindowSize.width (), clientWindowSize.height ());
286+
279287// xcb_clear_area(c, 0, m_windowId, 0, 0, qMin(clientGeom->width, iconSize), qMin(clientGeom->height, iconSize));
280288
281289 xcb_flush (c);
290+
291+ // 通过xcb获取window属性,判断该window是否处理button press事件
292+ // 当window不关注button press等事件时,使用xtest extension,
293+ // 此过滤方式依然存在部分应用(gtk3/4)无法响应X Direct事件
294+ auto windowAttributesCookie = xcb_get_window_attributes (c, m_windowId);
295+ QScopedPointer<xcb_get_window_attributes_reply_t , QScopedPointerPodDeleter> windowAttributes (xcb_get_window_attributes_reply (c, windowAttributesCookie, nullptr ));
296+ if (windowAttributes && !(windowAttributes->all_event_masks & XCB_EVENT_MASK_BUTTON_PRESS)) {
297+ m_injectMode = XTest;
298+ }
299+
282300// setWindowOnTop(false);
283301 setWindowOnTop (true );
284302 setX11PassMouseEvent (true );
@@ -290,15 +308,37 @@ void XEmbedTrayWidget::sendHoverEvent()
290308 return ;
291309 }
292310
293- // fake enter event
294311 const QPoint p (rawXPosition (QCursor::pos ()));
295312 configContainerPosition ();
296313 setX11PassMouseEvent (false );
297314 setWindowOnTop (true );
298315 Display *display = IS_WAYLAND_DISPLAY ? m_display : QX11Info::display ();
299316 if (display) {
300- XTestFakeMotionEvent (display, 0 , p.x (), p.y (), CurrentTime);
301- XFlush (display);
317+ if (m_injectMode == XTest) {
318+ // fake enter event
319+ XTestFakeMotionEvent (display, 0 , p.x (), p.y (), CurrentTime);
320+ XFlush (display);
321+ } else {
322+ // 发送 montion notify event到client,实现hover事件
323+ auto c = IS_WAYLAND_DISPLAY ? m_xcbCnn : QX11Info::connection ();
324+ if (!c) {
325+ qWarning () << " QX11Info::connection() is " << c;
326+ return ;
327+ }
328+ xcb_motion_notify_event_t * event = new xcb_motion_notify_event_t ;
329+ memset (event, 0x00 , sizeof (xcb_motion_notify_event_t ));
330+ event->response_type = XCB_MOTION_NOTIFY;
331+ event->event = m_windowId;
332+ event->same_screen = 1 ;
333+ event->root = QX11Info::appRootWindow ();
334+ event->time = 0 ;
335+ event->root_x = p.x ();
336+ event->root_y = p.y ();
337+ event->child = 0 ;
338+ event->state = 0 ;
339+ xcb_send_event (c, false , m_windowId, XCB_EVENT_MASK_POINTER_MOTION, (char *)event);
340+ delete event;
341+ }
302342 }
303343
304344 QTimer::singleShot (100 , this , [=] { setX11PassMouseEvent (true ); });
@@ -333,19 +373,59 @@ void XEmbedTrayWidget::sendClick(uint8_t mouseButton, int x, int y)
333373 return ;
334374
335375 m_sendHoverEvent->stop ();
336-
376+ auto c = IS_WAYLAND_DISPLAY ? m_xcbCnn : QX11Info::connection ();
377+ if (!c) {
378+ qWarning () << " QX11Info::connection() is " << c;
379+ return ;
380+ }
337381 const QPoint p (rawXPosition (QPoint (x, y)));
338382 configContainerPosition ();
339383 setX11PassMouseEvent (false );
340384 setWindowOnTop (true );
341385
342386 Display *display = IS_WAYLAND_DISPLAY ? m_display : QX11Info::display ();
343- XTestFakeMotionEvent (display, 0 , p.x (), p.y (), CurrentTime);
344- XFlush (display);
345- XTestFakeButtonEvent (display, mouseButton, true , CurrentTime);
346- XFlush (display);
347- XTestFakeButtonEvent (display, mouseButton, false , CurrentTime);
348- XFlush (display);
387+
388+ if (m_injectMode == XTest) {
389+ XTestFakeMotionEvent (display, 0 , p.x (), p.y (), CurrentTime);
390+ XFlush (display);
391+ XTestFakeButtonEvent (display, mouseButton, true , CurrentTime);
392+ XFlush (display);
393+ XTestFakeButtonEvent (display, mouseButton, false , CurrentTime);
394+ XFlush (display);
395+ } else {
396+ // press event
397+ xcb_button_press_event_t *pressEvent = new xcb_button_press_event_t ;
398+ memset (pressEvent, 0x00 , sizeof (xcb_button_press_event_t ));
399+ pressEvent->response_type = XCB_BUTTON_PRESS;
400+ pressEvent->event = m_windowId;
401+ pressEvent->same_screen = 1 ;
402+ pressEvent->root = QX11Info::appRootWindow ();
403+ pressEvent->time = 0 ;
404+ pressEvent->root_x = p.x ();
405+ pressEvent->root_y = p.y ();
406+ pressEvent->child = 0 ;
407+ pressEvent->state = 0 ;
408+ pressEvent->detail = mouseButton;
409+ xcb_send_event (c, false , m_windowId, XCB_EVENT_MASK_BUTTON_PRESS, (char *)pressEvent);
410+ delete pressEvent;
411+
412+ // release event
413+ xcb_button_release_event_t *releaseEvent = new xcb_button_release_event_t ;
414+ memset (releaseEvent, 0x00 , sizeof (xcb_button_release_event_t ));
415+ releaseEvent->response_type = XCB_BUTTON_RELEASE;
416+ releaseEvent->event = m_windowId;
417+ releaseEvent->same_screen = 1 ;
418+ releaseEvent->root = QX11Info::appRootWindow ();
419+ releaseEvent->time = QX11Info::getTimestamp ();
420+ releaseEvent->root_x = p.x ();
421+ releaseEvent->root_y = p.y ();
422+ releaseEvent->child = 0 ;
423+ releaseEvent->state = 0 ;
424+ releaseEvent->detail = mouseButton;
425+ xcb_send_event (c, false , m_windowId, XCB_EVENT_MASK_BUTTON_RELEASE, (char *)releaseEvent);
426+ delete releaseEvent;
427+ }
428+
349429 QTimer::singleShot (100 , this , [=] { setX11PassMouseEvent (true ); });
350430}
351431
@@ -418,8 +498,8 @@ void XEmbedTrayWidget::refershIconImage()
418498 expose.window = m_containerWid;
419499 expose.x = 0 ;
420500 expose.y = 0 ;
421- expose.width = iconSize * ratio;
422- expose.height = iconSize * ratio;
501+ expose.width = iconDefaultSize * ratio;
502+ expose.height = iconDefaultSize * ratio;
423503 xcb_send_event_checked (c, false , m_containerWid, XCB_EVENT_MASK_VISIBILITY_CHANGE, reinterpret_cast <char *>(&expose));
424504 xcb_flush (c);
425505
@@ -435,7 +515,7 @@ void XEmbedTrayWidget::refershIconImage()
435515 return ;
436516 }
437517
438- m_image = qimage.scaled (iconSize * ratio, iconSize * ratio, Qt::KeepAspectRatio, Qt::SmoothTransformation);
518+ m_image = qimage.scaled (iconDefaultSize * ratio, iconDefaultSize * ratio, Qt::KeepAspectRatio, Qt::SmoothTransformation);
439519 m_image.setDevicePixelRatio (ratio);
440520
441521 update ();
@@ -587,4 +667,4 @@ uint XEmbedTrayWidget::getWindowPID(uint winId)
587667 XCloseDisplay (display);
588668
589669 return pid;
590- }
670+ }
0 commit comments