forked from quickshell/quickshell
core/popupanchor: rework popup anchoring and add PopupAnchor
This commit is contained in:
parent
14910b1b60
commit
ebfa8ec448
14 changed files with 770 additions and 108 deletions
|
@ -33,6 +33,8 @@ qt_add_library(quickshell-core STATIC
|
|||
platformmenu.cpp
|
||||
qsmenu.cpp
|
||||
retainable.cpp
|
||||
popupanchor.cpp
|
||||
types.cpp
|
||||
)
|
||||
|
||||
set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}")
|
||||
|
|
|
@ -24,5 +24,7 @@ headers = [
|
|||
"objectrepeater.hpp",
|
||||
"qsmenu.hpp",
|
||||
"retainable.hpp",
|
||||
"popupanchor.hpp",
|
||||
"types.hpp",
|
||||
]
|
||||
-----
|
||||
|
|
275
src/core/popupanchor.cpp
Normal file
275
src/core/popupanchor.cpp
Normal file
|
@ -0,0 +1,275 @@
|
|||
#include "popupanchor.hpp"
|
||||
|
||||
#include <qlogging.h>
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qwindow.h>
|
||||
|
||||
#include "proxywindow.hpp"
|
||||
#include "types.hpp"
|
||||
#include "windowinterface.hpp"
|
||||
|
||||
bool PopupAnchorState::operator==(const PopupAnchorState& other) const {
|
||||
return this->rect == other.rect && this->edges == other.edges && this->gravity == other.gravity
|
||||
&& this->adjustment == other.adjustment && this->anchorpoint == other.anchorpoint;
|
||||
}
|
||||
|
||||
bool PopupAnchor::isDirty() const {
|
||||
return !this->lastState.has_value() || this->state != this->lastState.value();
|
||||
}
|
||||
|
||||
void PopupAnchor::markClean() { this->lastState = this->state; }
|
||||
void PopupAnchor::markDirty() { this->lastState.reset(); }
|
||||
|
||||
QObject* PopupAnchor::window() const { return this->mWindow; }
|
||||
ProxyWindowBase* PopupAnchor::proxyWindow() const { return this->mProxyWindow; }
|
||||
|
||||
QWindow* PopupAnchor::backingWindow() const {
|
||||
return this->mProxyWindow ? this->mProxyWindow->backingWindow() : nullptr;
|
||||
}
|
||||
|
||||
void PopupAnchor::setWindow(QObject* window) {
|
||||
if (window == this->mWindow) return;
|
||||
|
||||
if (this->mWindow) {
|
||||
QObject::disconnect(this->mWindow, nullptr, this, nullptr);
|
||||
QObject::disconnect(this->mProxyWindow, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
if (window) {
|
||||
if (auto* proxy = qobject_cast<ProxyWindowBase*>(window)) {
|
||||
this->mProxyWindow = proxy;
|
||||
} else if (auto* interface = qobject_cast<WindowInterface*>(window)) {
|
||||
this->mProxyWindow = interface->proxyWindow();
|
||||
} else {
|
||||
qWarning() << "Tried to set popup anchor window to" << window
|
||||
<< "which is not a quickshell window.";
|
||||
goto setnull;
|
||||
}
|
||||
|
||||
this->mWindow = window;
|
||||
|
||||
QObject::connect(this->mWindow, &QObject::destroyed, this, &PopupAnchor::onWindowDestroyed);
|
||||
|
||||
QObject::connect(
|
||||
this->mProxyWindow,
|
||||
&ProxyWindowBase::backerVisibilityChanged,
|
||||
this,
|
||||
&PopupAnchor::backingWindowVisibilityChanged
|
||||
);
|
||||
|
||||
emit this->windowChanged();
|
||||
emit this->backingWindowVisibilityChanged();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setnull:
|
||||
if (this->mWindow) {
|
||||
this->mWindow = nullptr;
|
||||
this->mProxyWindow = nullptr;
|
||||
|
||||
emit this->windowChanged();
|
||||
emit this->backingWindowVisibilityChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void PopupAnchor::onWindowDestroyed() {
|
||||
this->mWindow = nullptr;
|
||||
this->mProxyWindow = nullptr;
|
||||
emit this->windowChanged();
|
||||
emit this->backingWindowVisibilityChanged();
|
||||
}
|
||||
|
||||
Box PopupAnchor::rect() const { return this->state.rect; }
|
||||
|
||||
void PopupAnchor::setRect(Box rect) {
|
||||
if (rect == this->state.rect) return;
|
||||
if (rect.w <= 0) rect.w = 1;
|
||||
if (rect.h <= 0) rect.h = 1;
|
||||
|
||||
this->state.rect = rect;
|
||||
emit this->rectChanged();
|
||||
}
|
||||
|
||||
Edges::Flags PopupAnchor::edges() const { return this->state.edges; }
|
||||
|
||||
void PopupAnchor::setEdges(Edges::Flags edges) {
|
||||
if (edges == this->state.edges) return;
|
||||
|
||||
if (Edges::isOpposing(edges)) {
|
||||
qWarning() << "Cannot set opposing edges for anchor edges. Tried to set" << edges;
|
||||
return;
|
||||
}
|
||||
|
||||
this->state.edges = edges;
|
||||
emit this->edgesChanged();
|
||||
}
|
||||
|
||||
Edges::Flags PopupAnchor::gravity() const { return this->state.gravity; }
|
||||
|
||||
void PopupAnchor::setGravity(Edges::Flags gravity) {
|
||||
if (gravity == this->state.gravity) return;
|
||||
|
||||
if (Edges::isOpposing(gravity)) {
|
||||
qWarning() << "Cannot set opposing edges for anchor gravity. Tried to set" << gravity;
|
||||
return;
|
||||
}
|
||||
|
||||
this->state.gravity = gravity;
|
||||
emit this->gravityChanged();
|
||||
}
|
||||
|
||||
PopupAdjustment::Flags PopupAnchor::adjustment() const { return this->state.adjustment; }
|
||||
|
||||
void PopupAnchor::setAdjustment(PopupAdjustment::Flags adjustment) {
|
||||
if (adjustment == this->state.adjustment) return;
|
||||
this->state.adjustment = adjustment;
|
||||
emit this->adjustmentChanged();
|
||||
}
|
||||
|
||||
void PopupAnchor::updateAnchorpoint(const QPoint& anchorpoint) {
|
||||
this->state.anchorpoint = anchorpoint;
|
||||
}
|
||||
|
||||
static PopupPositioner* POSITIONER = nullptr; // NOLINT
|
||||
|
||||
void PopupPositioner::reposition(PopupAnchor* anchor, QWindow* window, bool onlyIfDirty) {
|
||||
auto* parentWindow = window->transientParent();
|
||||
if (!parentWindow) {
|
||||
qFatal() << "Cannot reposition popup that does not have a transient parent.";
|
||||
}
|
||||
|
||||
auto adjustment = anchor->adjustment();
|
||||
auto screenGeometry = parentWindow->screen()->geometry();
|
||||
auto parentGeometry = parentWindow->geometry();
|
||||
auto windowGeometry = window->geometry();
|
||||
auto anchorRectGeometry = anchor->rect().qrect().translated(parentGeometry.topLeft());
|
||||
|
||||
auto anchorEdges = anchor->edges();
|
||||
auto anchorGravity = anchor->gravity();
|
||||
|
||||
auto width = windowGeometry.width();
|
||||
auto height = windowGeometry.height();
|
||||
|
||||
auto anchorX = anchorEdges.testFlag(Edges::Left) ? anchorRectGeometry.left()
|
||||
: anchorEdges.testFlag(Edges::Right) ? anchorRectGeometry.right()
|
||||
: anchorRectGeometry.center().x();
|
||||
|
||||
auto anchorY = anchorEdges.testFlag(Edges::Top) ? anchorRectGeometry.top()
|
||||
: anchorEdges.testFlag(Edges::Bottom) ? anchorRectGeometry.bottom()
|
||||
: anchorRectGeometry.center().y();
|
||||
|
||||
anchor->updateAnchorpoint({anchorX, anchorY});
|
||||
if (onlyIfDirty && !anchor->isDirty()) return;
|
||||
anchor->markClean();
|
||||
|
||||
auto calcEffectiveX = [&]() {
|
||||
return anchorGravity.testFlag(Edges::Left) ? anchorX - windowGeometry.width() + 1
|
||||
: anchorGravity.testFlag(Edges::Right) ? anchorX
|
||||
: anchorX - windowGeometry.width() / 2;
|
||||
};
|
||||
|
||||
auto calcEffectiveY = [&]() {
|
||||
return anchorGravity.testFlag(Edges::Top) ? anchorY - windowGeometry.height() + 1
|
||||
: anchorGravity.testFlag(Edges::Bottom) ? anchorY
|
||||
: anchorY - windowGeometry.height() / 2;
|
||||
};
|
||||
|
||||
auto effectiveX = calcEffectiveX();
|
||||
auto effectiveY = calcEffectiveY();
|
||||
|
||||
if (adjustment.testFlag(PopupAdjustment::FlipX)) {
|
||||
if (anchorGravity.testFlag(Edges::Left)) {
|
||||
if (effectiveX < screenGeometry.left()) {
|
||||
anchorGravity = anchorGravity ^ Edges::Left | Edges::Right;
|
||||
anchorX = anchorRectGeometry.right();
|
||||
effectiveX = calcEffectiveX();
|
||||
}
|
||||
} else if (anchorGravity.testFlag(Edges::Right)) {
|
||||
if (effectiveX + windowGeometry.width() > screenGeometry.right()) {
|
||||
anchorGravity = anchorGravity ^ Edges::Right | Edges::Left;
|
||||
anchorX = anchorRectGeometry.left();
|
||||
effectiveX = calcEffectiveX();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (adjustment.testFlag(PopupAdjustment::FlipY)) {
|
||||
if (anchorGravity.testFlag(Edges::Top)) {
|
||||
if (effectiveY < screenGeometry.top()) {
|
||||
anchorGravity = anchorGravity ^ Edges::Top | Edges::Bottom;
|
||||
effectiveY = calcEffectiveY();
|
||||
}
|
||||
} else if (anchorGravity.testFlag(Edges::Bottom)) {
|
||||
if (effectiveY + windowGeometry.height() > screenGeometry.bottom()) {
|
||||
anchorGravity = anchorGravity ^ Edges::Bottom | Edges::Top;
|
||||
effectiveY = calcEffectiveY();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Slide order is important for the case where the window is too large to fit on screen.
|
||||
if (adjustment.testFlag(PopupAdjustment::SlideX)) {
|
||||
if (effectiveX + windowGeometry.width() > screenGeometry.right()) {
|
||||
effectiveX = screenGeometry.right() - windowGeometry.width() + 1;
|
||||
}
|
||||
|
||||
if (effectiveX < screenGeometry.left()) {
|
||||
effectiveX = screenGeometry.left();
|
||||
}
|
||||
}
|
||||
|
||||
if (adjustment.testFlag(PopupAdjustment::SlideY)) {
|
||||
if (effectiveY + windowGeometry.height() > screenGeometry.bottom()) {
|
||||
effectiveY = screenGeometry.bottom() - windowGeometry.height() + 1;
|
||||
}
|
||||
|
||||
if (effectiveY < screenGeometry.top()) {
|
||||
effectiveY = screenGeometry.top();
|
||||
}
|
||||
}
|
||||
|
||||
if (adjustment.testFlag(PopupAdjustment::ResizeX)) {
|
||||
if (effectiveX < screenGeometry.left()) {
|
||||
auto diff = screenGeometry.left() - effectiveX;
|
||||
effectiveX = screenGeometry.left();
|
||||
width -= diff;
|
||||
}
|
||||
|
||||
auto effectiveX2 = effectiveX + windowGeometry.width();
|
||||
if (effectiveX2 > screenGeometry.right()) {
|
||||
width -= effectiveX2 - screenGeometry.right() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (adjustment.testFlag(PopupAdjustment::ResizeY)) {
|
||||
if (effectiveY < screenGeometry.top()) {
|
||||
auto diff = screenGeometry.top() - effectiveY;
|
||||
effectiveY = screenGeometry.top();
|
||||
height -= diff;
|
||||
}
|
||||
|
||||
auto effectiveY2 = effectiveY + windowGeometry.height();
|
||||
if (effectiveY2 > screenGeometry.bottom()) {
|
||||
height -= effectiveY2 - screenGeometry.bottom() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
window->setGeometry({effectiveX, effectiveY, width, height});
|
||||
}
|
||||
|
||||
bool PopupPositioner::shouldRepositionOnMove() const { return true; }
|
||||
|
||||
PopupPositioner* PopupPositioner::instance() {
|
||||
if (POSITIONER == nullptr) {
|
||||
POSITIONER = new PopupPositioner();
|
||||
}
|
||||
|
||||
return POSITIONER;
|
||||
}
|
||||
|
||||
void PopupPositioner::setInstance(PopupPositioner* instance) {
|
||||
delete POSITIONER;
|
||||
POSITIONER = instance;
|
||||
}
|
157
src/core/popupanchor.hpp
Normal file
157
src/core/popupanchor.hpp
Normal file
|
@ -0,0 +1,157 @@
|
|||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <QtQmlIntegration/qqmlintegration.h>
|
||||
#include <qflags.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qpoint.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtclasshelpermacros.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qwindow.h>
|
||||
|
||||
#include "doc.hpp"
|
||||
#include "proxywindow.hpp"
|
||||
#include "types.hpp"
|
||||
|
||||
///! Adjustment strategy for popups that do not fit on screen.
|
||||
/// Adjustment strategy for popups. See @@PopupAnchor.adjustment.
|
||||
///
|
||||
/// Adjustment flags can be combined with the `|` operator.
|
||||
///
|
||||
/// `Flip` will be applied first, then `Slide`, then `Resize`.
|
||||
namespace PopupAdjustment { // NOLINT
|
||||
Q_NAMESPACE;
|
||||
QML_ELEMENT;
|
||||
|
||||
enum Enum {
|
||||
None = 0,
|
||||
/// If the X axis is constrained, the popup will slide along the X axis until it fits onscreen.
|
||||
SlideX = 1,
|
||||
/// If the Y axis is constrained, the popup will slide along the Y axis until it fits onscreen.
|
||||
SlideY = 2,
|
||||
/// Alias for `SlideX | SlideY`.
|
||||
Slide = SlideX | SlideY,
|
||||
/// If the X axis is constrained, the popup will invert its horizontal gravity if any.
|
||||
FlipX = 4,
|
||||
/// If the Y axis is constrained, the popup will invert its vertical gravity if any.
|
||||
FlipY = 8,
|
||||
/// Alias for `FlipX | FlipY`.
|
||||
Flip = FlipX | FlipY,
|
||||
/// If the X axis is constrained, the width of the popup will be reduced to fit on screen.
|
||||
ResizeX = 16,
|
||||
/// If the Y axis is constrained, the height of the popup will be reduced to fit on screen.
|
||||
ResizeY = 32,
|
||||
/// Alias for `ResizeX | ResizeY`
|
||||
Resize = ResizeX | ResizeY,
|
||||
/// Alias for `Flip | Slide | Resize`.
|
||||
All = Slide | Flip | Resize,
|
||||
};
|
||||
Q_ENUM_NS(Enum);
|
||||
Q_DECLARE_FLAGS(Flags, Enum);
|
||||
|
||||
} // namespace PopupAdjustment
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(PopupAdjustment::Flags);
|
||||
|
||||
struct PopupAnchorState {
|
||||
bool operator==(const PopupAnchorState& other) const;
|
||||
|
||||
Box rect = {0, 0, 1, 1};
|
||||
Edges::Flags edges = Edges::Top | Edges::Left;
|
||||
Edges::Flags gravity = Edges::Bottom | Edges::Right;
|
||||
PopupAdjustment::Flags adjustment = PopupAdjustment::Slide;
|
||||
QPoint anchorpoint;
|
||||
};
|
||||
|
||||
///! Anchorpoint or positioner for popup windows.
|
||||
class PopupAnchor: public QObject {
|
||||
Q_OBJECT;
|
||||
// clang-format off
|
||||
/// The window to anchor / attach the popup to.
|
||||
Q_PROPERTY(QObject* window READ window WRITE setWindow NOTIFY windowChanged);
|
||||
/// The anchorpoints the popup will attach to. Which anchors will be used is
|
||||
/// determined by the @@edges, @@gravity, and @@adjustment.
|
||||
///
|
||||
/// If you leave @@edges, @@gravity and @@adjustment at their default values,
|
||||
/// setting more than `x` and `y` does not matter.
|
||||
///
|
||||
/// > [!INFO] The anchor rect cannot be smaller than 1x1 pixels.
|
||||
Q_PROPERTY(Box rect READ rect WRITE setRect NOTIFY rectChanged);
|
||||
/// The point on the anchor rectangle the popup should anchor to.
|
||||
/// Opposing edges suchs as `Edges.Left | Edges.Right` are not allowed.
|
||||
///
|
||||
/// Defaults to `Edges.Top | Edges.Left`.
|
||||
Q_PROPERTY(Edges::Flags edges READ edges WRITE setEdges NOTIFY edgesChanged);
|
||||
/// The direction the popup should expand towards, relative to the anchorpoint.
|
||||
/// Opposing edges suchs as `Edges.Left | Edges.Right` are not allowed.
|
||||
///
|
||||
/// Defaults to `Edges.Bottom | Edges.Right`.
|
||||
Q_PROPERTY(Edges::Flags gravity READ gravity WRITE setGravity NOTIFY gravityChanged);
|
||||
/// The strategy used to adjust the popup's position if it would otherwise not fit on screen,
|
||||
/// based on the anchor @@rect, preferred @@edges, and @@gravity.
|
||||
///
|
||||
/// See the documentation for @@PopupAdjustment for details.
|
||||
Q_PROPERTY(PopupAdjustment::Flags adjustment READ adjustment WRITE setAdjustment NOTIFY adjustmentChanged);
|
||||
// clang-format on
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("");
|
||||
|
||||
public:
|
||||
explicit PopupAnchor(QObject* parent): QObject(parent) {}
|
||||
|
||||
[[nodiscard]] bool isDirty() const;
|
||||
void markClean();
|
||||
void markDirty();
|
||||
|
||||
[[nodiscard]] QObject* window() const;
|
||||
[[nodiscard]] ProxyWindowBase* proxyWindow() const;
|
||||
[[nodiscard]] QWindow* backingWindow() const;
|
||||
void setWindow(QObject* window);
|
||||
|
||||
[[nodiscard]] Box rect() const;
|
||||
void setRect(Box rect);
|
||||
|
||||
[[nodiscard]] Edges::Flags edges() const;
|
||||
void setEdges(Edges::Flags edges);
|
||||
|
||||
[[nodiscard]] Edges::Flags gravity() const;
|
||||
void setGravity(Edges::Flags gravity);
|
||||
|
||||
[[nodiscard]] PopupAdjustment::Flags adjustment() const;
|
||||
void setAdjustment(PopupAdjustment::Flags adjustment);
|
||||
|
||||
void updateAnchorpoint(const QPoint& anchorpoint);
|
||||
|
||||
signals:
|
||||
void windowChanged();
|
||||
QSDOC_HIDE void backingWindowVisibilityChanged();
|
||||
void rectChanged();
|
||||
void edgesChanged();
|
||||
void gravityChanged();
|
||||
void adjustmentChanged();
|
||||
|
||||
private slots:
|
||||
void onWindowDestroyed();
|
||||
|
||||
private:
|
||||
QObject* mWindow = nullptr;
|
||||
ProxyWindowBase* mProxyWindow = nullptr;
|
||||
PopupAnchorState state;
|
||||
std::optional<PopupAnchorState> lastState;
|
||||
};
|
||||
|
||||
class PopupPositioner {
|
||||
public:
|
||||
explicit PopupPositioner() = default;
|
||||
virtual ~PopupPositioner() = default;
|
||||
Q_DISABLE_COPY_MOVE(PopupPositioner);
|
||||
|
||||
virtual void reposition(PopupAnchor* anchor, QWindow* window, bool onlyIfDirty = true);
|
||||
[[nodiscard]] virtual bool shouldRepositionOnMove() const;
|
||||
|
||||
static PopupPositioner* instance();
|
||||
static void setInstance(PopupPositioner* instance);
|
||||
};
|
|
@ -4,86 +4,66 @@
|
|||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
#include <qwindow.h>
|
||||
|
||||
#include "popupanchor.hpp"
|
||||
#include "proxywindow.hpp"
|
||||
#include "qmlscreen.hpp"
|
||||
#include "windowinterface.hpp"
|
||||
|
||||
ProxyPopupWindow::ProxyPopupWindow(QObject* parent): ProxyWindowBase(parent) {
|
||||
this->mVisible = false;
|
||||
// clang-format off
|
||||
QObject::connect(&this->mAnchor, &PopupAnchor::windowChanged, this, &ProxyPopupWindow::parentWindowChanged);
|
||||
QObject::connect(&this->mAnchor, &PopupAnchor::rectChanged, this, &ProxyPopupWindow::reposition);
|
||||
QObject::connect(&this->mAnchor, &PopupAnchor::edgesChanged, this, &ProxyPopupWindow::reposition);
|
||||
QObject::connect(&this->mAnchor, &PopupAnchor::gravityChanged, this, &ProxyPopupWindow::reposition);
|
||||
QObject::connect(&this->mAnchor, &PopupAnchor::adjustmentChanged, this, &ProxyPopupWindow::reposition);
|
||||
QObject::connect(&this->mAnchor, &PopupAnchor::backingWindowVisibilityChanged, this, &ProxyPopupWindow::onParentUpdated);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
void ProxyPopupWindow::completeWindow() {
|
||||
this->ProxyWindowBase::completeWindow();
|
||||
QObject::connect(
|
||||
this->window,
|
||||
&QWindow::visibleChanged,
|
||||
this,
|
||||
&ProxyPopupWindow::onVisibleChanged
|
||||
);
|
||||
|
||||
this->window->setFlag(Qt::ToolTip);
|
||||
this->updateTransientParent();
|
||||
}
|
||||
|
||||
void ProxyPopupWindow::postCompleteWindow() { this->ProxyWindowBase::setVisible(this->mVisible); }
|
||||
|
||||
bool ProxyPopupWindow::deleteOnInvisible() const {
|
||||
// Currently crashes in normal mode, do not have the time to debug it now.
|
||||
return true;
|
||||
}
|
||||
|
||||
qint32 ProxyPopupWindow::x() const {
|
||||
// QTBUG-121550
|
||||
auto basepos = this->mParentProxyWindow == nullptr ? 0 : this->mParentProxyWindow->x();
|
||||
return basepos + this->mRelativeX;
|
||||
}
|
||||
void ProxyPopupWindow::postCompleteWindow() { this->updateTransientParent(); }
|
||||
|
||||
void ProxyPopupWindow::setParentWindow(QObject* parent) {
|
||||
if (parent == this->mParentWindow) return;
|
||||
|
||||
if (this->mParentWindow != nullptr) {
|
||||
QObject::disconnect(this->mParentWindow, nullptr, this, nullptr);
|
||||
QObject::disconnect(this->mParentProxyWindow, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
if (parent == nullptr) {
|
||||
this->mParentWindow = nullptr;
|
||||
this->mParentProxyWindow = nullptr;
|
||||
} else {
|
||||
if (auto* proxy = qobject_cast<ProxyWindowBase*>(parent)) {
|
||||
this->mParentProxyWindow = proxy;
|
||||
} else if (auto* interface = qobject_cast<WindowInterface*>(parent)) {
|
||||
this->mParentProxyWindow = interface->proxyWindow();
|
||||
} else {
|
||||
qWarning() << "Tried to set popup parent window to something that is not a quickshell window:"
|
||||
<< parent;
|
||||
this->mParentWindow = nullptr;
|
||||
this->mParentProxyWindow = nullptr;
|
||||
this->updateTransientParent();
|
||||
return;
|
||||
}
|
||||
|
||||
this->mParentWindow = parent;
|
||||
|
||||
// clang-format off
|
||||
QObject::connect(this->mParentWindow, &QObject::destroyed, this, &ProxyPopupWindow::onParentDestroyed);
|
||||
|
||||
QObject::connect(this->mParentProxyWindow, &ProxyWindowBase::xChanged, this, &ProxyPopupWindow::updateX);
|
||||
QObject::connect(this->mParentProxyWindow, &ProxyWindowBase::yChanged, this, &ProxyPopupWindow::updateY);
|
||||
QObject::connect(this->mParentProxyWindow, &ProxyWindowBase::backerVisibilityChanged, this, &ProxyPopupWindow::onParentUpdated);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
this->updateTransientParent();
|
||||
qWarning() << "PopupWindow.parentWindow is deprecated. Use PopupWindow.anchor.window.";
|
||||
this->mAnchor.setWindow(parent);
|
||||
}
|
||||
|
||||
QObject* ProxyPopupWindow::parentWindow() const { return this->mParentWindow; }
|
||||
QObject* ProxyPopupWindow::parentWindow() const {
|
||||
qWarning() << "PopupWindow.parentWindow is deprecated. Use PopupWindow.anchor.window.";
|
||||
return this->mAnchor.window();
|
||||
}
|
||||
|
||||
void ProxyPopupWindow::updateTransientParent() {
|
||||
this->updateX();
|
||||
this->updateY();
|
||||
auto* bw = this->mAnchor.backingWindow();
|
||||
|
||||
if (this->window != nullptr) {
|
||||
this->window->setTransientParent(
|
||||
this->mParentProxyWindow == nullptr ? nullptr : this->mParentProxyWindow->backingWindow()
|
||||
);
|
||||
if (this->window != nullptr && bw != this->window->transientParent()) {
|
||||
if (this->window->transientParent()) {
|
||||
QObject::disconnect(this->window->transientParent(), nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
if (bw && PopupPositioner::instance()->shouldRepositionOnMove()) {
|
||||
QObject::connect(bw, &QWindow::xChanged, this, &ProxyPopupWindow::reposition);
|
||||
QObject::connect(bw, &QWindow::yChanged, this, &ProxyPopupWindow::reposition);
|
||||
QObject::connect(bw, &QWindow::widthChanged, this, &ProxyPopupWindow::reposition);
|
||||
QObject::connect(bw, &QWindow::heightChanged, this, &ProxyPopupWindow::reposition);
|
||||
}
|
||||
|
||||
this->window->setTransientParent(bw);
|
||||
}
|
||||
|
||||
this->updateVisible();
|
||||
|
@ -91,13 +71,6 @@ void ProxyPopupWindow::updateTransientParent() {
|
|||
|
||||
void ProxyPopupWindow::onParentUpdated() { this->updateTransientParent(); }
|
||||
|
||||
void ProxyPopupWindow::onParentDestroyed() {
|
||||
this->mParentWindow = nullptr;
|
||||
this->mParentProxyWindow = nullptr;
|
||||
this->updateVisible();
|
||||
emit this->parentWindowChanged();
|
||||
}
|
||||
|
||||
void ProxyPopupWindow::setScreen(QuickshellScreenInfo* /*unused*/) {
|
||||
qWarning() << "Cannot set screen of popup window, as that is controlled by the parent window";
|
||||
}
|
||||
|
@ -109,53 +82,55 @@ void ProxyPopupWindow::setVisible(bool visible) {
|
|||
}
|
||||
|
||||
void ProxyPopupWindow::updateVisible() {
|
||||
auto target = this->wantsVisible && this->mParentWindow != nullptr
|
||||
&& this->mParentProxyWindow->isVisibleDirect();
|
||||
auto target = this->wantsVisible && this->mAnchor.window() != nullptr
|
||||
&& this->mAnchor.proxyWindow()->isVisibleDirect();
|
||||
|
||||
if (target && this->window != nullptr && !this->window->isVisible()) {
|
||||
this->updateX(); // QTBUG-121550
|
||||
PopupPositioner::instance()->reposition(&this->mAnchor, this->window);
|
||||
}
|
||||
|
||||
this->ProxyWindowBase::setVisible(target);
|
||||
}
|
||||
|
||||
void ProxyPopupWindow::setRelativeX(qint32 x) {
|
||||
if (x == this->mRelativeX) return;
|
||||
this->mRelativeX = x;
|
||||
this->updateX();
|
||||
void ProxyPopupWindow::onVisibleChanged() {
|
||||
// If the window was made invisible without its parent becoming invisible
|
||||
// the compositor probably destroyed it. Without this the window won't ever
|
||||
// be able to become visible again.
|
||||
if (this->window->transientParent() && this->window->transientParent()->isVisible()) {
|
||||
this->wantsVisible = this->window->isVisible();
|
||||
}
|
||||
}
|
||||
|
||||
qint32 ProxyPopupWindow::relativeX() const { return this->mRelativeX; }
|
||||
void ProxyPopupWindow::setRelativeX(qint32 x) {
|
||||
qWarning() << "PopupWindow.relativeX is deprecated. Use PopupWindow.anchor.rect.x.";
|
||||
auto rect = this->mAnchor.rect();
|
||||
if (x == rect.x) return;
|
||||
rect.x = x;
|
||||
this->mAnchor.setRect(rect);
|
||||
}
|
||||
|
||||
qint32 ProxyPopupWindow::relativeX() const {
|
||||
qWarning() << "PopupWindow.relativeX is deprecated. Use PopupWindow.anchor.rect.x.";
|
||||
return this->mAnchor.rect().x;
|
||||
}
|
||||
|
||||
void ProxyPopupWindow::setRelativeY(qint32 y) {
|
||||
if (y == this->mRelativeY) return;
|
||||
this->mRelativeY = y;
|
||||
this->updateY();
|
||||
qWarning() << "PopupWindow.relativeY is deprecated. Use PopupWindow.anchor.rect.y.";
|
||||
auto rect = this->mAnchor.rect();
|
||||
if (y == rect.y) return;
|
||||
rect.y = y;
|
||||
this->mAnchor.setRect(rect);
|
||||
}
|
||||
|
||||
qint32 ProxyPopupWindow::relativeY() const { return this->mRelativeY; }
|
||||
|
||||
void ProxyPopupWindow::updateX() {
|
||||
if (this->mParentWindow == nullptr || this->window == nullptr) return;
|
||||
|
||||
auto target = this->x() - 1; // QTBUG-121550
|
||||
|
||||
auto reshow = this->isVisibleDirect() && (this->window->x() != target && this->x() != target);
|
||||
if (reshow) this->setVisibleDirect(false);
|
||||
if (this->window != nullptr) this->window->setX(target);
|
||||
if (reshow && this->wantsVisible) this->setVisibleDirect(true);
|
||||
qint32 ProxyPopupWindow::relativeY() const {
|
||||
qWarning() << "PopupWindow.relativeY is deprecated. Use PopupWindow.anchor.rect.y.";
|
||||
return this->mAnchor.rect().y;
|
||||
}
|
||||
|
||||
void ProxyPopupWindow::updateY() {
|
||||
if (this->mParentWindow == nullptr || this->window == nullptr) return;
|
||||
PopupAnchor* ProxyPopupWindow::anchor() { return &this->mAnchor; }
|
||||
|
||||
auto target = this->mParentProxyWindow->y() + this->relativeY();
|
||||
|
||||
auto reshow = this->isVisibleDirect() && this->window->y() != target;
|
||||
if (reshow) {
|
||||
this->setVisibleDirect(false);
|
||||
this->updateX(); // QTBUG-121550
|
||||
void ProxyPopupWindow::reposition() {
|
||||
if (this->window != nullptr) {
|
||||
PopupPositioner::instance()->reposition(&this->mAnchor, this->window);
|
||||
}
|
||||
if (this->window != nullptr) this->window->setY(target);
|
||||
if (reshow && this->wantsVisible) this->setVisibleDirect(true);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <qtypes.h>
|
||||
|
||||
#include "doc.hpp"
|
||||
#include "popupanchor.hpp"
|
||||
#include "proxywindow.hpp"
|
||||
#include "qmlscreen.hpp"
|
||||
#include "windowinterface.hpp"
|
||||
|
@ -42,15 +43,37 @@ class ProxyPopupWindow: public ProxyWindowBase {
|
|||
QSDOC_BASECLASS(WindowInterface);
|
||||
Q_OBJECT;
|
||||
// clang-format off
|
||||
/// > [!ERROR] Deprecated in favor of `anchor.window`.
|
||||
///
|
||||
/// The parent window of this popup.
|
||||
///
|
||||
/// Changing this property reparents the popup.
|
||||
Q_PROPERTY(QObject* parentWindow READ parentWindow WRITE setParentWindow NOTIFY parentWindowChanged);
|
||||
/// > [!ERROR] Deprecated in favor of `anchor.rect.x`.
|
||||
///
|
||||
/// The X position of the popup relative to the parent window.
|
||||
Q_PROPERTY(qint32 relativeX READ relativeX WRITE setRelativeX NOTIFY relativeXChanged);
|
||||
/// > [!ERROR] Deprecated in favor of `anchor.rect.y`.
|
||||
///
|
||||
/// The Y position of the popup relative to the parent window.
|
||||
Q_PROPERTY(qint32 relativeY READ relativeY WRITE setRelativeY NOTIFY relativeYChanged);
|
||||
/// The popup's anchor / positioner relative to another window. The popup will not be
|
||||
/// shown until it has a valid anchor relative to a window and @@visible is true.
|
||||
///
|
||||
/// You can set properties of the anchor like so:
|
||||
/// ```qml
|
||||
/// PopupWindow {
|
||||
/// anchor.window: parentwindow
|
||||
/// // or
|
||||
/// anchor {
|
||||
/// window: parentwindow
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
Q_PROPERTY(PopupAnchor* anchor READ anchor CONSTANT);
|
||||
/// If the window is shown or hidden. Defaults to false.
|
||||
///
|
||||
/// The popup will not be shown until @@anchor is valid, regardless of this property.
|
||||
QSDOC_PROPERTY_OVERRIDE(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged);
|
||||
/// The screen that the window currently occupies.
|
||||
///
|
||||
|
@ -64,13 +87,10 @@ public:
|
|||
|
||||
void completeWindow() override;
|
||||
void postCompleteWindow() override;
|
||||
[[nodiscard]] bool deleteOnInvisible() const override;
|
||||
|
||||
void setScreen(QuickshellScreenInfo* screen) override;
|
||||
void setVisible(bool visible) override;
|
||||
|
||||
[[nodiscard]] qint32 x() const override;
|
||||
|
||||
[[nodiscard]] QObject* parentWindow() const;
|
||||
void setParentWindow(QObject* parent);
|
||||
|
||||
|
@ -80,25 +100,23 @@ public:
|
|||
[[nodiscard]] qint32 relativeY() const;
|
||||
void setRelativeY(qint32 y);
|
||||
|
||||
[[nodiscard]] PopupAnchor* anchor();
|
||||
|
||||
signals:
|
||||
void parentWindowChanged();
|
||||
void relativeXChanged();
|
||||
void relativeYChanged();
|
||||
|
||||
private slots:
|
||||
void onVisibleChanged();
|
||||
void onParentUpdated();
|
||||
void onParentDestroyed();
|
||||
void updateX();
|
||||
void updateY();
|
||||
void reposition();
|
||||
|
||||
private:
|
||||
QQuickWindow* parentBackingWindow();
|
||||
void updateTransientParent();
|
||||
void updateVisible();
|
||||
|
||||
QObject* mParentWindow = nullptr;
|
||||
ProxyWindowBase* mParentProxyWindow = nullptr;
|
||||
qint32 mRelativeX = 0;
|
||||
qint32 mRelativeY = 0;
|
||||
PopupAnchor mAnchor {this};
|
||||
bool wantsVisible = false;
|
||||
};
|
||||
|
|
23
src/core/types.cpp
Normal file
23
src/core/types.cpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
#include "types.hpp"
|
||||
|
||||
#include <qdebug.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qrect.h>
|
||||
|
||||
QRect Box::qrect() const { return {this->x, this->y, this->w, this->h}; }
|
||||
|
||||
bool Box::operator==(const Box& other) const {
|
||||
return this->x == other.x && this->y == other.y && this->w == other.w && this->h == other.h;
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const Box& box) {
|
||||
auto saver = QDebugStateSaver(debug);
|
||||
debug.nospace() << "Box(" << box.x << ',' << box.y << ' ' << box.w << 'x' << box.h << ')';
|
||||
return debug;
|
||||
}
|
||||
|
||||
Qt::Edges Edges::toQt(Edges::Flags edges) { return Qt::Edges(edges.toInt()); }
|
||||
|
||||
bool Edges::isOpposing(Edges::Flags edges) {
|
||||
return edges.testFlags(Edges::Top | Edges::Bottom) || edges.testFlags(Edges::Left | Edges::Right);
|
||||
}
|
54
src/core/types.hpp
Normal file
54
src/core/types.hpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
|
||||
#include <qdebug.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
class Box {
|
||||
Q_GADGET;
|
||||
Q_PROPERTY(qint32 x MEMBER x);
|
||||
Q_PROPERTY(qint32 y MEMBER y);
|
||||
Q_PROPERTY(qint32 w MEMBER w);
|
||||
Q_PROPERTY(qint32 h MEMBER h);
|
||||
Q_PROPERTY(qint32 width MEMBER w);
|
||||
Q_PROPERTY(qint32 height MEMBER h);
|
||||
QML_VALUE_TYPE(box);
|
||||
|
||||
public:
|
||||
explicit Box() = default;
|
||||
Box(qint32 x, qint32 y, qint32 w, qint32 h): x(x), y(y), w(w), h(h) {}
|
||||
bool operator==(const Box& other) const;
|
||||
|
||||
qint32 x = 0;
|
||||
qint32 y = 0;
|
||||
qint32 w = 0;
|
||||
qint32 h = 0;
|
||||
|
||||
[[nodiscard]] QRect qrect() const;
|
||||
};
|
||||
|
||||
QDebug operator<<(QDebug debug, const Box& box);
|
||||
|
||||
///! Top Left Right Bottom flags.
|
||||
/// Edge flags can be combined with the `|` operator.
|
||||
namespace Edges { // NOLINT
|
||||
Q_NAMESPACE;
|
||||
QML_NAMED_ELEMENT(Edges);
|
||||
|
||||
enum Enum {
|
||||
None = 0,
|
||||
Top = Qt::TopEdge,
|
||||
Left = Qt::LeftEdge,
|
||||
Right = Qt::RightEdge,
|
||||
Bottom = Qt::BottomEdge,
|
||||
};
|
||||
Q_ENUM_NS(Enum);
|
||||
Q_DECLARE_FLAGS(Flags, Enum);
|
||||
|
||||
Qt::Edges toQt(Flags edges);
|
||||
bool isOpposing(Flags edges);
|
||||
|
||||
}; // namespace Edges
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(Edges::Flags);
|
|
@ -52,6 +52,8 @@ endfunction()
|
|||
|
||||
qt_add_library(quickshell-wayland STATIC
|
||||
platformmenu.cpp
|
||||
popupanchor.cpp
|
||||
xdgshell.cpp
|
||||
)
|
||||
|
||||
# required to make sure the constructor is linked
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
#include <qtenvironmentvariables.h>
|
||||
|
||||
#include "../core/plugin.hpp"
|
||||
#include "platformmenu.hpp"
|
||||
|
||||
#ifdef QS_WAYLAND_WLR_LAYERSHELL
|
||||
#include "wlr_layershell.hpp"
|
||||
#endif
|
||||
|
||||
void installPlatformMenuHook();
|
||||
void installPopupPositioner();
|
||||
|
||||
namespace {
|
||||
|
||||
class WaylandPlugin: public QuickshellPlugin {
|
||||
|
@ -27,7 +29,10 @@ class WaylandPlugin: public QuickshellPlugin {
|
|||
return isWayland;
|
||||
}
|
||||
|
||||
void init() override { installPlatformMenuHook(); }
|
||||
void init() override {
|
||||
installPlatformMenuHook();
|
||||
installPopupPositioner();
|
||||
}
|
||||
|
||||
void registerTypes() override {
|
||||
#ifdef QS_WAYLAND_WLR_LAYERSHELL
|
||||
|
|
99
src/wayland/popupanchor.cpp
Normal file
99
src/wayland/popupanchor.cpp
Normal file
|
@ -0,0 +1,99 @@
|
|||
#include "popupanchor.hpp"
|
||||
|
||||
#include <private/qwayland-xdg-shell.h>
|
||||
#include <private/qwaylandwindow_p.h>
|
||||
#include <private/wayland-xdg-shell-client-protocol.h>
|
||||
#include <qvariant.h>
|
||||
#include <qwindow.h>
|
||||
|
||||
#include "../core/popupanchor.hpp"
|
||||
#include "../core/types.hpp"
|
||||
#include "xdgshell.hpp"
|
||||
|
||||
using QtWaylandClient::QWaylandWindow;
|
||||
using XdgPositioner = QtWayland::xdg_positioner;
|
||||
using qs::wayland::xdg_shell::XdgWmBase;
|
||||
|
||||
void WaylandPopupPositioner::reposition(PopupAnchor* anchor, QWindow* window, bool onlyIfDirty) {
|
||||
if (onlyIfDirty && !anchor->isDirty()) return;
|
||||
|
||||
auto* waylandWindow = dynamic_cast<QWaylandWindow*>(window->handle());
|
||||
auto* popupRole = waylandWindow ? waylandWindow->surfaceRole<::xdg_popup>() : nullptr;
|
||||
|
||||
anchor->markClean();
|
||||
|
||||
if (popupRole) {
|
||||
auto* xdgWmBase = XdgWmBase::instance();
|
||||
|
||||
if (xdgWmBase->QtWayland::xdg_wm_base::version() < XDG_POPUP_REPOSITION_SINCE_VERSION) {
|
||||
window->setVisible(false);
|
||||
WaylandPopupPositioner::setFlags(anchor, window);
|
||||
window->setVisible(true);
|
||||
return;
|
||||
}
|
||||
|
||||
auto positioner = XdgPositioner(xdgWmBase->create_positioner());
|
||||
|
||||
positioner.set_constraint_adjustment(anchor->adjustment().toInt());
|
||||
|
||||
auto anchorRect = anchor->rect();
|
||||
positioner.set_anchor_rect(anchorRect.x, anchorRect.y, anchorRect.w, anchorRect.h);
|
||||
|
||||
XdgPositioner::anchor anchorFlag = XdgPositioner::anchor_none;
|
||||
switch (anchor->edges()) {
|
||||
case Edges::Top: anchorFlag = XdgPositioner::anchor_top; break;
|
||||
case Edges::Top | Edges::Right: anchorFlag = XdgPositioner::anchor_top_right; break;
|
||||
case Edges::Right: anchorFlag = XdgPositioner::anchor_right; break;
|
||||
case Edges::Bottom | Edges::Right: anchorFlag = XdgPositioner::anchor_bottom_right; break;
|
||||
case Edges::Bottom: anchorFlag = XdgPositioner::anchor_bottom; break;
|
||||
case Edges::Bottom | Edges::Left: anchorFlag = XdgPositioner::anchor_bottom_left; break;
|
||||
case Edges::Left: anchorFlag = XdgPositioner::anchor_left; break;
|
||||
case Edges::Top | Edges::Left: anchorFlag = XdgPositioner::anchor_top_left; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
positioner.set_anchor(anchorFlag);
|
||||
|
||||
XdgPositioner::gravity gravity = XdgPositioner::gravity_none;
|
||||
switch (anchor->gravity()) {
|
||||
case Edges::Top: gravity = XdgPositioner::gravity_top; break;
|
||||
case Edges::Top | Edges::Right: gravity = XdgPositioner::gravity_top_right; break;
|
||||
case Edges::Right: gravity = XdgPositioner::gravity_right; break;
|
||||
case Edges::Bottom | Edges::Right: gravity = XdgPositioner::gravity_bottom_right; break;
|
||||
case Edges::Bottom: gravity = XdgPositioner::gravity_bottom; break;
|
||||
case Edges::Bottom | Edges::Left: gravity = XdgPositioner::gravity_bottom_left; break;
|
||||
case Edges::Left: gravity = XdgPositioner::gravity_left; break;
|
||||
case Edges::Top | Edges::Left: gravity = XdgPositioner::gravity_top_left; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
positioner.set_gravity(gravity);
|
||||
auto geometry = waylandWindow->geometry();
|
||||
positioner.set_size(geometry.width(), geometry.height());
|
||||
|
||||
// Note: this needs to be set for the initial position as well but no compositor
|
||||
// supports it enough to test
|
||||
positioner.set_reactive();
|
||||
|
||||
xdg_popup_reposition(popupRole, positioner.object(), 0);
|
||||
|
||||
positioner.destroy();
|
||||
} else {
|
||||
WaylandPopupPositioner::setFlags(anchor, window);
|
||||
}
|
||||
}
|
||||
|
||||
// Should be false but nobody supports set_reactive.
|
||||
// This just tries its best when something like a bar gets resized.
|
||||
bool WaylandPopupPositioner::shouldRepositionOnMove() const { return true; }
|
||||
|
||||
void WaylandPopupPositioner::setFlags(PopupAnchor* anchor, QWindow* window) {
|
||||
// clang-format off
|
||||
window->setProperty("_q_waylandPopupConstraintAdjustment", anchor->adjustment().toInt());
|
||||
window->setProperty("_q_waylandPopupAnchorRect", anchor->rect().qrect());
|
||||
window->setProperty("_q_waylandPopupAnchor", QVariant::fromValue(Edges::toQt(anchor->edges())));
|
||||
window->setProperty("_q_waylandPopupGravity", QVariant::fromValue(Edges::toQt(anchor->gravity())));
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
void installPopupPositioner() { PopupPositioner::setInstance(new WaylandPopupPositioner()); }
|
16
src/wayland/popupanchor.hpp
Normal file
16
src/wayland/popupanchor.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <qwindow.h>
|
||||
|
||||
#include "../core/popupanchor.hpp"
|
||||
|
||||
class WaylandPopupPositioner: public PopupPositioner {
|
||||
public:
|
||||
void reposition(PopupAnchor* anchor, QWindow* window, bool onlyIfDirty = true) override;
|
||||
[[nodiscard]] bool shouldRepositionOnMove() const override;
|
||||
|
||||
private:
|
||||
static void setFlags(PopupAnchor* anchor, QWindow* window);
|
||||
};
|
||||
|
||||
void installPopupPositioner();
|
14
src/wayland/xdgshell.cpp
Normal file
14
src/wayland/xdgshell.cpp
Normal file
|
@ -0,0 +1,14 @@
|
|||
#include "xdgshell.hpp"
|
||||
|
||||
#include <qwaylandclientextension.h>
|
||||
|
||||
namespace qs::wayland::xdg_shell {
|
||||
|
||||
XdgWmBase::XdgWmBase(): QWaylandClientExtensionTemplate(6) { this->initialize(); }
|
||||
|
||||
XdgWmBase* XdgWmBase::instance() {
|
||||
static auto* instance = new XdgWmBase(); // NOLINT
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // namespace qs::wayland::xdg_shell
|
20
src/wayland/xdgshell.hpp
Normal file
20
src/wayland/xdgshell.hpp
Normal file
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <private/qwayland-xdg-shell.h>
|
||||
#include <qwaylandclientextension.h>
|
||||
|
||||
namespace qs::wayland::xdg_shell {
|
||||
|
||||
// Hack that binds xdg_wm_base twice as QtWaylandXdgShell headers are not exported anywhere.
|
||||
|
||||
class XdgWmBase
|
||||
: public QWaylandClientExtensionTemplate<XdgWmBase>
|
||||
, public QtWayland::xdg_wm_base {
|
||||
public:
|
||||
static XdgWmBase* instance();
|
||||
|
||||
private:
|
||||
explicit XdgWmBase();
|
||||
};
|
||||
|
||||
} // namespace qs::wayland::xdg_shell
|
Loading…
Add table
Add a link
Reference in a new issue