File indexing completed on 2024-04-28 16:49:19

0001 /*
0002     SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 #include "utils/xcbutils.h"
0007 
0008 #include <QApplication>
0009 #include <QCheckBox>
0010 #include <QHBoxLayout>
0011 #include <QMenu>
0012 #include <QPlatformSurfaceEvent>
0013 #include <QPushButton>
0014 #include <QScreen>
0015 #include <QTimer>
0016 #include <QToolButton>
0017 #include <QWidget>
0018 #include <QWindow>
0019 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0020 #include <private/qtx11extras_p.h>
0021 #else
0022 #include <QX11Info>
0023 #endif
0024 
0025 #include <KWindowSystem>
0026 
0027 #include <KWayland/Client/connection_thread.h>
0028 #include <KWayland/Client/plasmashell.h>
0029 #include <KWayland/Client/registry.h>
0030 #include <KWayland/Client/surface.h>
0031 
0032 class ScreenEdgeHelper : public QObject
0033 {
0034     Q_OBJECT
0035 protected:
0036     ScreenEdgeHelper(QWidget *widget, QObject *parent = nullptr);
0037 
0038     QWindow *window() const
0039     {
0040         return m_widget->windowHandle();
0041     }
0042 
0043     virtual void restore() = 0;
0044 
0045 public:
0046     ~ScreenEdgeHelper() override;
0047 
0048     virtual void hide() = 0;
0049     virtual void raiseOrShow(bool raise) = 0;
0050     virtual void init(){};
0051 
0052     virtual void moveToTop();
0053     virtual void moveToRight();
0054     virtual void moveToBottom();
0055     virtual void moveToLeft();
0056     virtual void moveToFloating();
0057 
0058     void hideAndRestore()
0059     {
0060         hide();
0061         m_timer->start(10000);
0062     }
0063 
0064 private:
0065     QWidget *m_widget;
0066     QTimer *m_timer;
0067 };
0068 
0069 class ScreenEdgeHelperX11 : public ScreenEdgeHelper
0070 {
0071     Q_OBJECT
0072 public:
0073     ScreenEdgeHelperX11(QWidget *widget, QObject *parent = nullptr);
0074     ~ScreenEdgeHelperX11() override = default;
0075 
0076     void hide() override;
0077     void raiseOrShow(bool raise) override;
0078 
0079     void moveToTop() override;
0080     void moveToRight() override;
0081     void moveToBottom() override;
0082     void moveToLeft() override;
0083     void moveToFloating() override;
0084 
0085 protected:
0086     void restore() override;
0087 
0088 private:
0089     uint32_t m_locationValue = 2;
0090     uint32_t m_actionValue = 0;
0091     KWin::Xcb::Atom m_atom;
0092 };
0093 
0094 class ScreenEdgeHelperWayland : public ScreenEdgeHelper
0095 {
0096     Q_OBJECT
0097 public:
0098     ScreenEdgeHelperWayland(QWidget *widget, QObject *parent = nullptr);
0099     ~ScreenEdgeHelperWayland() override = default;
0100 
0101     void hide() override;
0102     void raiseOrShow(bool raise) override;
0103     void init() override;
0104 
0105     bool eventFilter(QObject *watched, QEvent *event) override;
0106 
0107 protected:
0108     void restore() override;
0109 
0110 private:
0111     void setupSurface();
0112     KWayland::Client::PlasmaShell *m_shell = nullptr;
0113     KWayland::Client::PlasmaShellSurface *m_shellSurface = nullptr;
0114     bool m_autoHide = true;
0115 };
0116 
0117 ScreenEdgeHelper::ScreenEdgeHelper(QWidget *widget, QObject *parent)
0118     : QObject(parent)
0119     , m_widget(widget)
0120     , m_timer(new QTimer(this))
0121 {
0122     m_timer->setSingleShot(true);
0123     connect(m_timer, &QTimer::timeout, this, &ScreenEdgeHelper::restore);
0124 }
0125 
0126 ScreenEdgeHelper::~ScreenEdgeHelper() = default;
0127 
0128 void ScreenEdgeHelper::moveToTop()
0129 {
0130     const QRect geo = QGuiApplication::primaryScreen()->geometry();
0131     m_widget->setGeometry(geo.x(), geo.y(), geo.width(), 100);
0132 }
0133 
0134 void ScreenEdgeHelper::moveToRight()
0135 {
0136     const QRect geo = QGuiApplication::primaryScreen()->geometry();
0137     m_widget->setGeometry(geo.x(), geo.y(), geo.width(), 100);
0138 }
0139 
0140 void ScreenEdgeHelper::moveToBottom()
0141 {
0142     const QRect geo = QGuiApplication::primaryScreen()->geometry();
0143     m_widget->setGeometry(geo.x(), geo.y() + geo.height() - 100, geo.width(), 100);
0144 }
0145 
0146 void ScreenEdgeHelper::moveToLeft()
0147 {
0148     const QRect geo = QGuiApplication::primaryScreen()->geometry();
0149     m_widget->setGeometry(geo.x(), geo.y(), 100, geo.height());
0150 }
0151 
0152 void ScreenEdgeHelper::moveToFloating()
0153 {
0154     const QRect geo = QGuiApplication::primaryScreen()->geometry();
0155     m_widget->setGeometry(QRect(geo.center(), QSize(100, 100)));
0156 }
0157 
0158 ScreenEdgeHelperX11::ScreenEdgeHelperX11(QWidget *widget, QObject *parent)
0159     : ScreenEdgeHelper(widget, parent)
0160     , m_atom(QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW"))
0161 {
0162 }
0163 
0164 void ScreenEdgeHelperX11::hide()
0165 {
0166     uint32_t value = m_locationValue | (m_actionValue << 8);
0167     xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, window()->winId(), m_atom, XCB_ATOM_CARDINAL, 32, 1, &value);
0168 }
0169 
0170 void ScreenEdgeHelperX11::restore()
0171 {
0172     xcb_delete_property(QX11Info::connection(), window()->winId(), m_atom);
0173 }
0174 
0175 void ScreenEdgeHelperX11::raiseOrShow(bool raise)
0176 {
0177     m_actionValue = raise ? 1 : 0;
0178 }
0179 
0180 void ScreenEdgeHelperX11::moveToBottom()
0181 {
0182     ScreenEdgeHelper::moveToBottom();
0183     m_locationValue = 2;
0184 }
0185 
0186 void ScreenEdgeHelperX11::moveToFloating()
0187 {
0188     ScreenEdgeHelper::moveToFloating();
0189     m_locationValue = 4;
0190 }
0191 
0192 void ScreenEdgeHelperX11::moveToLeft()
0193 {
0194     ScreenEdgeHelper::moveToLeft();
0195     m_locationValue = 3;
0196 }
0197 
0198 void ScreenEdgeHelperX11::moveToRight()
0199 {
0200     ScreenEdgeHelper::moveToRight();
0201     m_locationValue = 1;
0202 }
0203 
0204 void ScreenEdgeHelperX11::moveToTop()
0205 {
0206     ScreenEdgeHelper::moveToTop();
0207     m_locationValue = 0;
0208 }
0209 
0210 using namespace KWayland::Client;
0211 
0212 ScreenEdgeHelperWayland::ScreenEdgeHelperWayland(QWidget *widget, QObject *parent)
0213     : ScreenEdgeHelper(widget, parent)
0214 {
0215     ConnectionThread *connection = ConnectionThread::fromApplication(this);
0216     Registry *registry = new Registry(connection);
0217     registry->create(connection);
0218 
0219     connect(registry, &Registry::interfacesAnnounced, this,
0220             [registry, this] {
0221                 const auto interface = registry->interface(Registry::Interface::PlasmaShell);
0222                 if (interface.name == 0) {
0223                     return;
0224                 }
0225                 m_shell = registry->createPlasmaShell(interface.name, interface.version);
0226             });
0227 
0228     registry->setup();
0229     connection->roundtrip();
0230 }
0231 
0232 void ScreenEdgeHelperWayland::init()
0233 {
0234     window()->installEventFilter(this);
0235     setupSurface();
0236 }
0237 
0238 void ScreenEdgeHelperWayland::setupSurface()
0239 {
0240     if (!m_shell) {
0241         return;
0242     }
0243     if (auto s = Surface::fromWindow(window())) {
0244         m_shellSurface = m_shell->createSurface(s, window());
0245         m_shellSurface->setRole(PlasmaShellSurface::Role::Panel);
0246         m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AutoHide);
0247         m_shellSurface->setPosition(window()->position());
0248     }
0249 }
0250 
0251 void ScreenEdgeHelperWayland::hide()
0252 {
0253     if (m_shellSurface && m_autoHide) {
0254         m_shellSurface->requestHideAutoHidingPanel();
0255     }
0256 }
0257 
0258 void ScreenEdgeHelperWayland::restore()
0259 {
0260     if (m_shellSurface && m_autoHide) {
0261         m_shellSurface->requestShowAutoHidingPanel();
0262     }
0263 }
0264 
0265 void ScreenEdgeHelperWayland::raiseOrShow(bool raise)
0266 {
0267     m_autoHide = !raise;
0268     if (m_shellSurface) {
0269         if (raise) {
0270             m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::WindowsCanCover);
0271         } else {
0272             m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AutoHide);
0273         }
0274     }
0275 }
0276 
0277 bool ScreenEdgeHelperWayland::eventFilter(QObject *watched, QEvent *event)
0278 {
0279     if (watched != window() || !m_shell) {
0280         return false;
0281     }
0282     if (event->type() == QEvent::PlatformSurface) {
0283         QPlatformSurfaceEvent *pe = static_cast<QPlatformSurfaceEvent *>(event);
0284         if (pe->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) {
0285             setupSurface();
0286         } else {
0287             delete m_shellSurface;
0288             m_shellSurface = nullptr;
0289         }
0290     }
0291     if (event->type() == QEvent::Move) {
0292         if (m_shellSurface) {
0293             m_shellSurface->setPosition(window()->position());
0294         }
0295     }
0296     return false;
0297 }
0298 
0299 int main(int argc, char **argv)
0300 {
0301     QApplication app(argc, argv);
0302     QApplication::setApplicationDisplayName(QStringLiteral("Screen Edge Show Test App"));
0303     ScreenEdgeHelper *helper = nullptr;
0304     std::unique_ptr<QWidget> widget(new QWidget(nullptr, Qt::FramelessWindowHint));
0305     if (KWindowSystem::isPlatformX11()) {
0306         app.setProperty("x11Connection", QVariant::fromValue<void *>(QX11Info::connection()));
0307         helper = new ScreenEdgeHelperX11(widget.get(), &app);
0308     } else if (KWindowSystem::isPlatformWayland()) {
0309         helper = new ScreenEdgeHelperWayland(widget.get(), &app);
0310     }
0311 
0312     if (!helper) {
0313         return 2;
0314     }
0315 
0316     QPushButton *hideWindowButton = new QPushButton(QStringLiteral("Hide"), widget.get());
0317 
0318     QObject::connect(hideWindowButton, &QPushButton::clicked, helper, &ScreenEdgeHelper::hide);
0319 
0320     QPushButton *hideAndRestoreButton = new QPushButton(QStringLiteral("Hide and Restore after 10 sec"), widget.get());
0321     QObject::connect(hideAndRestoreButton, &QPushButton::clicked, helper, &ScreenEdgeHelper::hideAndRestore);
0322 
0323     QToolButton *edgeButton = new QToolButton(widget.get());
0324 
0325     QCheckBox *raiseCheckBox = new QCheckBox("Raise:", widget.get());
0326     QObject::connect(raiseCheckBox, &QCheckBox::toggled, helper, &ScreenEdgeHelper::raiseOrShow);
0327 
0328     edgeButton->setText(QStringLiteral("Edge"));
0329     edgeButton->setPopupMode(QToolButton::MenuButtonPopup);
0330     QMenu *edgeButtonMenu = new QMenu(edgeButton);
0331     QObject::connect(edgeButtonMenu->addAction("Top"), &QAction::triggered, helper, &ScreenEdgeHelper::moveToTop);
0332     QObject::connect(edgeButtonMenu->addAction("Right"), &QAction::triggered, helper, &ScreenEdgeHelper::moveToRight);
0333     QObject::connect(edgeButtonMenu->addAction("Bottom"), &QAction::triggered, helper, &ScreenEdgeHelper::moveToBottom);
0334     QObject::connect(edgeButtonMenu->addAction("Left"), &QAction::triggered, helper, &ScreenEdgeHelper::moveToLeft);
0335     edgeButtonMenu->addSeparator();
0336     QObject::connect(edgeButtonMenu->addAction("Floating"), &QAction::triggered, helper, &ScreenEdgeHelper::moveToFloating);
0337     edgeButton->setMenu(edgeButtonMenu);
0338 
0339     QHBoxLayout *layout = new QHBoxLayout(widget.get());
0340     layout->addWidget(hideWindowButton);
0341     layout->addWidget(hideAndRestoreButton);
0342     layout->addWidget(edgeButton);
0343     widget->setLayout(layout);
0344 
0345     const QRect geo = QGuiApplication::primaryScreen()->geometry();
0346     widget->setGeometry(geo.x(), geo.y() + geo.height() - 100, geo.width(), 100);
0347     widget->show();
0348     helper->init();
0349 
0350     return app.exec();
0351 }
0352 
0353 #include "screenedgeshowtest.moc"