wip: sniproxy to traymanager1
This commit is contained in:
302
traymanagerproxy.cpp
Normal file
302
traymanagerproxy.cpp
Normal file
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
Xembed Tray Manager Proxy - holds one embedded window
|
||||
SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
|
||||
SPDX-FileCopyrightText: 2019 Konrad Materka <materka@gmail.com>
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#include "traymanagerproxy.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <xcb/xcb_atom.h>
|
||||
#include <xcb/xcb_event.h>
|
||||
|
||||
#include <QScreen>
|
||||
#include <QTimer>
|
||||
#include <QBitmap>
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusPendingCall>
|
||||
#include <QDBusPendingCallWatcher>
|
||||
|
||||
#include <KWindowInfo>
|
||||
#include <KWindowSystem>
|
||||
#include <netwm.h>
|
||||
|
||||
#include "xcbutils.h"
|
||||
#include "xtestsender.h"
|
||||
#include "c_ptr.h"
|
||||
#include <X11/Xlib.h>
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
#ifdef Status
|
||||
typedef Status XStatus;
|
||||
#undef Status
|
||||
typedef XStatus Status;
|
||||
#endif
|
||||
|
||||
static uint16_t s_embedSize = 32;
|
||||
static unsigned int XEMBED_VERSION = 0;
|
||||
|
||||
void xembed_message_send(xcb_window_t towin, long message, long d1, long d2, long d3)
|
||||
{
|
||||
xcb_client_message_event_t ev;
|
||||
|
||||
ev.response_type = XCB_CLIENT_MESSAGE;
|
||||
ev.window = towin;
|
||||
ev.format = 32;
|
||||
ev.data.data32[0] = XCB_CURRENT_TIME;
|
||||
ev.data.data32[1] = message;
|
||||
ev.data.data32[2] = d1;
|
||||
ev.data.data32[3] = d2;
|
||||
ev.data.data32[4] = d3;
|
||||
ev.type = Xcb::atoms->xembedAtom;
|
||||
xcb_send_event(qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->connection(), false, towin, XCB_EVENT_MASK_NO_EVENT, (char *)&ev);
|
||||
}
|
||||
|
||||
static bool checkWindowOrDescendantWantButtonEvents(xcb_window_t window)
|
||||
{
|
||||
auto connection = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->connection();
|
||||
auto waCookie = xcb_get_window_attributes(connection, window);
|
||||
UniqueCPointer<xcb_get_window_attributes_reply_t> windowAttributes(xcb_get_window_attributes_reply(connection, waCookie, nullptr));
|
||||
if (windowAttributes && windowAttributes->all_event_masks & XCB_EVENT_MASK_BUTTON_PRESS) {
|
||||
return true;
|
||||
}
|
||||
if (windowAttributes && windowAttributes->do_not_propagate_mask & XCB_EVENT_MASK_BUTTON_PRESS) {
|
||||
return false;
|
||||
}
|
||||
auto treeCookie = xcb_query_tree(connection, window);
|
||||
UniqueCPointer<xcb_query_tree_reply_t> tree(xcb_query_tree_reply(connection, treeCookie, nullptr));
|
||||
if (!tree) {
|
||||
return false;
|
||||
}
|
||||
std::span<xcb_window_t> children(xcb_query_tree_children(tree.get()), xcb_query_tree_children_length(tree.get()));
|
||||
return std::ranges::any_of(children, &checkWindowOrDescendantWantButtonEvents);
|
||||
}
|
||||
|
||||
TrayManagerProxy::TrayManagerProxy(xcb_window_t wid, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_x11Interface(qGuiApp->nativeInterface<QNativeInterface::QX11Application>())
|
||||
, m_windowId(wid)
|
||||
, m_injectMode(Direct)
|
||||
{
|
||||
m_name = getWindowName();
|
||||
|
||||
auto c = m_x11Interface->connection();
|
||||
|
||||
// create a container window
|
||||
auto screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data;
|
||||
m_containerWid = xcb_generate_id(c);
|
||||
uint32_t values[3];
|
||||
uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
|
||||
values[0] = screen->black_pixel;
|
||||
values[1] = true;
|
||||
values[2] = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
|
||||
xcb_create_window(c,
|
||||
XCB_COPY_FROM_PARENT,
|
||||
m_containerWid,
|
||||
screen->root,
|
||||
0, 0,
|
||||
s_embedSize, s_embedSize,
|
||||
0,
|
||||
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||
screen->root_visual,
|
||||
mask,
|
||||
values);
|
||||
|
||||
setActiveForInput(false);
|
||||
|
||||
NETWinInfo wm(c, m_containerWid, screen->root, NET::Properties(), NET::Properties2());
|
||||
wm.setOpacity(0);
|
||||
|
||||
xcb_flush(c);
|
||||
xcb_map_window(c, m_containerWid);
|
||||
|
||||
xcb_reparent_window(c, wid, m_containerWid, 0, 0);
|
||||
|
||||
// Render the embedded window offscreen
|
||||
xcb_composite_redirect_window(c, wid, XCB_COMPOSITE_REDIRECT_MANUAL);
|
||||
|
||||
xcb_change_save_set(c, XCB_SET_MODE_INSERT, wid);
|
||||
|
||||
// tell client we're embedding it
|
||||
xembed_message_send(wid, XEMBED_EMBEDDED_NOTIFY, 0, m_containerWid, XEMBED_VERSION);
|
||||
|
||||
// move window we're embedding
|
||||
const uint32_t windowMoveConfigVals[2] = {0, 0};
|
||||
xcb_configure_window(c, wid, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, windowMoveConfigVals);
|
||||
|
||||
QSize clientWindowSize = calculateClientWindowSize();
|
||||
|
||||
// show the embedded window
|
||||
xcb_map_window(c, wid);
|
||||
xcb_clear_area(c, 0, wid, 0, 0, clientWindowSize.width(), clientWindowSize.height());
|
||||
xcb_flush(c);
|
||||
|
||||
// guess which input injection method to use
|
||||
auto waCookie = xcb_get_window_attributes(c, wid);
|
||||
UniqueCPointer<xcb_get_window_attributes_reply_t> windowAttributes(xcb_get_window_attributes_reply(c, waCookie, nullptr));
|
||||
if (!checkWindowOrDescendantWantButtonEvents(wid)) {
|
||||
m_injectMode = XTest;
|
||||
}
|
||||
|
||||
// First update after delay
|
||||
QTimer::singleShot(500, this, &TrayManagerProxy::update);
|
||||
}
|
||||
|
||||
TrayManagerProxy::~TrayManagerProxy()
|
||||
{
|
||||
auto c = m_x11Interface->connection();
|
||||
xcb_destroy_window(c, m_containerWid);
|
||||
xcb_flush(c);
|
||||
}
|
||||
|
||||
QString TrayManagerProxy::getWindowName() const
|
||||
{
|
||||
auto connection = m_x11Interface->connection();
|
||||
KWindowInfo info(m_windowId, NET::WMName | NET::WMIconName);
|
||||
return info.name();
|
||||
}
|
||||
|
||||
void TrayManagerProxy::update()
|
||||
{
|
||||
QImage newImage = getImageNonComposite();
|
||||
|
||||
if (!newImage.isNull() && newImage != m_lastImage) {
|
||||
m_lastImage = newImage;
|
||||
// Icon image changed, could trigger update on TrayManager1
|
||||
// This would be handled at a higher level
|
||||
}
|
||||
|
||||
QTimer::singleShot(100, this, &TrayManagerProxy::update);
|
||||
}
|
||||
|
||||
void TrayManagerProxy::resizeWindow(const uint16_t width, const uint16_t height) const
|
||||
{
|
||||
auto c = m_x11Interface->connection();
|
||||
const uint32_t values[] = {width, height};
|
||||
xcb_configure_window(c, m_windowId, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
|
||||
xcb_flush(c);
|
||||
}
|
||||
|
||||
QSize TrayManagerProxy::calculateClientWindowSize() const
|
||||
{
|
||||
auto c = m_x11Interface->connection();
|
||||
auto geoCookie = xcb_get_geometry(c, m_windowId);
|
||||
UniqueCPointer<xcb_get_geometry_reply_t> geo(xcb_get_geometry_reply(c, geoCookie, nullptr));
|
||||
if (geo) {
|
||||
return QSize(geo->width, geo->height);
|
||||
}
|
||||
return QSize(s_embedSize, s_embedSize);
|
||||
}
|
||||
|
||||
void TrayManagerProxy::sendClick(uint8_t mouseButton, int x, int y)
|
||||
{
|
||||
if (m_injectMode == XTest) {
|
||||
// Use XTest helper functions defined in xtestsender.h
|
||||
sendXTestPressed(m_x11Interface->display(), mouseButton);
|
||||
sendXTestReleased(m_x11Interface->display(), mouseButton);
|
||||
return;
|
||||
}
|
||||
|
||||
auto c = m_x11Interface->connection();
|
||||
xcb_button_press_event_t pressEvent{};
|
||||
memset(&pressEvent, 0, sizeof(pressEvent));
|
||||
pressEvent.response_type = XCB_BUTTON_PRESS;
|
||||
pressEvent.event = m_windowId;
|
||||
pressEvent.time = XCB_CURRENT_TIME;
|
||||
pressEvent.same_screen = 1;
|
||||
pressEvent.root = DefaultRootWindow(m_x11Interface->display());
|
||||
pressEvent.root_x = x;
|
||||
pressEvent.root_y = y;
|
||||
pressEvent.event_x = static_cast<int16_t>(x);
|
||||
pressEvent.event_y = static_cast<int16_t>(y);
|
||||
pressEvent.child = 0;
|
||||
pressEvent.state = 0;
|
||||
pressEvent.detail = mouseButton;
|
||||
|
||||
xcb_send_event(c, false, m_windowId, XCB_EVENT_MASK_BUTTON_PRESS, reinterpret_cast<const char *>(&pressEvent));
|
||||
|
||||
xcb_button_release_event_t releaseEvent{};
|
||||
memset(&releaseEvent, 0, sizeof(releaseEvent));
|
||||
releaseEvent.response_type = XCB_BUTTON_RELEASE;
|
||||
releaseEvent.event = m_windowId;
|
||||
releaseEvent.time = XCB_CURRENT_TIME;
|
||||
releaseEvent.same_screen = 1;
|
||||
releaseEvent.root = DefaultRootWindow(m_x11Interface->display());
|
||||
releaseEvent.root_x = x;
|
||||
releaseEvent.root_y = y;
|
||||
releaseEvent.event_x = static_cast<int16_t>(x);
|
||||
releaseEvent.event_y = static_cast<int16_t>(y);
|
||||
releaseEvent.child = 0;
|
||||
releaseEvent.state = 0;
|
||||
releaseEvent.detail = mouseButton;
|
||||
|
||||
xcb_send_event(c, false, m_windowId, XCB_EVENT_MASK_BUTTON_RELEASE, reinterpret_cast<const char *>(&releaseEvent));
|
||||
xcb_flush(c);
|
||||
}
|
||||
|
||||
QImage TrayManagerProxy::getImageNonComposite() const
|
||||
{
|
||||
auto c = m_x11Interface->connection();
|
||||
|
||||
auto geoCookie = xcb_get_geometry(c, m_windowId);
|
||||
UniqueCPointer<xcb_get_geometry_reply_t> geo(xcb_get_geometry_reply(c, geoCookie, nullptr));
|
||||
if (!geo) {
|
||||
return QImage();
|
||||
}
|
||||
// Use xcb_image_get directly on the drawable. Use UINT32_MAX for plane mask.
|
||||
xcb_image_t *xcbimg = xcb_image_get(c, m_windowId, 0, 0, geo->width, geo->height, UINT32_MAX, XCB_IMAGE_FORMAT_Z_PIXMAP);
|
||||
if (!xcbimg) {
|
||||
return QImage();
|
||||
}
|
||||
|
||||
return convertFromNative(xcbimg);
|
||||
}
|
||||
|
||||
bool TrayManagerProxy::isTransparentImage(const QImage &image) const
|
||||
{
|
||||
if (image.format() != QImage::Format_ARGB32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QRgb *data = reinterpret_cast<const QRgb *>(image.bits());
|
||||
for (int i = 0; i < image.width() * image.height(); ++i) {
|
||||
if (qAlpha(data[i]) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QImage TrayManagerProxy::convertFromNative(xcb_image_t *xcbImage) const
|
||||
{
|
||||
if (!xcbImage) {
|
||||
return QImage();
|
||||
}
|
||||
|
||||
QImage qimage(xcbImage->width, xcbImage->height, QImage::Format_ARGB32);
|
||||
memcpy(qimage.bits(), xcbImage->data, qimage.sizeInBytes());
|
||||
|
||||
return qimage;
|
||||
}
|
||||
|
||||
QPoint TrayManagerProxy::calculateClickPoint() const
|
||||
{
|
||||
return QPoint(s_embedSize / 2, s_embedSize / 2);
|
||||
}
|
||||
|
||||
void TrayManagerProxy::setActiveForInput(bool active) const
|
||||
{
|
||||
auto c = m_x11Interface->connection();
|
||||
auto screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data;
|
||||
|
||||
xcb_rectangle_t rect = {0, 0, 0, 0};
|
||||
if (active) {
|
||||
rect.width = s_embedSize;
|
||||
rect.height = s_embedSize;
|
||||
}
|
||||
|
||||
xcb_shape_rectangles(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, m_containerWid, 0, 0, 1, &rect);
|
||||
xcb_flush(c);
|
||||
}
|
||||
Reference in New Issue
Block a user