From ed4a0d031997ea6ca438741102a89885c25a7860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Thu, 20 Feb 2014 12:39:23 +0100 Subject: [PATCH] Screenedge show support for Clients This provides a new protocol intended to be used by auto-hiding panels to make use of the centralized screen edges. To use it a Client can set an X11 property of type _KDE_NET_WM_SCREEN_EDGE_SHOW to KWin. As value it takes: * 0: top edge * 1: right edge * 2: bottom edge * 3: left edge KWin will hide the Client (hide because unmap or minimize would break it) and create an Edge. If that Edge gets triggered the Client is shown again and the property gets deleted. If the Client doesn't border the specified screen edge the Client gets shown immediately so that we never end in a situation that we cannot unhide the auto-hidden panel again. The exact process is described in the documentation of ScreenEdges. The Client can request to be shown again by deleting the property. If KWin gets restarted the state is read from the property and it is tried to create the edge as described. As this is a KWin specific extension we need to discuss what it means for Clients using this feature with other WMs: it does nothing. As the Client gets hidden by KWin and not by the Client, it just doesn't get hidden if the WM doesn't provide the feature. In case of an auto-hiding panel this seems like a good solution given that we don't want to hide it if we cannot unhide it. Of course there's the option for the Client to provide that feature itself and if that's wanted we would need to announce the feature in the _NET_SUPPORTED atom. At the moment that doesn't sound like being needed as Plasma doesn't want to provide an own implementation. The implementation comes with a small test application showing how the feature is intended to be used. REVIEW: 115910 --- atoms.cpp | 1 + atoms.h | 1 + client.cpp | 54 ++++++++++ client.h | 12 +++ events.cpp | 2 + manage.cpp | 1 + screenedge.cpp | 196 ++++++++++++++++++++++++++++++++--- screenedge.h | 44 +++++++- tests/CMakeLists.txt | 5 + tests/screenedgeshowtest.cpp | 98 ++++++++++++++++++ 10 files changed, 399 insertions(+), 15 deletions(-) create mode 100644 tests/screenedgeshowtest.cpp diff --git a/atoms.cpp b/atoms.cpp index 904f5efe4..ec2c60fcb 100644 --- a/atoms.cpp +++ b/atoms.cpp @@ -58,6 +58,7 @@ Atoms::Atoms() , kde_first_in_window_list(QByteArrayLiteral("_KDE_FIRST_IN_WINDOWLIST")) , kde_color_sheme(QByteArrayLiteral("_KDE_NET_WM_COLOR_SCHEME")) , kde_skip_close_animation(QByteArrayLiteral("_KDE_NET_WM_SKIP_CLOSE_ANIMATION")) + , kde_screen_edge_show(QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW")) , m_dtSmWindowInfo(QByteArrayLiteral("_DT_SM_WINDOW_INFO")) , m_motifSupport(QByteArrayLiteral("_MOTIF_WM_INFO")) , m_helpersRetrieved(false) diff --git a/atoms.h b/atoms.h index 1690067c5..3566d0a38 100644 --- a/atoms.h +++ b/atoms.h @@ -67,6 +67,7 @@ public: Xcb::Atom kde_first_in_window_list; Xcb::Atom kde_color_sheme; Xcb::Atom kde_skip_close_animation; + Xcb::Atom kde_screen_edge_show; /** * @internal diff --git a/client.cpp b/client.cpp index 36431bfc3..023419254 100644 --- a/client.cpp +++ b/client.cpp @@ -42,6 +42,7 @@ along with this program. If not, see . #include "tabbox.h" #endif #include "workspace.h" +#include "screenedge.h" // KDE #include #include @@ -2503,6 +2504,59 @@ xcb_window_t Client::frameId() const return m_frame; } +void Client::updateShowOnScreenEdge() +{ + auto cookie = xcb_get_property_unchecked(connection(), false, window(), atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 0, 1); + ScopedCPointer reply(xcb_get_property_reply(connection(), cookie, nullptr)); + + auto restore = [this]() { + // TODO: add proper unreserve + ScreenEdges::self()->reserve(this, ElectricNone); + hideClient(false); + }; + + if (!reply.isNull()) { + if (reply->format == 32 && reply->type == XCB_ATOM_CARDINAL && reply->value_len == 1) { + const uint32_t value = *reinterpret_cast(xcb_get_property_value(reply.data())); + ElectricBorder border = ElectricNone; + switch (value) { + case 0: + border = ElectricTop; + break; + case 1: + border = ElectricRight; + break; + case 2: + border = ElectricBottom; + break; + case 3: + border = ElectricLeft; + break; + } + if (border != ElectricNone) { + hideClient(true); + ScreenEdges::self()->reserve(this, border); + } else { + // property value is incorrect, delete the property + // so that the client knows that it is not hidden + xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show); + } + + } else if (reply->type == XCB_ATOM_NONE) { + // the property got deleted, show the client again + restore(); + } + } else { + restore(); + } +} + +void Client::showOnScreenEdge() +{ + hideClient(false); + xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show); +} + } // namespace #include "client.moc" diff --git a/client.h b/client.h index 6a0dbe4f4..a8ab0027b 100644 --- a/client.h +++ b/client.h @@ -648,6 +648,12 @@ public: QPalette palette() const; + /** + * Restores the Client after it had been hidden due to show on screen edge functionality. + * In addition the property gets deleted so that the Client knows that it is visible again. + **/ + void showOnScreenEdge(); + public Q_SLOTS: void closeWindow(); void updateCaption(); @@ -834,6 +840,12 @@ private: bool tabTo(Client *other, bool behind, bool activate); + /** + * Reads the property and creates/destroys the screen edge if required + * and shows/hides the client. + **/ + void updateShowOnScreenEdge(); + Xcb::Window m_client; Xcb::Window m_wrapper; Xcb::Window m_frame; diff --git a/events.cpp b/events.cpp index 1fa6e425d..a2ba41f24 100644 --- a/events.cpp +++ b/events.cpp @@ -803,6 +803,8 @@ void Client::propertyNotifyEvent(xcb_property_notify_event_t *e) updateFirstInTabBox(); else if (e->atom == atoms->kde_color_sheme) updateColorScheme(); + else if (e->atom == atoms->kde_screen_edge_show) + updateShowOnScreenEdge(); break; } } diff --git a/manage.cpp b/manage.cpp index 3e385cd6a..563cedb34 100644 --- a/manage.cpp +++ b/manage.cpp @@ -619,6 +619,7 @@ bool Client::manage(xcb_window_t w, bool isMapped) updateCompositeBlocking(true); updateColorScheme(); + updateShowOnScreenEdge(); // TODO: there's a small problem here - isManaged() depends on the mapping state, // but this client is not yet in Workspace's client list at this point, will diff --git a/screenedge.cpp b/screenedge.cpp index 04cf0d6d5..3c3d1d3ee 100644 --- a/screenedge.cpp +++ b/screenedge.cpp @@ -59,6 +59,7 @@ Edge::Edge(ScreenEdges *parent) , m_approaching(false) , m_lastApproachingFactor(0) , m_blocked(false) + , m_client(nullptr) { } @@ -87,6 +88,7 @@ void Edge::unreserve() m_reserved--; if (m_reserved == 0) { // got deactivated + stopApproaching(); deactivate(); } } @@ -122,20 +124,28 @@ bool Edge::triggersFor(const QPoint &cursorPos) const return true; } -void Edge::check(const QPoint &cursorPos, const QDateTime &triggerTime, bool forceNoPushBack) +bool Edge::check(const QPoint &cursorPos, const QDateTime &triggerTime, bool forceNoPushBack) { if (!triggersFor(cursorPos)) { - return; + return false; } // no pushback so we have to activate at once bool directActivate = forceNoPushBack || edges()->cursorPushBackDistance().isNull(); if (directActivate || canActivate(cursorPos, triggerTime)) { - m_lastTrigger = triggerTime; - m_lastReset = QDateTime(); // invalidate + markAsTriggered(cursorPos, triggerTime); handle(cursorPos); + return true; } else { pushCursorBack(cursorPos); + m_triggeredPoint = cursorPos; } + return false; +} + +void Edge::markAsTriggered(const QPoint &cursorPos, const QDateTime &triggerTime) +{ + m_lastTrigger = triggerTime; + m_lastReset = QDateTime(); // invalidate m_triggeredPoint = cursorPos; } @@ -164,6 +174,12 @@ bool Edge::canActivate(const QPoint &cursorPos, const QDateTime &triggerTime) void Edge::handle(const QPoint &cursorPos) { + if (m_client) { + pushCursorBack(cursorPos); + m_client->showOnScreenEdge(); + unreserve(); + return; + } if ((edges()->isDesktopSwitchingMovingClients() && Workspace::self()->getMovingClient()) || (edges()->isDesktopSwitching() && isScreenEdge())) { // always switch desktops in case: @@ -555,6 +571,12 @@ ScreenEdges::ScreenEdges(QObject *parent) { QWidget w; m_cornerOffset = (w.physicalDpiX() + w.physicalDpiY() + 5) / 6; + + connect(workspace(), &Workspace::clientRemoved, [this](KWin::Client *client) { + deleteEdgeForClient(client); + QObject::disconnect(client, &Client::geometryChanged, + ScreenEdges::self(), &ScreenEdges::handleClientGeometryChanged); + }); } ScreenEdges::~ScreenEdges() @@ -808,6 +830,11 @@ void ScreenEdges::recreateEdges() oldIt != oldEdges.constEnd(); ++oldIt) { WindowBasedEdge *oldEdge = *oldIt; + if (oldEdge->client()) { + // show the client again and don't recreate the edge + oldEdge->client()->showOnScreenEdge(); + continue; + } if (oldEdge->border() != edge->border()) { continue; } @@ -869,15 +896,17 @@ void ScreenEdges::createHorizontalEdge(ElectricBorder border, const QRect &scree m_edges << createEdge(border, x, y, width, 1); } -WindowBasedEdge *ScreenEdges::createEdge(ElectricBorder border, int x, int y, int width, int height) +WindowBasedEdge *ScreenEdges::createEdge(ElectricBorder border, int x, int y, int width, int height, bool createAction) { WindowBasedEdge *edge = new WindowBasedEdge(this); edge->setBorder(border); edge->setGeometry(QRect(x, y, width, height)); - const ElectricBorderAction action = actionForEdge(edge); - if (action != KWin::ElectricActionNone) { - edge->reserve(); - edge->setAction(action); + if (createAction) { + const ElectricBorderAction action = actionForEdge(edge); + if (action != KWin::ElectricActionNone) { + edge->reserve(); + edge->setAction(action); + } } if (isDesktopSwitching()) { if (edge->isCorner()) { @@ -961,8 +990,127 @@ void ScreenEdges::unreserve(ElectricBorder border, QObject *object) } } +void ScreenEdges::reserve(Client *client, ElectricBorder border) +{ + auto it = m_edges.begin(); + while (it != m_edges.end()) { + if ((*it)->client() == client) { + if ((*it)->border() == border) { + if (client->isHiddenInternal() && !(*it)->isReserved()) { + (*it)->reserve(); + } + return; + } else { + delete *it; + it = m_edges.erase(it); + } + } else { + it++; + } + } + createEdgeForClient(client, border); + + connect(client, &Client::geometryChanged, this, &ScreenEdges::handleClientGeometryChanged); +} + +void ScreenEdges::createEdgeForClient(Client *client, ElectricBorder border) +{ + int y = 0; + int x = 0; + int width = 0; + int height = 0; + const QRect geo = client->geometry(); + const QRect fullArea = workspace()->clientArea(FullArea, 0, 1); + for (int i = 0; i < screens()->count(); ++i) { + const QRect screen = screens()->geometry(i); + if (!screen.contains(geo)) { + // ignoring Clients having a geometry overlapping with multiple screens + // this would make the code more complex. If it's needed in future it can be added + continue; + } + const bool bordersTop = (screen.y() == geo.y()); + const bool bordersLeft = (screen.x() == geo.x()); + const bool bordersBottom = (screen.y() + screen.height() == geo.y() + geo.height()); + const bool bordersRight = (screen.x() + screen.width() == geo.x() + geo.width()); + + if (bordersTop && border == ElectricTop) { + if (!isTopScreen(screen, fullArea)) { + continue; + } + y = geo.y(); + x = geo.x(); + height = 1; + width = geo.width(); + break; + } + if (bordersBottom && border == ElectricBottom) { + if (!isBottomScreen(screen, fullArea)) { + continue; + } + y = geo.y() + geo.height() - 1; + x = geo.x(); + height = 1; + width = geo.width(); + break; + } + if (bordersLeft && border == ElectricLeft) { + if (!isLeftScreen(screen, fullArea)) { + continue; + } + x = geo.x(); + y = geo.y(); + width = 1; + height = geo.height(); + break; + } + if (bordersRight && border == ElectricRight) { + if (!isRightScreen(screen, fullArea)) { + continue; + } + x = geo.x() + geo.width() - 1; + y = geo.y(); + width = 1; + height = geo.height(); + break; + } + } + + if (width > 0 && height > 0) { + WindowBasedEdge *edge = createEdge(border, x, y, width, height, false); + edge->setClient(client); + m_edges.append(edge); + if (client->isHiddenInternal()) { + edge->reserve(); + } + } else { + // we could not create an edge window, so don't allow the window to hide + client->showOnScreenEdge(); + } +} + +void ScreenEdges::handleClientGeometryChanged() +{ + Client *c = static_cast(sender()); + deleteEdgeForClient(c); + c->showOnScreenEdge(); +} + +void ScreenEdges::deleteEdgeForClient(Client* c) +{ + auto it = m_edges.begin(); + while (it != m_edges.end()) { + if ((*it)->client() == c) { + delete *it; + it = m_edges.erase(it); + } else { + it++; + } + } +} + void ScreenEdges::check(const QPoint &pos, const QDateTime &now, bool forceNoPushBack) { + bool activatedForClient = false; for (QList::iterator it = m_edges.begin(); it != m_edges.end(); ++it) { if (!(*it)->isReserved()) { continue; @@ -970,7 +1118,15 @@ void ScreenEdges::check(const QPoint &pos, const QDateTime &now, bool forceNoPus if ((*it)->approachGeometry().contains(pos)) { (*it)->startApproaching(); } - (*it)->check(pos, now, forceNoPushBack); + if ((*it)->client() != nullptr && activatedForClient) { + (*it)->markAsTriggered(pos, now); + continue; + } + if ((*it)->check(pos, now, forceNoPushBack)) { + if ((*it)->client()) { + activatedForClient = true; + } + } } } @@ -992,14 +1148,21 @@ bool ScreenEdges::isEntered(xcb_client_message_event_t *event) bool ScreenEdges::handleEnterNotifiy(xcb_window_t window, const QPoint &point, const QDateTime ×tamp) { + bool activated = false; + bool activatedForClient = false; for (QList::iterator it = m_edges.begin(); it != m_edges.end(); ++it) { WindowBasedEdge *edge = *it; if (!edge->isReserved()) { continue; } if (edge->window() == window) { - edge->check(point, timestamp); - return true; + if (edge->check(point, timestamp)) { + if ((*it)->client()) { + activatedForClient = true; + } + } + activated = true; + break; } if (edge->approachWindow() == window) { edge->startApproaching(); @@ -1007,7 +1170,14 @@ bool ScreenEdges::handleEnterNotifiy(xcb_window_t window, const QPoint &point, c return true; } } - return false; + if (activatedForClient) { + for (auto it = m_edges.constBegin(); it != m_edges.constEnd(); ++it) { + if ((*it)->client()) { + (*it)->markAsTriggered(point, timestamp); + } + } + } + return activated; } bool ScreenEdges::handleDndNotify(xcb_window_t window, const QPoint &point) diff --git a/screenedge.h b/screenedge.h index 60f5fd669..f1c579cf1 100644 --- a/screenedge.h +++ b/screenedge.h @@ -56,7 +56,8 @@ public: bool isCorner() const; bool isScreenEdge() const; bool triggersFor(const QPoint &cursorPos) const; - void check(const QPoint &cursorPos, const QDateTime &triggerTime, bool forceNoPushBack = false); + bool check(const QPoint &cursorPos, const QDateTime &triggerTime, bool forceNoPushBack = false); + void markAsTriggered(const QPoint &cursorPos, const QDateTime &triggerTime); bool isReserved() const; const QRect &approachGeometry() const; @@ -65,6 +66,8 @@ public: const QHash &callBacks() const; void startApproaching(); void stopApproaching(); + void setClient(Client *client); + Client *client() const; public Q_SLOTS: void reserve(); @@ -108,6 +111,7 @@ private: bool m_approaching; int m_lastApproachingFactor; bool m_blocked; + Client *m_client; }; class WindowBasedEdge : public Edge @@ -242,6 +246,29 @@ public: * @todo: add pointer to script/effect */ void unreserve(ElectricBorder border, QObject *object); + /** + * Reserves an edge for the @p client. The idea behind this is to show the @p client if the + * screen edge which the @p client borders gets triggered. + * + * When first called it is tried to create an Edge for the client. This is only done if the + * client borders with a screen edge specified by @p border. If the client doesn't border the + * screen edge, no Edge gets created and the client is shown again. Otherwise there would not + * be a possibility to show the client again. + * + * On subsequent calls for the client no new Edge is created, but the existing one gets reused + * and if the client is already hidden, the Edge gets reserved. + * + * Once the Edge for the client triggers, the client gets shown again and the Edge unreserved. + * The idea is that the Edge can only get activated if the client is currently hidden. + * + * To make sure that the client can always be shown again the implementation also starts to + * track geometry changes and shows the Client again. The same for screen geometry changes. + * + * The Edge gets automatically destroyed if the client gets released. + * @param client The Client for which an Edge should be reserved + * @param border The border which the client wants to use, only proper borders are supported (no corners) + **/ + void reserve(KWin::Client *client, ElectricBorder border); /** * Reserve desktop switching for screen edges, if @p isToReserve is @c true. Unreserve otherwise. * @param reserve indicated weather desktop switching should be reserved or unreseved @@ -317,11 +344,14 @@ private: void setReActivationThreshold(int threshold); void createHorizontalEdge(ElectricBorder border, const QRect &screen, const QRect &fullArea); void createVerticalEdge(ElectricBorder border, const QRect &screen, const QRect &fullArea); - WindowBasedEdge *createEdge(ElectricBorder border, int x, int y, int width, int height); + WindowBasedEdge *createEdge(ElectricBorder border, int x, int y, int width, int height, bool createAction = true); void setActionForBorder(ElectricBorder border, ElectricBorderAction *oldValue, ElectricBorderAction newValue); ElectricBorderAction actionForEdge(Edge *edge) const; bool handleEnterNotifiy(xcb_window_t window, const QPoint &point, const QDateTime ×tamp); bool handleDndNotify(xcb_window_t window, const QPoint &point); + void createEdgeForClient(Client *client, ElectricBorder border); + void handleClientGeometryChanged(); + void deleteEdgeForClient(Client *client); bool m_desktopSwitching; bool m_desktopSwitchingMovingClients; QSize m_cursorPushBackDistance; @@ -433,6 +463,16 @@ inline bool Edge::isBlocked() const return m_blocked; } +inline void Edge::setClient(Client *client) +{ + m_client = client; +} + +inline Client *Edge::client() const +{ + return m_client; +} + /********************************************************** * Inlines WindowBasedEdge *********************************************************/ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5d8cb132a..26524ffa0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,3 +3,8 @@ if (XCB_ICCCM_FOUND) add_executable(normalhintsbasesizetest ${normalhintsbasesizetest_SRCS}) target_link_libraries(normalhintsbasesizetest XCB::XCB XCB::ICCCM) endif() + +# next target +set(screenedgeshowtest_SRCS screenedgeshowtest.cpp) +add_executable(screenedgeshowtest ${screenedgeshowtest_SRCS}) +target_link_libraries(screenedgeshowtest Qt5::Widgets Qt5::X11Extras ${XCB_XCB_LIBRARY}) diff --git a/tests/screenedgeshowtest.cpp b/tests/screenedgeshowtest.cpp new file mode 100644 index 000000000..e8bb1ceb7 --- /dev/null +++ b/tests/screenedgeshowtest.cpp @@ -0,0 +1,98 @@ +/* + * Copyright 2014 Martin Gräßlin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include "../xcbutils.h" + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + QApplication::setApplicationDisplayName(QStringLiteral("Screen Edge Show Test App")); + + QScopedPointer widget(new QWidget(nullptr, Qt::FramelessWindowHint)); + + KWin::Xcb::Atom atom(QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW")); + + uint32_t value = 2; + QPushButton *hideWindowButton = new QPushButton(QStringLiteral("Hide"), widget.data()); + QObject::connect(hideWindowButton, &QPushButton::clicked, [&widget, &atom, &value]() { + xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, widget->winId(), atom, XCB_ATOM_CARDINAL, 32, 1, &value); + }); + QPushButton *hideAndRestoreButton = new QPushButton(QStringLiteral("Hide and Restore after 10 sec"), widget.data()); + QTimer *restoreTimer = new QTimer(hideAndRestoreButton); + restoreTimer->setSingleShot(true); + QObject::connect(hideAndRestoreButton, &QPushButton::clicked, [&widget, &atom, &value, restoreTimer]() { + xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, widget->winId(), atom, XCB_ATOM_CARDINAL, 32, 1, &value); + restoreTimer->start(10000); + }); + QObject::connect(restoreTimer, &QTimer::timeout, [&widget, &atom]() { + xcb_delete_property(QX11Info::connection(), widget->winId(), atom); + }); + + QToolButton *edgeButton = new QToolButton(widget.data()); + edgeButton->setText(QStringLiteral("Edge")); + edgeButton->setPopupMode(QToolButton::MenuButtonPopup); + QMenu *edgeButtonMenu = new QMenu(edgeButton); + QObject::connect(edgeButtonMenu->addAction("Top"), &QAction::triggered, [&widget, &value]() { + const QRect geo = QGuiApplication::primaryScreen()->geometry(); + widget->setGeometry(geo.x(), geo.y(), geo.width(), 100); + value = 0; + }); + QObject::connect(edgeButtonMenu->addAction("Right"), &QAction::triggered, [&widget, &value]() { + const QRect geo = QGuiApplication::primaryScreen()->geometry(); + widget->setGeometry(geo.x() + geo.width() - 100, geo.y(), 100, geo.height()); + value = 1; + }); + QObject::connect(edgeButtonMenu->addAction("Bottom"), &QAction::triggered, [&widget, &value]() { + const QRect geo = QGuiApplication::primaryScreen()->geometry(); + widget->setGeometry(geo.x(), geo.y() + geo.height() - 100, geo.width(), 100); + value = 2; + }); + QObject::connect(edgeButtonMenu->addAction("Left"), &QAction::triggered, [&widget, &value]() { + const QRect geo = QGuiApplication::primaryScreen()->geometry(); + widget->setGeometry(geo.x(), geo.y(), 100, geo.height()); + value = 3; + }); + edgeButtonMenu->addSeparator(); + QObject::connect(edgeButtonMenu->addAction("Floating"), &QAction::triggered, [&widget, &value]() { + const QRect geo = QGuiApplication::primaryScreen()->geometry(); + widget->setGeometry(QRect(geo.center(), QSize(100, 100))); + value = 4; + }); + edgeButton->setMenu(edgeButtonMenu); + + QHBoxLayout *layout = new QHBoxLayout(widget.data()); + layout->addWidget(hideWindowButton); + layout->addWidget(hideAndRestoreButton); + layout->addWidget(edgeButton); + widget->setLayout(layout); + + const QRect geo = QGuiApplication::primaryScreen()->geometry(); + widget->setGeometry(geo.x(), geo.y() + geo.height() - 100, geo.width(), 100); + widget->show(); + + return app.exec(); +}