File indexing completed on 2024-05-05 05:35:17

0001 /*
0002     SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "oxygensizegrip.h"
0008 
0009 #include <KDecoration2/DecoratedClient>
0010 
0011 #include <QPainter>
0012 #include <QPalette>
0013 #include <QPolygon>
0014 #include <QTimer>
0015 
0016 #if OXYGEN_HAVE_X11
0017 #include <private/qtx11extras_p.h>
0018 #endif
0019 
0020 namespace Oxygen
0021 {
0022 //* scoped pointer convenience typedef
0023 template<typename T>
0024 using ScopedPointer = QScopedPointer<T, QScopedPointerPodDeleter>;
0025 
0026 //_____________________________________________
0027 SizeGrip::SizeGrip(Decoration *decoration)
0028     : QWidget(nullptr)
0029     , m_decoration(decoration)
0030 {
0031     setAttribute(Qt::WA_NoSystemBackground);
0032     setAutoFillBackground(false);
0033 
0034     // cursor
0035     setCursor(Qt::SizeFDiagCursor);
0036 
0037     // size
0038     setFixedSize(QSize(GripSize, GripSize));
0039 
0040     // mask
0041     QPolygon p;
0042     p << QPoint(0, GripSize) << QPoint(GripSize, 0) << QPoint(GripSize, GripSize) << QPoint(0, GripSize);
0043 
0044     setMask(QRegion(p));
0045 
0046     // embed
0047     embed();
0048     updatePosition();
0049 
0050     // connections
0051     const auto *clientP = decoration->client();
0052     connect(clientP, &KDecoration2::DecoratedClient::widthChanged, this, &SizeGrip::updatePosition);
0053     connect(clientP, &KDecoration2::DecoratedClient::heightChanged, this, &SizeGrip::updatePosition);
0054     connect(clientP, &KDecoration2::DecoratedClient::activeChanged, this, &SizeGrip::updateActiveState);
0055 
0056     // show
0057     show();
0058 }
0059 
0060 //_____________________________________________
0061 void SizeGrip::updateActiveState(void)
0062 {
0063 #if OXYGEN_HAVE_X11
0064     if (QX11Info::isPlatformX11()) {
0065         const quint32 value = XCB_STACK_MODE_ABOVE;
0066         xcb_configure_window(QX11Info::connection(), winId(), XCB_CONFIG_WINDOW_STACK_MODE, &value);
0067         xcb_map_window(QX11Info::connection(), winId());
0068     }
0069 #endif
0070 
0071     update();
0072 }
0073 
0074 //_____________________________________________
0075 void SizeGrip::embed(void)
0076 {
0077 #if OXYGEN_HAVE_X11
0078 
0079     if (!QX11Info::isPlatformX11())
0080         return;
0081 
0082     xcb_window_t windowId = m_decoration->client()->windowId();
0083     if (windowId) {
0084         /*
0085         find client's parent
0086         we want the size grip to be at the same level as the client in the stack
0087         */
0088         xcb_window_t current = windowId;
0089         auto connection = QX11Info::connection();
0090         xcb_query_tree_cookie_t cookie = xcb_query_tree_unchecked(connection, current);
0091         ScopedPointer<xcb_query_tree_reply_t> tree(xcb_query_tree_reply(connection, cookie, nullptr));
0092         if (!tree.isNull() && tree->parent)
0093             current = tree->parent;
0094 
0095         // reparent
0096         xcb_reparent_window(connection, winId(), current, 0, 0);
0097         setWindowTitle(QStringLiteral("Oxygen::SizeGrip"));
0098 
0099     } else {
0100         hide();
0101     }
0102 
0103 #endif
0104 }
0105 
0106 //_____________________________________________
0107 void SizeGrip::paintEvent(QPaintEvent *)
0108 {
0109     if (!m_decoration)
0110         return;
0111 
0112     // get relevant colors
0113     const QColor backgroundColor(m_decoration->client()->palette().color(QPalette::Window));
0114 
0115     // create and configure painter
0116     QPainter painter(this);
0117     painter.setRenderHints(QPainter::Antialiasing);
0118 
0119     painter.setPen(Qt::NoPen);
0120     painter.setBrush(backgroundColor);
0121 
0122     // polygon
0123     QPolygon p;
0124     p << QPoint(0, GripSize) << QPoint(GripSize, 0) << QPoint(GripSize, GripSize) << QPoint(0, GripSize);
0125     painter.drawPolygon(p);
0126 }
0127 
0128 //_____________________________________________
0129 void SizeGrip::mousePressEvent(QMouseEvent *event)
0130 {
0131     switch (event->button()) {
0132     case Qt::RightButton: {
0133         hide();
0134         QTimer::singleShot(5000, this, SLOT(show()));
0135         break;
0136     }
0137 
0138     case Qt::MiddleButton: {
0139         hide();
0140         break;
0141     }
0142 
0143     case Qt::LeftButton:
0144         if (rect().contains(event->pos())) {
0145             sendMoveResizeEvent(event->pos());
0146         }
0147         break;
0148 
0149     default:
0150         break;
0151     }
0152 
0153     return;
0154 }
0155 
0156 //_______________________________________________________________________________
0157 void SizeGrip::updatePosition(void)
0158 {
0159 #if OXYGEN_HAVE_X11
0160     if (!QX11Info::isPlatformX11())
0161         return;
0162 
0163     const auto c = m_decoration->client();
0164     QPoint position(c->width() - GripSize - Offset, c->height() - GripSize - Offset);
0165 
0166     quint32 values[2] = {quint32(position.x()), quint32(position.y())};
0167     xcb_configure_window(QX11Info::connection(), winId(), XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values);
0168 #endif
0169 }
0170 
0171 //_____________________________________________
0172 void SizeGrip::sendMoveResizeEvent(QPoint position)
0173 {
0174 #if OXYGEN_HAVE_X11
0175     if (!QX11Info::isPlatformX11())
0176         return;
0177 
0178     // pointer to connection
0179     auto connection(QX11Info::connection());
0180 
0181     /*
0182     get root position matching position
0183     need to use xcb because the embedding of the widget
0184     breaks QT's mapToGlobal and other methods
0185     */
0186     QPoint rootPosition(position);
0187     xcb_get_geometry_cookie_t cookie(xcb_get_geometry(connection, winId()));
0188     ScopedPointer<xcb_get_geometry_reply_t> reply(xcb_get_geometry_reply(connection, cookie, nullptr));
0189     if (reply) {
0190         // translate coordinates
0191         xcb_translate_coordinates_cookie_t coordCookie(
0192             xcb_translate_coordinates(connection, winId(), reply.data()->root, -reply.data()->border_width, -reply.data()->border_width));
0193 
0194         ScopedPointer<xcb_translate_coordinates_reply_t> coordReply(xcb_translate_coordinates_reply(connection, coordCookie, nullptr));
0195 
0196         if (coordReply) {
0197             rootPosition.rx() += coordReply.data()->dst_x;
0198             rootPosition.ry() += coordReply.data()->dst_y;
0199         }
0200     }
0201 
0202     // move/resize atom
0203     if (!m_moveResizeAtom) {
0204         // create atom if not found
0205         const QString atomName = QStringLiteral("_NET_WM_MOVERESIZE");
0206         xcb_intern_atom_cookie_t cookie(xcb_intern_atom(connection, false, atomName.size(), qPrintable(atomName)));
0207         ScopedPointer<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply(connection, cookie, nullptr));
0208         m_moveResizeAtom = reply ? reply->atom : 0;
0209     }
0210 
0211     if (!m_moveResizeAtom)
0212         return;
0213 
0214     // button release event
0215     xcb_button_release_event_t releaseEvent;
0216     memset(&releaseEvent, 0, sizeof(releaseEvent));
0217 
0218     releaseEvent.response_type = XCB_BUTTON_RELEASE;
0219     releaseEvent.event = winId();
0220     releaseEvent.child = XCB_WINDOW_NONE;
0221     releaseEvent.root = QX11Info::appRootWindow();
0222     releaseEvent.event_x = position.x();
0223     releaseEvent.event_y = position.y();
0224     releaseEvent.root_x = rootPosition.x();
0225     releaseEvent.root_y = rootPosition.y();
0226     releaseEvent.detail = XCB_BUTTON_INDEX_1;
0227     releaseEvent.state = XCB_BUTTON_MASK_1;
0228     releaseEvent.time = XCB_CURRENT_TIME;
0229     releaseEvent.same_screen = true;
0230     xcb_send_event(connection, false, winId(), XCB_EVENT_MASK_BUTTON_RELEASE, reinterpret_cast<const char *>(&releaseEvent));
0231 
0232     xcb_ungrab_pointer(connection, XCB_TIME_CURRENT_TIME);
0233 
0234     // move resize event
0235     xcb_client_message_event_t clientMessageEvent;
0236     memset(&clientMessageEvent, 0, sizeof(clientMessageEvent));
0237 
0238     clientMessageEvent.response_type = XCB_CLIENT_MESSAGE;
0239     clientMessageEvent.type = m_moveResizeAtom;
0240     clientMessageEvent.format = 32;
0241     clientMessageEvent.window = m_decoration->client()->windowId();
0242     clientMessageEvent.data.data32[0] = rootPosition.x();
0243     clientMessageEvent.data.data32[1] = rootPosition.y();
0244     clientMessageEvent.data.data32[2] = 4; // bottom right
0245     clientMessageEvent.data.data32[3] = Qt::LeftButton;
0246     clientMessageEvent.data.data32[4] = 0;
0247 
0248     xcb_send_event(connection,
0249                    false,
0250                    QX11Info::appRootWindow(),
0251                    XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
0252                    reinterpret_cast<const char *>(&clientMessageEvent));
0253 
0254     xcb_flush(connection);
0255 #endif
0256 }
0257 }