You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
7.8 KiB
263 lines
7.8 KiB
/* |
|
* SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr> |
|
* |
|
* SPDX-License-Identifier: GPL-2.0-or-later |
|
*/ |
|
|
|
#include "breezesizegrip.h" |
|
|
|
#include <KDecoration2/DecoratedClient> |
|
|
|
#include <QPainter> |
|
#include <QPolygon> |
|
#include <QTimer> |
|
|
|
#if BREEZE_HAVE_X11 |
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
|
#include <private/qtx11extras_p.h> |
|
#else |
|
#include <QX11Info> |
|
#endif |
|
#endif |
|
|
|
namespace Breeze |
|
{ |
|
//* scoped pointer convenience typedef |
|
template<typename T> |
|
using ScopedPointer = QScopedPointer<T, QScopedPointerPodDeleter>; |
|
|
|
//_____________________________________________ |
|
SizeGrip::SizeGrip(Decoration *decoration) |
|
: QWidget(nullptr) |
|
, m_decoration(decoration) |
|
{ |
|
setAttribute(Qt::WA_NoSystemBackground); |
|
setAutoFillBackground(false); |
|
|
|
// cursor |
|
setCursor(Qt::SizeFDiagCursor); |
|
|
|
// size |
|
setFixedSize(QSize(GripSize, GripSize)); |
|
|
|
// mask |
|
setMask(QRegion(QVector<QPoint>{QPoint(0, GripSize), QPoint(0,0), QPoint(GripSize, 0), QPoint(GripSize, GripSize), QPoint(0, GripSize)})); |
|
|
|
// embed |
|
embed(); |
|
updatePosition(); |
|
|
|
// connections |
|
const auto c = decoration->client().toStrongRef(); |
|
connect(c.data(), &KDecoration2::DecoratedClient::widthChanged, this, &SizeGrip::updatePosition); |
|
connect(c.data(), &KDecoration2::DecoratedClient::heightChanged, this, &SizeGrip::updatePosition); |
|
connect(c.data(), &KDecoration2::DecoratedClient::activeChanged, this, &SizeGrip::updateActiveState); |
|
|
|
// show |
|
show(); |
|
} |
|
|
|
//_____________________________________________ |
|
SizeGrip::~SizeGrip() |
|
{ |
|
} |
|
|
|
//_____________________________________________ |
|
void SizeGrip::updateActiveState() |
|
{ |
|
#if BREEZE_HAVE_X11 |
|
if (QX11Info::isPlatformX11()) { |
|
const quint32 value = XCB_STACK_MODE_ABOVE; |
|
xcb_configure_window(QX11Info::connection(), winId(), XCB_CONFIG_WINDOW_STACK_MODE, &value); |
|
xcb_map_window(QX11Info::connection(), winId()); |
|
} |
|
#endif |
|
|
|
update(); |
|
} |
|
|
|
//_____________________________________________ |
|
void SizeGrip::embed() |
|
{ |
|
#if BREEZE_HAVE_X11 |
|
|
|
if (!QX11Info::isPlatformX11()) |
|
return; |
|
const auto c = m_decoration.data()->client().toStrongRef(); |
|
|
|
xcb_window_t windowId = c->windowId(); |
|
if (windowId) { |
|
/* |
|
find client's parent |
|
we want the size grip to be at the same level as the client in the stack |
|
*/ |
|
xcb_window_t current = windowId; |
|
auto connection = QX11Info::connection(); |
|
xcb_query_tree_cookie_t cookie = xcb_query_tree_unchecked(connection, current); |
|
ScopedPointer<xcb_query_tree_reply_t> tree(xcb_query_tree_reply(connection, cookie, nullptr)); |
|
if (!tree.isNull() && tree->parent) |
|
current = tree->parent; |
|
|
|
// reparent |
|
xcb_reparent_window(connection, winId(), current, 0, 0); |
|
setWindowTitle("Breeze::SizeGrip"); |
|
|
|
} else { |
|
hide(); |
|
} |
|
|
|
#endif |
|
} |
|
|
|
//_____________________________________________ |
|
void SizeGrip::paintEvent(QPaintEvent *) |
|
{ |
|
if (!m_decoration) |
|
return; |
|
|
|
// get relevant colors |
|
const QColor backgroundColor(m_decoration.data()->titleBarColor()); |
|
|
|
// create and configure painter |
|
QPainter painter(this); |
|
painter.setRenderHints(QPainter::Antialiasing); |
|
|
|
painter.setPen(Qt::NoPen); |
|
painter.setBrush(backgroundColor); |
|
|
|
// polygon |
|
painter.drawPolygon(QVector<QPoint>{QPoint(0, GripSize), QPoint(0,0), QPoint(GripSize, 0), QPoint(GripSize, GripSize), QPoint(0, GripSize)}); |
|
} |
|
|
|
//_____________________________________________ |
|
void SizeGrip::mousePressEvent(QMouseEvent *event) |
|
{ |
|
switch (event->button()) { |
|
case Qt::RightButton: { |
|
hide(); |
|
QTimer::singleShot(5000, this, &QWidget::show); |
|
break; |
|
} |
|
|
|
case Qt::MiddleButton: { |
|
hide(); |
|
break; |
|
} |
|
|
|
case Qt::LeftButton: |
|
if (rect().contains(event->pos())) { |
|
sendMoveResizeEvent(event->pos()); |
|
} |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
} |
|
|
|
//_______________________________________________________________________________ |
|
void SizeGrip::updatePosition() |
|
{ |
|
#if BREEZE_HAVE_X11 |
|
if (!QX11Info::isPlatformX11()) |
|
return; |
|
|
|
const auto c = m_decoration.data()->client().toStrongRef(); |
|
QPoint position(c->width() - GripSize - Offset, c->height() - GripSize - Offset); |
|
|
|
quint32 values[2] = {quint32(position.x()), quint32(position.y())}; |
|
xcb_configure_window(QX11Info::connection(), winId(), XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values); |
|
#endif |
|
} |
|
|
|
//_____________________________________________ |
|
void SizeGrip::sendMoveResizeEvent(QPoint position) |
|
{ |
|
#if BREEZE_HAVE_X11 |
|
if (!QX11Info::isPlatformX11()) |
|
return; |
|
|
|
// pointer to connection |
|
auto connection(QX11Info::connection()); |
|
|
|
// client |
|
const auto c = m_decoration.data()->client().toStrongRef(); |
|
|
|
/* |
|
get root position matching position |
|
need to use xcb because the embedding of the widget |
|
breaks QT's mapToGlobal and other methods |
|
*/ |
|
QPoint rootPosition(position); |
|
xcb_get_geometry_cookie_t cookie(xcb_get_geometry(connection, winId())); |
|
ScopedPointer<xcb_get_geometry_reply_t> reply(xcb_get_geometry_reply(connection, cookie, nullptr)); |
|
if (reply) { |
|
// translate coordinates |
|
xcb_translate_coordinates_cookie_t coordCookie( |
|
xcb_translate_coordinates(connection, winId(), reply.data()->root, -reply.data()->border_width, -reply.data()->border_width)); |
|
|
|
ScopedPointer<xcb_translate_coordinates_reply_t> coordReply(xcb_translate_coordinates_reply(connection, coordCookie, nullptr)); |
|
|
|
if (coordReply) { |
|
rootPosition.rx() += coordReply.data()->dst_x; |
|
rootPosition.ry() += coordReply.data()->dst_y; |
|
} |
|
} |
|
|
|
// move/resize atom |
|
if (!m_moveResizeAtom) { |
|
// create atom if not found |
|
const QString atomName("_NET_WM_MOVERESIZE"); |
|
xcb_intern_atom_cookie_t cookie(xcb_intern_atom(connection, false, atomName.size(), qPrintable(atomName))); |
|
ScopedPointer<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply(connection, cookie, nullptr)); |
|
m_moveResizeAtom = reply ? reply->atom : 0; |
|
} |
|
|
|
if (!m_moveResizeAtom) |
|
return; |
|
|
|
// button release event |
|
xcb_button_release_event_t releaseEvent; |
|
memset(&releaseEvent, 0, sizeof(releaseEvent)); |
|
|
|
releaseEvent.response_type = XCB_BUTTON_RELEASE; |
|
releaseEvent.event = winId(); |
|
releaseEvent.child = XCB_WINDOW_NONE; |
|
releaseEvent.root = QX11Info::appRootWindow(); |
|
releaseEvent.event_x = position.x(); |
|
releaseEvent.event_y = position.y(); |
|
releaseEvent.root_x = rootPosition.x(); |
|
releaseEvent.root_y = rootPosition.y(); |
|
releaseEvent.detail = XCB_BUTTON_INDEX_1; |
|
releaseEvent.state = XCB_BUTTON_MASK_1; |
|
releaseEvent.time = XCB_CURRENT_TIME; |
|
releaseEvent.same_screen = true; |
|
xcb_send_event(connection, false, winId(), XCB_EVENT_MASK_BUTTON_RELEASE, reinterpret_cast<const char *>(&releaseEvent)); |
|
|
|
xcb_ungrab_pointer(connection, XCB_TIME_CURRENT_TIME); |
|
|
|
// move resize event |
|
xcb_client_message_event_t clientMessageEvent; |
|
memset(&clientMessageEvent, 0, sizeof(clientMessageEvent)); |
|
|
|
clientMessageEvent.response_type = XCB_CLIENT_MESSAGE; |
|
clientMessageEvent.type = m_moveResizeAtom; |
|
clientMessageEvent.format = 32; |
|
clientMessageEvent.window = c->windowId(); |
|
clientMessageEvent.data.data32[0] = rootPosition.x(); |
|
clientMessageEvent.data.data32[1] = rootPosition.y(); |
|
clientMessageEvent.data.data32[2] = 4; // bottom right |
|
clientMessageEvent.data.data32[3] = Qt::LeftButton; |
|
clientMessageEvent.data.data32[4] = 0; |
|
|
|
xcb_send_event(connection, |
|
false, |
|
QX11Info::appRootWindow(), |
|
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, |
|
reinterpret_cast<const char *>(&clientMessageEvent)); |
|
|
|
xcb_flush(connection); |
|
#endif |
|
} |
|
|
|
}
|
|
|