Changeset View
Changeset View
Standalone View
Standalone View
xembed-sni-proxy/sniproxy.cpp
1 | /* | 1 | /* | ||
---|---|---|---|---|---|
2 | * Holds one embedded window, registers as DBus entry | 2 | * Holds one embedded window, registers as DBus entry | ||
3 | * Copyright (C) 2015 <davidedmundson@kde.org> David Edmundson | 3 | * Copyright (C) 2015 <davidedmundson@kde.org> David Edmundson | ||
4 | * Copyright (C) 2019 <materka@gmail.com> Konrad Materka | ||||
4 | * | 5 | * | ||
5 | * This library is free software; you can redistribute it and/or | 6 | * This library is free software; you can redistribute it and/or | ||
6 | * modify it under the terms of the GNU Lesser General Public | 7 | * modify it under the terms of the GNU Lesser General Public | ||
7 | * License as published by the Free Software Foundation; either | 8 | * License as published by the Free Software Foundation; either | ||
8 | * version 2.1 of the License, or (at your option) any later version. | 9 | * version 2.1 of the License, or (at your option) any later version. | ||
9 | * | 10 | * | ||
10 | * This library is distributed in the hope that it will be useful, | 11 | * This library is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
▲ Show 20 Lines • Show All 62 Lines • ▼ Show 20 Line(s) | |||||
74 | 75 | | |||
75 | SNIProxy::SNIProxy(xcb_window_t wid, QObject* parent): | 76 | SNIProxy::SNIProxy(xcb_window_t wid, QObject* parent): | ||
76 | QObject(parent), | 77 | QObject(parent), | ||
77 | //Work round a bug in our SNIWatcher with multiple SNIs per connection. | 78 | //Work round a bug in our SNIWatcher with multiple SNIs per connection. | ||
78 | //there is an undocumented feature that you can register an SNI by path, however it doesn't detect an object on a service being removed, only the entire service closing | 79 | //there is an undocumented feature that you can register an SNI by path, however it doesn't detect an object on a service being removed, only the entire service closing | ||
79 | //instead lets use one DBus connection per SNI | 80 | //instead lets use one DBus connection per SNI | ||
80 | m_dbus(QDBusConnection::connectToBus(QDBusConnection::SessionBus, QStringLiteral("XembedSniProxy%1").arg(s_serviceCount++))), | 81 | m_dbus(QDBusConnection::connectToBus(QDBusConnection::SessionBus, QStringLiteral("XembedSniProxy%1").arg(s_serviceCount++))), | ||
81 | m_windowId(wid), | 82 | m_windowId(wid), | ||
83 | sendingClickEvent(false), | ||||
82 | m_injectMode(Direct) | 84 | m_injectMode(Direct) | ||
83 | { | 85 | { | ||
84 | //create new SNI | 86 | //create new SNI | ||
85 | new StatusNotifierItemAdaptor(this); | 87 | new StatusNotifierItemAdaptor(this); | ||
86 | m_dbus.registerObject(QStringLiteral("/StatusNotifierItem"), this); | 88 | m_dbus.registerObject(QStringLiteral("/StatusNotifierItem"), this); | ||
87 | 89 | | |||
88 | auto statusNotifierWatcher = new org::kde::StatusNotifierWatcher(QStringLiteral(SNI_WATCHER_SERVICE_NAME), QStringLiteral(SNI_WATCHER_PATH), QDBusConnection::sessionBus(), this); | 90 | auto statusNotifierWatcher = new org::kde::StatusNotifierWatcher(QStringLiteral(SNI_WATCHER_SERVICE_NAME), QStringLiteral(SNI_WATCHER_PATH), QDBusConnection::sessionBus(), this); | ||
89 | auto reply = statusNotifierWatcher->RegisterStatusNotifierItem(m_dbus.baseService()); | 91 | auto reply = statusNotifierWatcher->RegisterStatusNotifierItem(m_dbus.baseService()); | ||
90 | reply.waitForFinished(); | 92 | reply.waitForFinished(); | ||
91 | if (reply.isError()) { | 93 | if (reply.isError()) { | ||
92 | qCWarning(SNIPROXY) << "could not register SNI:" << reply.error().message(); | 94 | qCWarning(SNIPROXY) << "could not register SNI:" << reply.error().message(); | ||
93 | } | 95 | } | ||
94 | 96 | | |||
95 | auto c = QX11Info::connection(); | 97 | auto c = QX11Info::connection(); | ||
96 | 98 | | |||
97 | //create a container window | 99 | //create a container window | ||
98 | auto screen = xcb_setup_roots_iterator (xcb_get_setup (c)).data; | 100 | auto screen = xcb_setup_roots_iterator (xcb_get_setup (c)).data; | ||
99 | m_containerWid = xcb_generate_id(c); | 101 | m_containerWid = xcb_generate_id(c); | ||
100 | uint32_t values[3]; | 102 | uint32_t values[3]; | ||
101 | uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; | 103 | uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; | ||
102 | values[0] = screen->black_pixel; //draw a solid background so the embedded icon doesn't get garbage in it | 104 | values[0] = screen->black_pixel; //draw a solid background so the embedded icon doesn't get garbage in it | ||
103 | values[1] = true; //bypass wM | 105 | values[1] = true; //bypass wM | ||
104 | // Redirect and handle structure (size, position) requests on the embedded window. | 106 | values[2] = XCB_EVENT_MASK_VISIBILITY_CHANGE | // receive visibility change, to handle KWin restart #357443 | ||
105 | values[2] = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; | 107 | // Redirect and handle structure (size, position) requests from the embedded window. | ||
108 | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; | ||||
106 | xcb_create_window (c, /* connection */ | 109 | xcb_create_window (c, /* connection */ | ||
107 | XCB_COPY_FROM_PARENT, /* depth */ | 110 | XCB_COPY_FROM_PARENT, /* depth */ | ||
108 | m_containerWid, /* window Id */ | 111 | m_containerWid, /* window Id */ | ||
109 | screen->root, /* parent window */ | 112 | screen->root, /* parent window */ | ||
110 | 0, 0, /* x, y */ | 113 | 0, 0, /* x, y */ | ||
111 | s_embedSize, s_embedSize, /* width, height */ | 114 | s_embedSize, s_embedSize, /* width, height */ | ||
112 | 0, /* border_width */ | 115 | 0, /* border_width */ | ||
113 | XCB_WINDOW_CLASS_INPUT_OUTPUT,/* class */ | 116 | XCB_WINDOW_CLASS_INPUT_OUTPUT,/* class */ | ||
▲ Show 20 Lines • Show All 98 Lines • ▼ Show 20 Line(s) | 204 | { | |||
212 | if (w > s_embedSize || h > s_embedSize) { | 215 | if (w > s_embedSize || h > s_embedSize) { | ||
213 | qCDebug(SNIPROXY) << "Scaling pixmap of window" << m_windowId << Title() << "from w*h" << w << h; | 216 | qCDebug(SNIPROXY) << "Scaling pixmap of window" << m_windowId << Title() << "from w*h" << w << h; | ||
214 | m_pixmap = m_pixmap.scaled(s_embedSize, s_embedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); | 217 | m_pixmap = m_pixmap.scaled(s_embedSize, s_embedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); | ||
215 | } | 218 | } | ||
216 | emit NewIcon(); | 219 | emit NewIcon(); | ||
217 | emit NewToolTip(); | 220 | emit NewToolTip(); | ||
218 | } | 221 | } | ||
219 | 222 | | |||
220 | void SNIProxy::stackContainerWindow(const uint32_t stackMode) const | | |||
221 | { | | |||
222 | auto c = QX11Info::connection(); | | |||
223 | const uint32_t stackData[] = {stackMode}; | | |||
224 | xcb_configure_window(c, m_containerWid, XCB_CONFIG_WINDOW_STACK_MODE, stackData); | | |||
225 | } | | |||
226 | | ||||
227 | void SNIProxy::resizeWindow(const uint16_t width, const uint16_t height) const | 223 | void SNIProxy::resizeWindow(const uint16_t width, const uint16_t height) const | ||
228 | { | 224 | { | ||
229 | auto connection = QX11Info::connection(); | 225 | auto connection = QX11Info::connection(); | ||
230 | 226 | | |||
231 | uint16_t widthNormalized = std::min(width, s_embedSize); | 227 | uint16_t widthNormalized = std::min(width, s_embedSize); | ||
232 | uint16_t heighNormalized = std::min(height, s_embedSize); | 228 | uint16_t heighNormalized = std::min(height, s_embedSize); | ||
233 | 229 | | |||
234 | const uint32_t windowSizeConfigVals[2] = { widthNormalized, heighNormalized }; | 230 | const uint32_t windowSizeConfigVals[2] = { widthNormalized, heighNormalized }; | ||
235 | xcb_configure_window(connection, m_windowId, | 231 | xcb_configure_window(connection, m_windowId, | ||
236 | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, | 232 | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, | ||
237 | windowSizeConfigVals); | 233 | windowSizeConfigVals); | ||
238 | 234 | | |||
239 | xcb_flush(connection); | 235 | xcb_flush(connection); | ||
240 | } | 236 | } | ||
241 | 237 | | |||
238 | void SNIProxy::hideContainerWindow(xcb_window_t windowId) const | ||||
239 | { | ||||
240 | if (m_containerWid == windowId && !sendingClickEvent) { | ||||
241 | qDebug() << "Container window visible, stack below"; | ||||
242 | stackContainerWindow(XCB_STACK_MODE_BELOW); | ||||
243 | } | ||||
244 | } | ||||
245 | | ||||
242 | QSize SNIProxy::calculateClientWindowSize() const | 246 | QSize SNIProxy::calculateClientWindowSize() const | ||
243 | { | 247 | { | ||
244 | auto c = QX11Info::connection(); | 248 | auto c = QX11Info::connection(); | ||
245 | 249 | | |||
246 | auto cookie = xcb_get_geometry(c, m_windowId); | 250 | auto cookie = xcb_get_geometry(c, m_windowId); | ||
247 | QScopedPointer<xcb_get_geometry_reply_t, QScopedPointerPodDeleter> | 251 | QScopedPointer<xcb_get_geometry_reply_t, QScopedPointerPodDeleter> | ||
248 | clientGeom(xcb_get_geometry_reply(c, cookie, nullptr)); | 252 | clientGeom(xcb_get_geometry_reply(c, cookie, nullptr)); | ||
249 | 253 | | |||
▲ Show 20 Lines • Show All 177 Lines • ▼ Show 20 Line(s) | 429 | if (length < minLength) { | |||
427 | clickPoint = QPoint(rectangles[i].x, rectangles[i].y); | 431 | clickPoint = QPoint(rectangles[i].x, rectangles[i].y); | ||
428 | } | 432 | } | ||
429 | } | 433 | } | ||
430 | 434 | | |||
431 | qCDebug(SNIPROXY) << "Click point:" << clickPoint; | 435 | qCDebug(SNIPROXY) << "Click point:" << clickPoint; | ||
432 | return clickPoint; | 436 | return clickPoint; | ||
433 | } | 437 | } | ||
434 | 438 | | |||
439 | void SNIProxy::stackContainerWindow(const uint32_t stackMode) const | ||||
440 | { | ||||
441 | auto c = QX11Info::connection(); | ||||
442 | const uint32_t stackData[] = {stackMode}; | ||||
443 | xcb_configure_window(c, m_containerWid, XCB_CONFIG_WINDOW_STACK_MODE, stackData); | ||||
444 | } | ||||
445 | | ||||
435 | //____________properties__________ | 446 | //____________properties__________ | ||
436 | 447 | | |||
437 | QString SNIProxy::Category() const | 448 | QString SNIProxy::Category() const | ||
438 | { | 449 | { | ||
439 | return QStringLiteral("ApplicationStatus"); | 450 | return QStringLiteral("ApplicationStatus"); | ||
440 | } | 451 | } | ||
441 | 452 | | |||
442 | QString SNIProxy::Id() const | 453 | QString SNIProxy::Id() const | ||
▲ Show 20 Lines • Show All 65 Lines • ▼ Show 20 Line(s) | 517 | { | |||
508 | //GTK doesn't like send_events and double checks the mouse position matches where the window is and is top level | 519 | //GTK doesn't like send_events and double checks the mouse position matches where the window is and is top level | ||
509 | //in order to solve this we move the embed container over to where the mouse is then replay the event using send_event | 520 | //in order to solve this we move the embed container over to where the mouse is then replay the event using send_event | ||
510 | //if patching, test with xchat + xchat context menus | 521 | //if patching, test with xchat + xchat context menus | ||
511 | 522 | | |||
512 | //note x,y are not actually where the mouse is, but the plasmoid | 523 | //note x,y are not actually where the mouse is, but the plasmoid | ||
513 | //ideally we should make this match the plasmoid hit area | 524 | //ideally we should make this match the plasmoid hit area | ||
514 | 525 | | |||
515 | qCDebug(SNIPROXY) << "Received click" << mouseButton << "with passed x*y" << x << y; | 526 | qCDebug(SNIPROXY) << "Received click" << mouseButton << "with passed x*y" << x << y; | ||
527 | sendingClickEvent = true; | ||||
kmaterka: During click, container window is showed for a brief moment (STACK_ABOVE), so it is possible… | |||||
516 | 528 | | |||
517 | auto c = QX11Info::connection(); | 529 | auto c = QX11Info::connection(); | ||
518 | 530 | | |||
519 | auto cookieSize = xcb_get_geometry(c, m_windowId); | 531 | auto cookieSize = xcb_get_geometry(c, m_windowId); | ||
520 | QScopedPointer<xcb_get_geometry_reply_t, QScopedPointerPodDeleter> | 532 | QScopedPointer<xcb_get_geometry_reply_t, QScopedPointerPodDeleter> | ||
521 | clientGeom(xcb_get_geometry_reply(c, cookieSize, nullptr)); | 533 | clientGeom(xcb_get_geometry_reply(c, cookieSize, nullptr)); | ||
522 | 534 | | |||
523 | if (!clientGeom) { | 535 | if (!clientGeom) { | ||
▲ Show 20 Lines • Show All 74 Lines • ▼ Show 20 Line(s) | 593 | { | |||
598 | delete event; | 610 | delete event; | ||
599 | } else { | 611 | } else { | ||
600 | sendXTestReleased(QX11Info::display(), mouseButton); | 612 | sendXTestReleased(QX11Info::display(), mouseButton); | ||
601 | } | 613 | } | ||
602 | 614 | | |||
603 | #ifndef VISUAL_DEBUG | 615 | #ifndef VISUAL_DEBUG | ||
604 | stackContainerWindow(XCB_STACK_MODE_BELOW); | 616 | stackContainerWindow(XCB_STACK_MODE_BELOW); | ||
605 | #endif | 617 | #endif | ||
618 | | ||||
619 | sendingClickEvent = false; | ||||
606 | } | 620 | } |
During click, container window is showed for a brief moment (STACK_ABOVE), so it is possible that it will sent visibility event. This is probably not needed, I tested it and all events generated in this method (stack above, click, stack below) were processed before visibility event is received in XEmbedSNIProxy. I added this just in case - I'm not sure how X events are queued and how reliable the order is.