Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 111 additions & 32 deletions plugins/tray/xembedtraywidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <QX11Info>
#include <QDebug>
#include <QMouseEvent>
#include <QScopedPointer>
#include <QProcess>
#include <QThread>
#include <QApplication>
Expand All @@ -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,
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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<xcb_get_geometry_reply_t, QScopedPointerPodDeleter> 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];
Expand All @@ -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 */
Expand Down Expand Up @@ -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<xcb_get_window_attributes_reply_t, QScopedPointerPodDeleter> 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);
Expand All @@ -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); });
Expand Down Expand Up @@ -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); });
}

Expand Down Expand Up @@ -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<char *>(&expose));
xcb_flush(c);

Expand All @@ -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();
Expand Down Expand Up @@ -587,4 +666,4 @@ uint XEmbedTrayWidget::getWindowPID(uint winId)
XCloseDisplay(display);

return pid;
}
}
8 changes: 8 additions & 0 deletions plugins/tray/xembedtraywidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -62,6 +69,7 @@ private slots:
bool m_valid;
xcb_connection_t *m_xcbCnn;
Display* m_display;
InjectMode m_injectMode;
};

#endif // XEMBEDTRAYWIDGET_H