File indexing completed on 2024-04-21 05:36:01

0001 /*
0002     SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include <config-plasma.h>
0008 
0009 #include "autohidescreenedge.h"
0010 #include "debug.h"
0011 #include "panelview.h"
0012 
0013 #include "qwayland-kde-screen-edge-v1.h"
0014 
0015 #include <KWindowSystem>
0016 
0017 #include <QDebug>
0018 #include <QWindow>
0019 #include <QtWaylandClient/QWaylandClientExtension>
0020 #include <QtWaylandClient/QtWaylandClientVersion>
0021 #include <qpa/qplatformwindow_p.h>
0022 
0023 #if HAVE_X11
0024 #include <private/qtx11extras_p.h>
0025 #include <xcb/xcb.h>
0026 #endif
0027 
0028 class WaylandScreenEdgeManagerV1 : public QWaylandClientExtensionTemplate<WaylandScreenEdgeManagerV1>, public QtWayland::kde_screen_edge_manager_v1
0029 {
0030     Q_OBJECT
0031 
0032 public:
0033     WaylandScreenEdgeManagerV1()
0034         : QWaylandClientExtensionTemplate(1)
0035     {
0036         initialize();
0037     }
0038 
0039     ~WaylandScreenEdgeManagerV1() override
0040     {
0041         if (isInitialized()) {
0042             destroy();
0043         }
0044     }
0045 };
0046 
0047 class WaylandAutoHideScreenEdgeV1 : public QtWayland::kde_auto_hide_screen_edge_v1
0048 {
0049 public:
0050     WaylandAutoHideScreenEdgeV1(Plasma::Types::Location location, ::kde_auto_hide_screen_edge_v1 *edge)
0051         : QtWayland::kde_auto_hide_screen_edge_v1(edge)
0052         , location(location)
0053     {
0054     }
0055 
0056     ~WaylandAutoHideScreenEdgeV1() override
0057     {
0058         destroy();
0059     }
0060 
0061     const Plasma::Types::Location location;
0062 };
0063 
0064 class WaylandAutoHideScreenEdge : public AutoHideScreenEdge
0065 {
0066     Q_OBJECT
0067 
0068 public:
0069     WaylandAutoHideScreenEdge(PanelView *view);
0070 
0071     void deactivate() override;
0072     void activate() override;
0073 
0074 protected:
0075     bool eventFilter(QObject *watched, QEvent *event) override;
0076 
0077 private:
0078     bool create();
0079     void destroy();
0080 
0081     std::unique_ptr<WaylandScreenEdgeManagerV1> m_manager;
0082     std::unique_ptr<WaylandAutoHideScreenEdgeV1> m_edge;
0083     bool m_active = false;
0084 };
0085 
0086 WaylandAutoHideScreenEdge::WaylandAutoHideScreenEdge(PanelView *view)
0087     : AutoHideScreenEdge(view)
0088 {
0089     m_manager = std::make_unique<WaylandScreenEdgeManagerV1>();
0090     if (!m_manager->isActive()) {
0091         qCWarning(PLASMASHELL) << m_manager->extensionInterface()->name << "is unsupported by the compositor. Panel autohide won't work correctly";
0092         return;
0093     }
0094 
0095     // Create a screen edge only if there's a surface role. For now, there's no better alternative
0096     // than check if the window is exposed.
0097     view->installEventFilter(this);
0098 }
0099 
0100 bool WaylandAutoHideScreenEdge::eventFilter(QObject *watched, QEvent *event)
0101 {
0102     Q_UNUSED(watched)
0103     if (event->type() == QEvent::Expose) {
0104         if (!m_edge && m_active) {
0105             if (create()) {
0106                 m_edge->activate();
0107             }
0108         }
0109     } else if (event->type() == QEvent::Hide) {
0110         destroy();
0111     }
0112     return false;
0113 }
0114 
0115 void WaylandAutoHideScreenEdge::deactivate()
0116 {
0117     m_active = false;
0118     if (m_edge) {
0119         m_edge->deactivate();
0120     }
0121 }
0122 
0123 void WaylandAutoHideScreenEdge::activate()
0124 {
0125     if (!m_manager->isActive()) {
0126         return;
0127     }
0128 
0129     if (m_edge && m_edge->location != m_view->location()) {
0130         m_edge.reset();
0131     }
0132 
0133     if (!m_edge) {
0134         if (m_view->isExposed()) {
0135             create();
0136         }
0137     }
0138 
0139     m_active = true;
0140     if (m_edge) {
0141         m_edge->activate();
0142     }
0143 }
0144 
0145 bool WaylandAutoHideScreenEdge::create()
0146 {
0147     auto waylandWindow = m_view->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
0148     if (!waylandWindow || !waylandWindow->surface()) {
0149         return false;
0150     }
0151 
0152     uint32_t border;
0153     switch (m_view->location()) {
0154     case Plasma::Types::Location::LeftEdge:
0155         border = QtWayland::kde_screen_edge_manager_v1::border_left;
0156         break;
0157     case Plasma::Types::Location::RightEdge:
0158         border = QtWayland::kde_screen_edge_manager_v1::border_right;
0159         break;
0160     case Plasma::Types::Location::TopEdge:
0161         border = QtWayland::kde_screen_edge_manager_v1::border_top;
0162         break;
0163     case Plasma::Types::Location::BottomEdge:
0164     default:
0165         border = QtWayland::kde_screen_edge_manager_v1::border_bottom;
0166         break;
0167     }
0168 
0169     m_edge = std::make_unique<WaylandAutoHideScreenEdgeV1>(m_view->location(), m_manager->get_auto_hide_screen_edge(border, waylandWindow->surface()));
0170     return true;
0171 }
0172 
0173 void WaylandAutoHideScreenEdge::destroy()
0174 {
0175     m_edge.reset();
0176 }
0177 
0178 #if HAVE_X11
0179 
0180 class X11AutoHideScreenEdge : public AutoHideScreenEdge
0181 {
0182     Q_OBJECT
0183 
0184 public:
0185     X11AutoHideScreenEdge(PanelView *view);
0186     ~X11AutoHideScreenEdge() override;
0187 
0188     void deactivate() override;
0189     void activate() override;
0190 
0191 private:
0192     xcb_atom_t m_atom = XCB_ATOM_NONE;
0193 };
0194 
0195 X11AutoHideScreenEdge::X11AutoHideScreenEdge(PanelView *view)
0196     : AutoHideScreenEdge(view)
0197 {
0198     xcb_connection_t *connection = QX11Info::connection();
0199 
0200     const QByteArray atomName = QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW");
0201     xcb_intern_atom_cookie_t cookie = xcb_intern_atom_unchecked(connection, false, atomName.length(), atomName.constData());
0202     xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(connection, cookie, nullptr);
0203     if (reply) {
0204         m_atom = reply->atom;
0205         free(reply);
0206     }
0207 }
0208 
0209 X11AutoHideScreenEdge::~X11AutoHideScreenEdge()
0210 {
0211     if (!m_view) {
0212         return;
0213     }
0214     deactivate();
0215 }
0216 
0217 void X11AutoHideScreenEdge::deactivate()
0218 {
0219     if (m_atom != XCB_ATOM_NONE) {
0220         xcb_delete_property(QX11Info::connection(), m_view->winId(), m_atom);
0221     }
0222 }
0223 
0224 void X11AutoHideScreenEdge::activate()
0225 {
0226     if (m_atom == XCB_ATOM_NONE) {
0227         return;
0228     }
0229 
0230     uint32_t value = 0;
0231 
0232     switch (m_view->location()) {
0233     case Plasma::Types::TopEdge:
0234         value = 0;
0235         break;
0236     case Plasma::Types::RightEdge:
0237         value = 1;
0238         break;
0239     case Plasma::Types::BottomEdge:
0240         value = 2;
0241         break;
0242     case Plasma::Types::LeftEdge:
0243         value = 3;
0244         break;
0245     case Plasma::Types::Floating:
0246     default:
0247         value = 4;
0248         break;
0249     }
0250 
0251     xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, m_view->winId(), m_atom, XCB_ATOM_CARDINAL, 32, 1, &value);
0252 }
0253 
0254 #endif
0255 
0256 AutoHideScreenEdge::AutoHideScreenEdge(PanelView *view)
0257     : QObject(view)
0258     , m_view(view)
0259 {
0260 }
0261 
0262 AutoHideScreenEdge *AutoHideScreenEdge::create(PanelView *view)
0263 {
0264     if (KWindowSystem::isPlatformWayland()) {
0265         return new WaylandAutoHideScreenEdge(view);
0266     } else {
0267 #if HAVE_X11
0268         return new X11AutoHideScreenEdge(view);
0269 #else
0270         Q_UNREACHABLE();
0271 #endif
0272     }
0273 }
0274 
0275 #include "autohidescreenedge.moc"