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 }