Skip to content

Commit 2458b15

Browse files
tsic404hudeng-go
andcommitted
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: linuxdeepin/developer-center#2262 Bug: https://pms.uniontech.com/bug-view-125181.html Co-authored-by: hudeng <hudeng@deepin.org>
1 parent 32f8fe0 commit 2458b15

2 files changed

Lines changed: 120 additions & 32 deletions

File tree

plugins/tray/xembedtraywidget.cpp

Lines changed: 112 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <QX11Info>
1313
#include <QDebug>
1414
#include <QMouseEvent>
15+
#include <QScopedPointer>
1516
#include <QProcess>
1617
#include <QThread>
1718
#include <QApplication>
@@ -31,7 +32,7 @@
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+
}

plugins/tray/xembedtraywidget.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ private slots:
5151
bool isBadWindow();
5252

5353
private:
54+
// Direct client关注xevent,使用xevent来处理button事件等
55+
// XTest client不关注xevent,使用xtest extension处理
56+
enum InjectMode {
57+
Direct,
58+
XTest,
59+
};
60+
5461
bool m_active = false;
5562
WId m_windowId;
5663
WId m_containerWid;
@@ -62,6 +69,7 @@ private slots:
6269
bool m_valid;
6370
xcb_connection_t *m_xcbCnn;
6471
Display* m_display;
72+
InjectMode m_injectMode;
6573
};
6674

6775
#endif // XEMBEDTRAYWIDGET_H

0 commit comments

Comments
 (0)