feat(wayland): custom layershell implementation

A fair amount of things are broken but not in the layershell interface
itself. The shell window platform extensions are next in line for
refactoring and relevent issues will be fixed then. Same for docs.
This commit is contained in:
outfoxxed 2024-02-24 02:06:40 -08:00
parent b0567a569b
commit 4a82949854
Signed by untrusted user: outfoxxed
GPG key ID: 4C88A185FB89301E
21 changed files with 1167 additions and 401 deletions

View file

@ -16,13 +16,4 @@ qt_add_executable(quickshell
qt_add_qml_module(quickshell URI QuickShell)
target_link_libraries(quickshell PRIVATE ${QT_DEPS} quickshell-wayland)
if (LAYERSHELL)
find_package(LayerShellQt REQUIRED)
target_link_libraries(quickshell PRIVATE LayerShellQtInterface)
target_compile_definitions(quickshell PRIVATE CONF_LAYERSHELL)
target_sources(quickshell PRIVATE layershell.cpp)
endif()
target_link_libraries(quickshell PRIVATE ${QT_DEPS} quickshell-waylandplugin)

View file

@ -1,300 +0,0 @@
#include "layershell.hpp"
#include <LayerShellQt/window.h>
#include <qmargins.h>
#include <qobject.h>
#include <qqmllist.h>
#include <qquickitem.h>
#include <qquickwindow.h>
#include <qscreen.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qwindow.h>
#include "proxywindow.hpp"
#include "shellwindow.hpp"
WaylandShellWindow::WaylandShellWindow(QObject* parent):
ProxyShellWindow(parent), mWayland(new WaylandShellWindowExtensions(this)) {}
void WaylandShellWindow::setupWindow() {
this->shellWindow = LayerShellQt::Window::get(this->window);
this->ProxyShellWindow::setupWindow();
// clang-format off
QObject::connect(this->shellWindow, &LayerShellQt::Window::anchorsChanged, this, &ProxyShellWindow::anchorsChanged);
QObject::connect(this->shellWindow, &LayerShellQt::Window::marginsChanged, this, &ProxyShellWindow::marginsChanged);
QObject::connect(
this->shellWindow, &LayerShellQt::Window::layerChanged,
this->mWayland, &WaylandShellWindowExtensions::layerChanged
);
QObject::connect(
this->shellWindow, &LayerShellQt::Window::keyboardInteractivityChanged,
this->mWayland, &WaylandShellWindowExtensions::keyboardFocusChanged
);
QObject::connect(this->window, &QWindow::widthChanged, this, &WaylandShellWindow::updateExclusionZone);
QObject::connect(this->window, &QWindow::heightChanged, this, &WaylandShellWindow::updateExclusionZone);
QObject::connect(this, &ProxyShellWindow::anchorsChanged, this, &WaylandShellWindow::updateExclusionZone);
QObject::connect(this, &ProxyShellWindow::marginsChanged, this, &WaylandShellWindow::updateExclusionZone);
// clang-format on
this->setAnchors(this->mAnchors);
this->setMargins(this->mMargins);
this->setExclusionMode(this->mExclusionMode); // also sets exclusion zone
this->mWayland->setLayer(this->mLayer);
this->shellWindow->setScope(this->mScope);
this->mWayland->setKeyboardFocus(this->mKeyboardFocus);
this->connected = true;
}
QQuickWindow* WaylandShellWindow::disownWindow() {
QObject::disconnect(this->shellWindow, nullptr, this, nullptr);
return this->ProxyWindowBase::disownWindow();
}
void WaylandShellWindow::setWidth(qint32 width) {
this->mWidth = width;
// only update the actual size if not blocked by anchors
auto anchors = this->anchors();
if (!anchors.mLeft || !anchors.mRight) this->ProxyShellWindow::setWidth(width);
}
void WaylandShellWindow::setHeight(qint32 height) {
this->mHeight = height;
// only update the actual size if not blocked by anchors
auto anchors = this->anchors();
if (!anchors.mTop || !anchors.mBottom) this->ProxyShellWindow::setHeight(height);
}
void WaylandShellWindow::setAnchors(Anchors anchors) {
if (this->window == nullptr) {
this->mAnchors = anchors;
return;
}
auto lsAnchors = LayerShellQt::Window::Anchors();
if (anchors.mLeft) lsAnchors |= LayerShellQt::Window::AnchorLeft;
if (anchors.mRight) lsAnchors |= LayerShellQt::Window::AnchorRight;
if (anchors.mTop) lsAnchors |= LayerShellQt::Window::AnchorTop;
if (anchors.mBottom) lsAnchors |= LayerShellQt::Window::AnchorBottom;
if (!anchors.mLeft || !anchors.mRight) this->ProxyWindowBase::setWidth(this->mWidth);
if (!anchors.mTop || !anchors.mBottom) this->ProxyWindowBase::setHeight(this->mHeight);
this->shellWindow->setAnchors(lsAnchors);
}
Anchors WaylandShellWindow::anchors() const {
if (this->window == nullptr) return this->mAnchors;
auto lsAnchors = this->shellWindow->anchors();
Anchors anchors;
anchors.mLeft = lsAnchors.testFlag(LayerShellQt::Window::AnchorLeft);
anchors.mRight = lsAnchors.testFlag(LayerShellQt::Window::AnchorRight);
anchors.mTop = lsAnchors.testFlag(LayerShellQt::Window::AnchorTop);
anchors.mBottom = lsAnchors.testFlag(LayerShellQt::Window::AnchorBottom);
return anchors;
}
void WaylandShellWindow::setExclusiveZone(qint32 zone) {
if (zone < 0) zone = 0;
if (this->connected && zone == this->mExclusionZone) return;
this->mExclusionZone = zone;
if (this->window != nullptr && this->exclusionMode() == ExclusionMode::Normal) {
this->shellWindow->setExclusiveZone(zone);
emit this->exclusionZoneChanged();
}
}
qint32 WaylandShellWindow::exclusiveZone() const {
if (this->window == nullptr) return this->mExclusionZone;
else return this->shellWindow->exclusionZone();
}
ExclusionMode::Enum WaylandShellWindow::exclusionMode() const { return this->mExclusionMode; }
void WaylandShellWindow::setExclusionMode(ExclusionMode::Enum exclusionMode) {
if (this->connected && exclusionMode == this->mExclusionMode) return;
this->mExclusionMode = exclusionMode;
if (this->window != nullptr) {
if (exclusionMode == ExclusionMode::Normal) {
this->shellWindow->setExclusiveZone(this->mExclusionZone);
emit this->exclusionZoneChanged();
} else if (exclusionMode == ExclusionMode::Ignore) {
this->shellWindow->setExclusiveZone(-1);
emit this->exclusionZoneChanged();
} else {
this->updateExclusionZone();
}
}
}
void WaylandShellWindow::setMargins(Margins margins) {
if (this->window == nullptr) this->mMargins = margins;
else {
auto lsMargins = QMargins(margins.mLeft, margins.mTop, margins.mRight, margins.mBottom);
this->shellWindow->setMargins(lsMargins);
}
}
Margins WaylandShellWindow::margins() const {
if (this->window == nullptr) return this->mMargins;
auto lsMargins = this->shellWindow->margins();
auto margins = Margins();
margins.mLeft = lsMargins.left();
margins.mRight = lsMargins.right();
margins.mTop = lsMargins.top();
margins.mBottom = lsMargins.bottom();
return margins;
}
void WaylandShellWindowExtensions::setLayer(Layer::Enum layer) {
if (this->window->window == nullptr) {
this->window->mLayer = layer;
return;
}
auto lsLayer = LayerShellQt::Window::LayerBackground;
// clang-format off
switch (layer) {
case Layer::Background: lsLayer = LayerShellQt::Window::LayerBackground; break;
case Layer::Bottom: lsLayer = LayerShellQt::Window::LayerBottom; break;
case Layer::Top: lsLayer = LayerShellQt::Window::LayerTop; break;
case Layer::Overlay: lsLayer = LayerShellQt::Window::LayerOverlay; break;
}
// clang-format on
this->window->shellWindow->setLayer(lsLayer);
}
Layer::Enum WaylandShellWindowExtensions::layer() const {
if (this->window->window == nullptr) return this->window->mLayer;
auto layer = Layer::Top;
auto lsLayer = this->window->shellWindow->layer();
// clang-format off
switch (lsLayer) {
case LayerShellQt::Window::LayerBackground: layer = Layer::Background; break;
case LayerShellQt::Window::LayerBottom: layer = Layer::Bottom; break;
case LayerShellQt::Window::LayerTop: layer = Layer::Top; break;
case LayerShellQt::Window::LayerOverlay: layer = Layer::Overlay; break;
}
// clang-format on
return layer;
}
void WaylandShellWindowExtensions::setScope(const QString& scope) {
if (this->window->window == nullptr) this->window->mScope = scope;
else this->window->shellWindow->setScope(scope);
}
QString WaylandShellWindowExtensions::scope() const {
if (this->window->window == nullptr) return this->window->mScope;
else return this->window->shellWindow->scope();
}
void WaylandShellWindowExtensions::setKeyboardFocus(KeyboardFocus::Enum focus) {
if (this->window->window == nullptr) {
this->window->mKeyboardFocus = focus;
return;
}
auto lsFocus = LayerShellQt::Window::KeyboardInteractivityNone;
// clang-format off
switch (focus) {
case KeyboardFocus::None: lsFocus = LayerShellQt::Window::KeyboardInteractivityNone; break;
case KeyboardFocus::Exclusive: lsFocus = LayerShellQt::Window::KeyboardInteractivityExclusive; break;
case KeyboardFocus::OnDemand: lsFocus = LayerShellQt::Window::KeyboardInteractivityOnDemand; break;
}
// clang-format on
this->window->shellWindow->setKeyboardInteractivity(lsFocus);
}
KeyboardFocus::Enum WaylandShellWindowExtensions::keyboardFocus() const {
if (this->window->window == nullptr) return this->window->mKeyboardFocus;
auto focus = KeyboardFocus::None;
auto lsFocus = this->window->shellWindow->keyboardInteractivity();
// clang-format off
switch (lsFocus) {
case LayerShellQt::Window::KeyboardInteractivityNone: focus = KeyboardFocus::None; break;
case LayerShellQt::Window::KeyboardInteractivityExclusive: focus = KeyboardFocus::Exclusive; break;
case LayerShellQt::Window::KeyboardInteractivityOnDemand: focus = KeyboardFocus::OnDemand; break;
}
// clang-format on
return focus;
}
void WaylandShellWindowExtensions::setScreenConfiguration(ScreenConfiguration::Enum configuration) {
if (this->window->window == nullptr) {
this->window->mScreenConfiguration = configuration;
return;
}
auto lsConfiguration = LayerShellQt::Window::ScreenFromQWindow;
// clang-format off
switch (configuration) {
case ScreenConfiguration::Window: lsConfiguration = LayerShellQt::Window::ScreenFromQWindow; break;
case ScreenConfiguration::Compositor: lsConfiguration = LayerShellQt::Window::ScreenFromCompositor; break;
}
// clang-format on
this->window->shellWindow->setScreenConfiguration(lsConfiguration);
}
ScreenConfiguration::Enum WaylandShellWindowExtensions::screenConfiguration() const {
if (this->window->window == nullptr) return this->window->mScreenConfiguration;
auto configuration = ScreenConfiguration::Window;
auto lsConfiguration = this->window->shellWindow->screenConfiguration();
// clang-format off
switch (lsConfiguration) {
case LayerShellQt::Window::ScreenFromQWindow: configuration = ScreenConfiguration::Window; break;
case LayerShellQt::Window::ScreenFromCompositor: configuration = ScreenConfiguration::Compositor; break;
}
// clang-format on
return configuration;
}
void WaylandShellWindow::updateExclusionZone() {
if (this->window != nullptr && this->exclusionMode() == ExclusionMode::Auto) {
auto anchors = this->anchors();
auto zone = -1;
if (anchors.mTop && anchors.mBottom) {
if (anchors.mLeft) zone = this->width() + this->margins().mLeft;
else if (anchors.mRight) zone = this->width() + this->margins().mRight;
} else if (anchors.mLeft && anchors.mRight) {
if (anchors.mTop) zone = this->height() + this->margins().mTop;
else if (anchors.mBottom) zone = this->height() + this->margins().mBottom;
}
if (zone != -1) {
this->shellWindow->setExclusiveZone(zone);
emit this->exclusionZoneChanged();
}
}
}

View file

@ -1,144 +0,0 @@
#pragma once
#include <LayerShellQt/window.h>
#include <qobject.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "shellwindow.hpp"
namespace Layer { // NOLINT
Q_NAMESPACE;
QML_ELEMENT;
enum Enum {
Background = 0,
Bottom = 1,
Top = 2,
Overlay = 3,
};
Q_ENUM_NS(Enum);
} // namespace Layer
/// Type of keyboard focus that will be accepted by a [ShellWindow]
///
/// [ShellWindow]: ../shellwindow
namespace KeyboardFocus { // NOLINT
Q_NAMESPACE;
QML_ELEMENT;
enum Enum {
/// No keyboard input will be accepted.
None = 0,
/// Exclusive access to the keyboard, locking out all other windows.
Exclusive = 1,
/// Access to the keyboard as determined by the operating system.
///
/// > [!WARNING] On some systems, `OnDemand` may cause the shell window to
/// > retain focus over another window unexpectedly.
/// > You should try `None` if you experience issues.
OnDemand = 2,
};
Q_ENUM_NS(Enum);
} // namespace KeyboardFocus
namespace ScreenConfiguration { // NOLINT
Q_NAMESPACE;
QML_ELEMENT;
enum Enum {
Window = 0,
Compositor = 1,
};
Q_ENUM_NS(Enum);
} // namespace ScreenConfiguration
class WaylandShellWindowExtensions;
class WaylandShellWindow: public ProxyShellWindow {
Q_OBJECT;
Q_PROPERTY(WaylandShellWindowExtensions* wayland MEMBER mWayland CONSTANT);
QML_NAMED_ELEMENT(ShellWindow);
public:
explicit WaylandShellWindow(QObject* parent = nullptr);
WaylandShellWindowExtensions* wayland();
void setupWindow() override;
QQuickWindow* disownWindow() override;
void setWidth(qint32 width) override;
void setHeight(qint32 height) override;
void setAnchors(Anchors anchors) override;
[[nodiscard]] Anchors anchors() const override;
void setExclusiveZone(qint32 zone) override;
[[nodiscard]] qint32 exclusiveZone() const override;
void setExclusionMode(ExclusionMode::Enum exclusionMode) override;
[[nodiscard]] ExclusionMode::Enum exclusionMode() const override;
void setMargins(Margins margins) override;
[[nodiscard]] Margins margins() const override;
protected slots:
void updateExclusionZone();
private:
WaylandShellWindowExtensions* mWayland = nullptr;
LayerShellQt::Window* shellWindow = nullptr;
Layer::Enum mLayer = Layer::Top;
QString mScope;
KeyboardFocus::Enum mKeyboardFocus = KeyboardFocus::None;
ScreenConfiguration::Enum mScreenConfiguration = ScreenConfiguration::Window;
bool connected = false;
friend class WaylandShellWindowExtensions;
};
class WaylandShellWindowExtensions: public QObject {
Q_OBJECT;
/// The shell layer the window sits in. Defaults to `Layer.Top`.
Q_PROPERTY(Layer::Enum layer READ layer WRITE setLayer NOTIFY layerChanged);
Q_PROPERTY(QString scope READ scope WRITE setScope);
/// The degree of keyboard focus taken. Defaults to `KeyboardFocus.None`.
Q_PROPERTY(KeyboardFocus::Enum keyboardFocus READ keyboardFocus WRITE setKeyboardFocus NOTIFY
keyboardFocusChanged);
Q_PROPERTY(ScreenConfiguration::Enum screenConfiguration READ screenConfiguration WRITE
setScreenConfiguration);
QML_ELEMENT;
QML_UNCREATABLE("WaylandShellWindowExtensions cannot be created");
public:
explicit WaylandShellWindowExtensions(WaylandShellWindow* window):
QObject(window), window(window) {}
void setLayer(Layer::Enum layer);
[[nodiscard]] Layer::Enum layer() const;
void setScope(const QString& scope);
[[nodiscard]] QString scope() const;
void setKeyboardFocus(KeyboardFocus::Enum focus);
[[nodiscard]] KeyboardFocus::Enum keyboardFocus() const;
void setScreenConfiguration(ScreenConfiguration::Enum configuration);
[[nodiscard]] ScreenConfiguration::Enum screenConfiguration() const;
signals:
void layerChanged();
void keyboardFocusChanged();
private:
WaylandShellWindow* window;
friend class WaylandShellWindow;
};

View file

@ -10,10 +10,6 @@
#include "rootwrapper.hpp"
#ifdef CONF_LAYERSHELL
#include <LayerShellQt/shell.h>
#endif
int main(int argc, char** argv) {
const auto app = QGuiApplication(argc, argv);
QGuiApplication::setApplicationName("quickshell");
@ -42,10 +38,6 @@ int main(int argc, char** argv) {
return -1;
}
#if CONF_LAYERSHELL
LayerShellQt::Shell::useLayerShell();
#endif
// Base window transparency appears to be additive.
// Use a fully transparent window with a colored rect.
QQuickWindow::setDefaultAlphaBuffer(true);

View file

@ -154,10 +154,10 @@ signals:
void colorChanged();
void maskChanged();
private slots:
protected slots:
virtual void onWidthChanged();
virtual void onHeightChanged();
void onMaskChanged();
void onWidthChanged();
void onHeightChanged();
void onScreenDestroyed();
protected:

View file

@ -7,8 +7,9 @@
#include <qscreen.h>
#include <qtypes.h>
QuickShellScreenInfo::QuickShellScreenInfo(QObject* parent, QScreen* screen):
QObject(parent), screen(screen) {
QuickShellScreenInfo::QuickShellScreenInfo(QObject* parent, QScreen* screen)
: QObject(parent)
, screen(screen) {
if (this->screen != nullptr) {
// clang-format off

View file

@ -13,7 +13,9 @@
///
/// [ProxyWindowBase]: ../proxywindowbase
/// [PersistentProperties]: ../persistentproperties
class Reloadable: public QObject, public QQmlParserStatus {
class Reloadable
: public QObject
, public QQmlParserStatus {
Q_OBJECT;
Q_INTERFACES(QQmlParserStatus);
/// An additional identifier that can be used to try to match a reloadable object to its

View file

@ -12,8 +12,10 @@
#include "shell.hpp"
#include "watcher.hpp"
RootWrapper::RootWrapper(QString rootPath):
QObject(nullptr), rootPath(std::move(rootPath)), engine(this) {
RootWrapper::RootWrapper(QString rootPath)
: QObject(nullptr)
, rootPath(std::move(rootPath))
, engine(this) {
this->reloadGraph(true);
if (this->root == nullptr) {

View file

@ -20,6 +20,18 @@ class Anchors {
Q_PROPERTY(bool bottom MEMBER mBottom);
public:
[[nodiscard]] bool horizontalConstraint() const noexcept { return this->mLeft && this->mRight; }
[[nodiscard]] bool verticalConstraint() const noexcept { return this->mTop && this->mBottom; }
[[nodiscard]] bool operator==(const Anchors& other) const noexcept {
// clang-format off
return this->mLeft == other.mLeft
&& this->mRight == other.mRight
&& this->mTop == other.mTop
&& this->mBottom == other.mBottom;
// clang-format on
}
bool mLeft = false;
bool mRight = false;
bool mTop = false;
@ -34,6 +46,15 @@ class Margins {
Q_PROPERTY(qint32 bottom MEMBER mBottom);
public:
[[nodiscard]] bool operator==(const Margins& other) const noexcept {
// clang-format off
return this->mLeft == other.mLeft
&& this->mRight == other.mRight
&& this->mTop == other.mTop
&& this->mBottom == other.mBottom;
// clang-format on
}
qint32 mLeft = 0;
qint32 mRight = 0;
qint32 mTop = 0;