core/window: backing windows can now be destroyed and recreated

This fixes a crash in layershells and the setVisible crash on nvidia.
This commit is contained in:
outfoxxed 2024-03-27 00:44:13 -07:00
parent b6dc6967a1
commit 3a0381dcbe
Signed by: outfoxxed
GPG Key ID: 4C88A185FB89301E
16 changed files with 257 additions and 112 deletions

View File

@ -5,14 +5,18 @@ set(QT_MIN_VERSION "6.6.0")
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
option(TESTS "Build tests" OFF) option(BUILD_TESTING "Build tests" OFF)
option(ASAN "Enable ASAN" OFF)
option(FRAME_POINTERS "Always keep frame pointers" ${ASAN})
option(NVIDIA_COMPAT "Workarounds for nvidia gpus" OFF)
option(SOCKETS "Enable unix socket support" ON) option(SOCKETS "Enable unix socket support" ON)
option(WAYLAND "Enable wayland support" ON) option(WAYLAND "Enable wayland support" ON)
option(WAYLAND_WLR_LAYERSHELL "Support the zwlr_layer_shell_v1 wayland protocol" ON) option(WAYLAND_WLR_LAYERSHELL "Support the zwlr_layer_shell_v1 wayland protocol" ON)
option(WAYLAND_SESSION_LOCK "Support the ext_session_lock_v1 wayland protocol" ON) option(WAYLAND_SESSION_LOCK "Support the ext_session_lock_v1 wayland protocol" ON)
message(STATUS "Quickshell configuration") message(STATUS "Quickshell configuration")
message(STATUS " NVIDIA workarounds: ${NVIDIA_COMPAT}")
message(STATUS " Build tests: ${BUILD_TESTING}") message(STATUS " Build tests: ${BUILD_TESTING}")
message(STATUS " Sockets: ${SOCKETS}") message(STATUS " Sockets: ${SOCKETS}")
message(STATUS " Wayland: ${WAYLAND}") message(STATUS " Wayland: ${WAYLAND}")
@ -31,6 +35,15 @@ endif()
add_compile_options(-Wall -Wextra) add_compile_options(-Wall -Wextra)
if (FRAME_POINTERS)
add_compile_options(-fno-omit-frame-pointer)
endif()
if (ASAN)
add_compile_options(-fsanitize=address)
add_link_options(-fsanitize=address)
endif()
# nix workaround # nix workaround
if (CMAKE_EXPORT_COMPILE_COMMANDS) if (CMAKE_EXPORT_COMPILE_COMMANDS)
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
@ -92,4 +105,8 @@ function (qs_pch target)
endif() endif()
endfunction() endfunction()
if (NVIDIA_COMPAT)
add_compile_definitions(NVIDIA_COMPAT)
endif()
add_subdirectory(src) add_subdirectory(src)

View File

@ -47,6 +47,9 @@ This repo has a nix flake you can use to install the package directly:
Quickshell's binary is available at `quickshell.packages.<system>.default` to be added to Quickshell's binary is available at `quickshell.packages.<system>.default` to be added to
lists such as `environment.systemPackages` or `home.packages`. lists such as `environment.systemPackages` or `home.packages`.
`quickshell.packages.<system>.nvidia` is also available for nvidia users which fixes some
common crashes.
Note: by default this package is built with clang as it is significantly faster. Note: by default this package is built with clang as it is significantly faster.
## Manual ## Manual
@ -75,6 +78,15 @@ To make a release build of quickshell run:
$ just release $ just release
``` ```
If running an nvidia GPU, instead run:
```sh
$ just configure release -DNVIDIA_COMPAT=ON
$ just build
```
(These commands are just aliases for cmake commands you can run directly,
see the Justfile for more information.)
If you have all the dependencies installed and they are in expected If you have all the dependencies installed and they are in expected
locations this will build correctly. locations this will build correctly.

View File

@ -21,8 +21,10 @@
then builtins.readFile ./.git/refs/heads/${builtins.elemAt matches 0} then builtins.readFile ./.git/refs/heads/${builtins.elemAt matches 0}
else headContent) else headContent)
else "unknown"), else "unknown"),
debug ? false, debug ? false,
enableWayland ? true, enableWayland ? true,
nvidiaCompat ? false,
}: buildStdenv.mkDerivation { }: buildStdenv.mkDerivation {
pname = "quickshell${lib.optionalString debug "-debug"}"; pname = "quickshell${lib.optionalString debug "-debug"}";
version = "0.1.0"; version = "0.1.0";
@ -56,7 +58,8 @@
cmakeFlags = [ cmakeFlags = [
"-DGIT_REVISION=${gitRev}" "-DGIT_REVISION=${gitRev}"
] ++ lib.optional (!enableWayland) "-DWAYLAND=OFF"; ] ++ lib.optional (!enableWayland) "-DWAYLAND=OFF"
++ lib.optional nvidiaCompat "-DNVIDIA_COMPAT=ON";
buildPhase = "ninjaBuildPhase"; buildPhase = "ninjaBuildPhase";
enableParallelBuilding = true; enableParallelBuilding = true;

View File

@ -9,8 +9,11 @@
(system: fn system nixpkgs.legacyPackages.${system}); (system: fn system nixpkgs.legacyPackages.${system});
in { in {
packages = forEachSystem (system: pkgs: rec { packages = forEachSystem (system: pkgs: rec {
quickshell = import ./package.nix { inherit pkgs; }; quickshell = pkgs.callPackage ./default.nix {};
quickshell-nvidia = pkgs.callPackage ./default.nix { nvidiaCompat = true; };
default = quickshell; default = quickshell;
nvidia = quickshell-nvidia;
}); });
devShells = forEachSystem (system: pkgs: rec { devShells = forEachSystem (system: pkgs: rec {

View File

@ -1 +0,0 @@
{ pkgs ? import <nixpkgs> {}, ... }: pkgs.callPackage ./default.nix {}

View File

@ -122,6 +122,32 @@ void EngineGeneration::registerIncubationController(QQmlIncubationController* co
} }
} }
void EngineGeneration::deregisterIncubationController(QQmlIncubationController* controller) {
QObject* obj = nullptr;
this->incubationControllers.removeIf([&](QPair<QQmlIncubationController*, QObject*> other) {
if (controller == other.first) {
obj = other.second;
return true;
} else return false;
});
if (obj == nullptr) {
qCWarning(logIncubator) << "Failed to deregister incubation controller" << controller
<< "as it was not registered to begin with";
qCWarning(logIncubator) << "Current registered incuabation controllers"
<< this->incubationControllers;
} else {
QObject::disconnect(obj, nullptr, this, nullptr);
qCDebug(logIncubator) << "Deregistered incubation controller" << controller;
}
if (this->engine.incubationController() == controller) {
qCDebug(logIncubator
) << "Destroyed incubation controller was currently active, reassigning from pool";
this->assignIncubationController();
}
}
void EngineGeneration::incubationControllerDestroyed() { void EngineGeneration::incubationControllerDestroyed() {
auto* sender = this->sender(); auto* sender = this->sender();
QQmlIncubationController* controller = nullptr; QQmlIncubationController* controller = nullptr;
@ -150,8 +176,9 @@ void EngineGeneration::incubationControllerDestroyed() {
} }
void EngineGeneration::assignIncubationController() { void EngineGeneration::assignIncubationController() {
auto* controller = this->incubationControllers.first().first; QQmlIncubationController* controller = nullptr;
if (controller == nullptr) controller = &this->delayedIncubationController; if (this->incubationControllers.isEmpty()) controller = &this->delayedIncubationController;
else controller = this->incubationControllers.first().first;
qCDebug(logIncubator) << "Assigning incubation controller to engine:" << controller qCDebug(logIncubator) << "Assigning incubation controller to engine:" << controller
<< "fallback:" << (controller == &this->delayedIncubationController); << "fallback:" << (controller == &this->delayedIncubationController);
@ -162,9 +189,14 @@ void EngineGeneration::assignIncubationController() {
EngineGeneration* EngineGeneration::findObjectGeneration(QObject* object) { EngineGeneration* EngineGeneration::findObjectGeneration(QObject* object) {
while (object != nullptr) { while (object != nullptr) {
auto* context = QQmlEngine::contextForObject(object); auto* context = QQmlEngine::contextForObject(object);
if (auto* generation = g_generations.value(context->engine())) {
return generation; if (context != nullptr) {
if (auto* generation = g_generations.value(context->engine())) {
return generation;
}
} }
object = object->parent();
} }
return nullptr; return nullptr;

View File

@ -28,6 +28,7 @@ public:
void setWatchingFiles(bool watching); void setWatchingFiles(bool watching);
void registerIncubationController(QQmlIncubationController* controller); void registerIncubationController(QQmlIncubationController* controller);
void deregisterIncubationController(QQmlIncubationController* controller);
static EngineGeneration* findObjectGeneration(QObject* object); static EngineGeneration* findObjectGeneration(QObject* object);

View File

@ -15,15 +15,19 @@ ProxyPopupWindow::ProxyPopupWindow(QObject* parent): ProxyWindowBase(parent) {
this->mVisible = false; this->mVisible = false;
} }
void ProxyPopupWindow::setupWindow() { void ProxyPopupWindow::completeWindow() {
this->ProxyWindowBase::setupWindow(); this->ProxyWindowBase::completeWindow();
this->window->setFlag(Qt::ToolTip); this->window->setFlag(Qt::ToolTip);
this->updateTransientParent(); this->updateTransientParent();
} }
void ProxyPopupWindow::postCompleteWindow() {}
qint32 ProxyPopupWindow::x() const { qint32 ProxyPopupWindow::x() const {
return this->ProxyWindowBase::x() + 1; // QTBUG-121550 // QTBUG-121550
auto basepos = this->mParentProxyWindow == nullptr ? 0 : this->mParentProxyWindow->x();
return basepos + this->mRelativeX;
} }
void ProxyPopupWindow::setParentWindow(QObject* parent) { void ProxyPopupWindow::setParentWindow(QObject* parent) {
@ -58,7 +62,7 @@ void ProxyPopupWindow::setParentWindow(QObject* parent) {
QObject::connect(this->mParentProxyWindow, &ProxyWindowBase::xChanged, this, &ProxyPopupWindow::updateX); QObject::connect(this->mParentProxyWindow, &ProxyWindowBase::xChanged, this, &ProxyPopupWindow::updateX);
QObject::connect(this->mParentProxyWindow, &ProxyWindowBase::yChanged, this, &ProxyPopupWindow::updateY); QObject::connect(this->mParentProxyWindow, &ProxyWindowBase::yChanged, this, &ProxyPopupWindow::updateY);
QObject::connect(this->mParentProxyWindow, &ProxyWindowBase::windowConnected, this, &ProxyPopupWindow::onParentConnected); QObject::connect(this->mParentProxyWindow, &ProxyWindowBase::backerVisibilityChanged, this, &ProxyPopupWindow::onParentUpdated);
// clang-format on // clang-format on
} }
@ -68,18 +72,19 @@ void ProxyPopupWindow::setParentWindow(QObject* parent) {
QObject* ProxyPopupWindow::parentWindow() const { return this->mParentWindow; } QObject* ProxyPopupWindow::parentWindow() const { return this->mParentWindow; }
void ProxyPopupWindow::updateTransientParent() { void ProxyPopupWindow::updateTransientParent() {
if (this->window == nullptr) return;
this->updateX(); this->updateX();
this->updateY(); this->updateY();
this->window->setTransientParent( if (this->window != nullptr) {
this->mParentProxyWindow == nullptr ? nullptr : this->mParentProxyWindow->backingWindow() this->window->setTransientParent(
); this->mParentProxyWindow == nullptr ? nullptr : this->mParentProxyWindow->backingWindow()
);
}
this->updateVisible(); this->updateVisible();
} }
void ProxyPopupWindow::onParentConnected() { this->updateTransientParent(); } void ProxyPopupWindow::onParentUpdated() { this->updateTransientParent(); }
void ProxyPopupWindow::onParentDestroyed() { void ProxyPopupWindow::onParentDestroyed() {
this->mParentWindow = nullptr; this->mParentWindow = nullptr;
@ -99,7 +104,8 @@ void ProxyPopupWindow::setVisible(bool visible) {
} }
void ProxyPopupWindow::updateVisible() { void ProxyPopupWindow::updateVisible() {
auto target = this->wantsVisible && this->mParentWindow != nullptr; auto target = this->wantsVisible && this->mParentWindow != nullptr
&& this->mParentProxyWindow->isVisibleDirect();
if (target && this->window != nullptr && !this->window->isVisible()) { if (target && this->window != nullptr && !this->window->isVisible()) {
this->updateX(); // QTBUG-121550 this->updateX(); // QTBUG-121550
@ -127,13 +133,12 @@ qint32 ProxyPopupWindow::relativeY() const { return this->mRelativeY; }
void ProxyPopupWindow::updateX() { void ProxyPopupWindow::updateX() {
if (this->mParentWindow == nullptr || this->window == nullptr) return; if (this->mParentWindow == nullptr || this->window == nullptr) return;
// use the backing window's x to account for popups in popups with overridden x positions auto target = this->x() - 1; // QTBUG-121550
auto target = this->mParentProxyWindow->backingWindow()->x() + this->relativeX();
auto reshow = this->window->isVisible() && (this->window->x() != target && this->x() != target); auto reshow = this->isVisibleDirect() && (this->window->x() != target && this->x() != target);
if (reshow) this->window->setVisible(false); if (reshow) this->setVisibleDirect(false);
this->window->setX(target - 1); // -1 due to QTBUG-121550 if (this->window != nullptr) this->window->setX(target);
if (reshow && this->wantsVisible) this->window->setVisible(true); if (reshow && this->wantsVisible) this->setVisibleDirect(true);
} }
void ProxyPopupWindow::updateY() { void ProxyPopupWindow::updateY() {
@ -141,11 +146,11 @@ void ProxyPopupWindow::updateY() {
auto target = this->mParentProxyWindow->y() + this->relativeY(); auto target = this->mParentProxyWindow->y() + this->relativeY();
auto reshow = this->window->isVisible() && this->window->y() != target; auto reshow = this->isVisibleDirect() && this->window->y() != target;
if (reshow) { if (reshow) {
this->window->setVisible(false); this->setVisibleDirect(false);
this->updateX(); // QTBUG-121550 this->updateX(); // QTBUG-121550
} }
this->window->setY(target); if (this->window != nullptr) this->window->setY(target);
if (reshow && this->wantsVisible) this->window->setVisible(true); if (reshow && this->wantsVisible) this->setVisibleDirect(true);
} }

View File

@ -62,7 +62,8 @@ class ProxyPopupWindow: public ProxyWindowBase {
public: public:
explicit ProxyPopupWindow(QObject* parent = nullptr); explicit ProxyPopupWindow(QObject* parent = nullptr);
void setupWindow() override; void completeWindow() override;
void postCompleteWindow() override;
void setScreen(QuickshellScreenInfo* screen) override; void setScreen(QuickshellScreenInfo* screen) override;
void setVisible(bool visible) override; void setVisible(bool visible) override;
@ -84,7 +85,7 @@ signals:
void relativeYChanged(); void relativeYChanged();
private slots: private slots:
void onParentConnected(); void onParentUpdated();
void onParentDestroyed(); void onParentDestroyed();
void updateX(); void updateX();
void updateY(); void updateY();

View File

@ -23,51 +23,81 @@ ProxyWindowBase::ProxyWindowBase(QObject* parent)
QQmlEngine::setObjectOwnership(this->mContentItem, QQmlEngine::CppOwnership); QQmlEngine::setObjectOwnership(this->mContentItem, QQmlEngine::CppOwnership);
this->mContentItem->setParent(this); this->mContentItem->setParent(this);
// clang-format off
QObject::connect(this, &ProxyWindowBase::widthChanged, this, &ProxyWindowBase::onWidthChanged); QObject::connect(this, &ProxyWindowBase::widthChanged, this, &ProxyWindowBase::onWidthChanged);
QObject::connect(this, &ProxyWindowBase::heightChanged, this, &ProxyWindowBase::onHeightChanged); QObject::connect(this, &ProxyWindowBase::heightChanged, this, &ProxyWindowBase::onHeightChanged);
QObject::connect(this, &ProxyWindowBase::maskChanged, this, &ProxyWindowBase::onMaskChanged);
QObject::connect(this, &ProxyWindowBase::widthChanged, this, &ProxyWindowBase::onMaskChanged);
QObject::connect(this, &ProxyWindowBase::heightChanged, this, &ProxyWindowBase::onMaskChanged);
// clang-format on
} }
ProxyWindowBase::~ProxyWindowBase() { ProxyWindowBase::~ProxyWindowBase() { this->deleteWindow(); }
if (this->window != nullptr) {
this->window->deleteLater(); void ProxyWindowBase::onReload(QObject* oldInstance) {
this->window = this->retrieveWindow(oldInstance);
auto wasVisible = this->window != nullptr && this->window->isVisible();
if (this->window == nullptr) this->window = new QQuickWindow();
Reloadable::reloadRecursive(this->mContentItem, oldInstance);
this->connectWindow();
this->completeWindow();
emit this->windowConnected();
this->postCompleteWindow();
if (wasVisible && this->isVisibleDirect()) emit this->backerVisibilityChanged();
}
void ProxyWindowBase::postCompleteWindow() { this->setVisible(this->mVisible); }
QQuickWindow* ProxyWindowBase::createQQuickWindow() { return new QQuickWindow(); }
void ProxyWindowBase::createWindow() {
if (this->window != nullptr) return;
this->window = this->createQQuickWindow();
this->connectWindow();
this->completeWindow();
emit this->windowConnected();
}
void ProxyWindowBase::deleteWindow() {
if (auto* window = this->disownWindow()) {
if (auto* generation = EngineGeneration::findObjectGeneration(this)) {
generation->deregisterIncubationController(window->incubationController());
}
window->deleteLater();
} }
} }
void ProxyWindowBase::onReload(QObject* oldInstance) { QQuickWindow* ProxyWindowBase::disownWindow() {
this->window = this->createWindow(oldInstance); if (this->window == nullptr) return nullptr;
QObject::disconnect(this->window, nullptr, this, nullptr);
this->mContentItem->setParentItem(nullptr);
auto* window = this->window;
this->window = nullptr;
return window;
}
QQuickWindow* ProxyWindowBase::retrieveWindow(QObject* oldInstance) {
auto* old = qobject_cast<ProxyWindowBase*>(oldInstance);
return old == nullptr ? nullptr : old->disownWindow();
}
void ProxyWindowBase::connectWindow() {
if (auto* generation = EngineGeneration::findObjectGeneration(this)) { if (auto* generation = EngineGeneration::findObjectGeneration(this)) {
// All windows have effectively the same incubation controller so it dosen't matter // All windows have effectively the same incubation controller so it dosen't matter
// which window it belongs to. We do want to replace the delay one though. // which window it belongs to. We do want to replace the delay one though.
generation->registerIncubationController(this->window->incubationController()); generation->registerIncubationController(this->window->incubationController());
} }
this->setupWindow();
Reloadable::reloadRecursive(this->mContentItem, oldInstance);
this->mContentItem->setParentItem(this->window->contentItem());
this->mContentItem->setWidth(this->width());
this->mContentItem->setHeight(this->height());
// without this the dangling screen pointer wont be updated to a real screen
emit this->screenChanged();
emit this->windowConnected();
this->window->setVisible(this->mVisible);
}
QQuickWindow* ProxyWindowBase::createWindow(QObject* oldInstance) {
auto* old = qobject_cast<ProxyWindowBase*>(oldInstance);
if (old == nullptr || old->window == nullptr) {
return new QQuickWindow();
} else {
return old->disownWindow();
}
}
void ProxyWindowBase::setupWindow() {
// clang-format off // clang-format off
QObject::connect(this->window, &QWindow::visibilityChanged, this, &ProxyWindowBase::visibleChanged); QObject::connect(this->window, &QWindow::visibilityChanged, this, &ProxyWindowBase::visibleChanged);
QObject::connect(this->window, &QWindow::xChanged, this, &ProxyWindowBase::xChanged); QObject::connect(this->window, &QWindow::xChanged, this, &ProxyWindowBase::xChanged);
@ -76,12 +106,10 @@ void ProxyWindowBase::setupWindow() {
QObject::connect(this->window, &QWindow::heightChanged, this, &ProxyWindowBase::heightChanged); QObject::connect(this->window, &QWindow::heightChanged, this, &ProxyWindowBase::heightChanged);
QObject::connect(this->window, &QWindow::screenChanged, this, &ProxyWindowBase::screenChanged); QObject::connect(this->window, &QWindow::screenChanged, this, &ProxyWindowBase::screenChanged);
QObject::connect(this->window, &QQuickWindow::colorChanged, this, &ProxyWindowBase::colorChanged); QObject::connect(this->window, &QQuickWindow::colorChanged, this, &ProxyWindowBase::colorChanged);
QObject::connect(this, &ProxyWindowBase::maskChanged, this, &ProxyWindowBase::onMaskChanged);
QObject::connect(this, &ProxyWindowBase::widthChanged, this, &ProxyWindowBase::onMaskChanged);
QObject::connect(this, &ProxyWindowBase::heightChanged, this, &ProxyWindowBase::onMaskChanged);
// clang-format on // clang-format on
}
void ProxyWindowBase::completeWindow() {
if (this->mScreen != nullptr && this->window->screen() != this->mScreen) { if (this->mScreen != nullptr && this->window->screen() != this->mScreen) {
if (this->window->isVisible()) this->window->setVisible(false); if (this->window->isVisible()) this->window->setVisible(false);
this->window->setScreen(this->mScreen); this->window->setScreen(this->mScreen);
@ -95,16 +123,24 @@ void ProxyWindowBase::setupWindow() {
// notify initial x and y positions // notify initial x and y positions
emit this->xChanged(); emit this->xChanged();
emit this->yChanged(); emit this->yChanged();
this->mContentItem->setParentItem(this->window->contentItem());
this->mContentItem->setWidth(this->width());
this->mContentItem->setHeight(this->height());
// without this the dangling screen pointer wont be updated to a real screen
emit this->screenChanged();
} }
QQuickWindow* ProxyWindowBase::disownWindow() { bool ProxyWindowBase::deleteOnInvisible() const {
QObject::disconnect(this->window, nullptr, this, nullptr); #ifdef NVIDIA_COMPAT
// Nvidia drivers and Qt do not play nice when hiding and showing a window
this->mContentItem->setParentItem(nullptr); // so for nvidia compatibility we can never reuse windows if they have been
// hidden.
auto* window = this->window; return true;
this->window = nullptr; #else
return window; return false;
#endif
} }
QQuickWindow* ProxyWindowBase::backingWindow() const { return this->window; } QQuickWindow* ProxyWindowBase::backingWindow() const { return this->window; }
@ -112,14 +148,38 @@ QQuickItem* ProxyWindowBase::contentItem() const { return this->mContentItem; }
bool ProxyWindowBase::isVisible() const { bool ProxyWindowBase::isVisible() const {
if (this->window == nullptr) return this->mVisible; if (this->window == nullptr) return this->mVisible;
else return this->isVisibleDirect();
}
bool ProxyWindowBase::isVisibleDirect() const {
if (this->window == nullptr) return false;
else return this->window->isVisible(); else return this->window->isVisible();
} }
void ProxyWindowBase::setVisible(bool visible) { void ProxyWindowBase::setVisible(bool visible) {
if (this->window == nullptr) { this->mVisible = visible;
this->mVisible = visible; this->setVisibleDirect(visible);
emit this->visibleChanged(); }
} else this->window->setVisible(visible);
void ProxyWindowBase::setVisibleDirect(bool visible) {
if (this->deleteOnInvisible()) {
if (visible == this->isVisibleDirect()) return;
if (visible) {
this->createWindow();
this->window->setVisible(true);
emit this->backerVisibilityChanged();
} else {
if (this->window != nullptr) {
this->window->setVisible(false);
emit this->backerVisibilityChanged();
this->deleteWindow();
}
}
} else if (this->window != nullptr) {
this->window->setVisible(visible);
emit this->backerVisibilityChanged();
}
} }
qint32 ProxyWindowBase::x() const { qint32 ProxyWindowBase::x() const {
@ -138,8 +198,8 @@ qint32 ProxyWindowBase::width() const {
} }
void ProxyWindowBase::setWidth(qint32 width) { void ProxyWindowBase::setWidth(qint32 width) {
this->mWidth = width;
if (this->window == nullptr) { if (this->window == nullptr) {
this->mWidth = width;
emit this->widthChanged(); emit this->widthChanged();
} else this->window->setWidth(width); } else this->window->setWidth(width);
} }
@ -150,8 +210,8 @@ qint32 ProxyWindowBase::height() const {
} }
void ProxyWindowBase::setHeight(qint32 height) { void ProxyWindowBase::setHeight(qint32 height) {
this->mHeight = height;
if (this->window == nullptr) { if (this->window == nullptr) {
this->mHeight = height;
emit this->heightChanged(); emit this->heightChanged();
} else this->window->setHeight(height); } else this->window->setHeight(height);
} }
@ -170,10 +230,10 @@ void ProxyWindowBase::setScreen(QuickshellScreenInfo* screen) {
this->mScreen = qscreen; this->mScreen = qscreen;
emit this->screenChanged(); emit this->screenChanged();
} else { } else {
auto reshow = this->window->isVisible(); auto reshow = this->isVisibleDirect();
if (reshow) this->window->setVisible(false); if (reshow) this->setVisibleDirect(false);
this->window->setScreen(qscreen); if (this->window != nullptr) this->window->setScreen(qscreen);
if (reshow) this->window->setVisible(true); if (reshow) this->setVisibleDirect(true);
} }
} }

View File

@ -12,6 +12,7 @@
#include <qtmetamacros.h> #include <qtmetamacros.h>
#include <qtypes.h> #include <qtypes.h>
#include "qmlglobal.hpp"
#include "qmlscreen.hpp" #include "qmlscreen.hpp"
#include "region.hpp" #include "region.hpp"
#include "reload.hpp" #include "reload.hpp"
@ -54,18 +55,26 @@ public:
void operator=(ProxyWindowBase&&) = delete; void operator=(ProxyWindowBase&&) = delete;
void onReload(QObject* oldInstance) override; void onReload(QObject* oldInstance) override;
void createWindow();
virtual QQuickWindow* createWindow(QObject* oldInstance); void deleteWindow();
virtual void setupWindow();
// Disown the backing window and delete all its children. // Disown the backing window and delete all its children.
virtual QQuickWindow* disownWindow(); virtual QQuickWindow* disownWindow();
virtual QQuickWindow* retrieveWindow(QObject* oldInstance);
virtual QQuickWindow* createQQuickWindow();
virtual void connectWindow();
virtual void completeWindow();
virtual void postCompleteWindow();
[[nodiscard]] virtual bool deleteOnInvisible() const;
[[nodiscard]] QQuickWindow* backingWindow() const; [[nodiscard]] QQuickWindow* backingWindow() const;
[[nodiscard]] QQuickItem* contentItem() const; [[nodiscard]] QQuickItem* contentItem() const;
[[nodiscard]] virtual bool isVisible() const; [[nodiscard]] virtual bool isVisible() const;
[[nodiscard]] virtual bool isVisibleDirect() const;
virtual void setVisible(bool visible); virtual void setVisible(bool visible);
virtual void setVisibleDirect(bool visible);
[[nodiscard]] virtual qint32 x() const; [[nodiscard]] virtual qint32 x() const;
[[nodiscard]] virtual qint32 y() const; [[nodiscard]] virtual qint32 y() const;
@ -90,10 +99,12 @@ public:
signals: signals:
void windowConnected(); void windowConnected();
void visibleChanged(); void visibleChanged();
void backerVisibilityChanged();
void xChanged(); void xChanged();
void yChanged(); void yChanged();
void widthChanged(); void widthChanged();
void heightChanged(); void heightChanged();
void windowTransformChanged();
void screenChanged(); void screenChanged();
void colorChanged(); void colorChanged();
void maskChanged(); void maskChanged();

View File

@ -1,15 +1,7 @@
function (qs_test name) function (qs_test name)
add_executable(${name} ${ARGN}) add_executable(${name} ${ARGN})
target_link_libraries(${name} PRIVATE ${QT_DEPS} Qt6::Test) target_link_libraries(${name} PRIVATE ${QT_DEPS} Qt6::Test quickshell-core)
add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $<TARGET_FILE:${name}>) add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $<TARGET_FILE:${name}>)
endfunction() endfunction()
qs_test(popupwindow qs_test(popupwindow popupwindow.cpp)
popupwindow.cpp
../popupwindow.cpp
../proxywindow.cpp
../qmlscreen.cpp
../region.cpp
../reload.cpp
../windowinterface.cpp
)

View File

@ -1,6 +1,5 @@
#include "popupwindow.hpp" #include "popupwindow.hpp"
#include <qlogging.h>
#include <qquickwindow.h> #include <qquickwindow.h>
#include <qsignalspy.h> #include <qsignalspy.h>
#include <qtest.h> #include <qtest.h>
@ -52,7 +51,6 @@ void TestPopupWindow::reloadReparent() { // NOLINT
auto spy = QSignalSpy(oldWindow, &QWindow::visibleChanged); auto spy = QSignalSpy(oldWindow, &QWindow::visibleChanged);
qDebug() << "reload";
newParent.onReload(&parent); newParent.onReload(&parent);
newPopup.onReload(&popup); newPopup.onReload(&popup);

View File

@ -18,33 +18,34 @@ WlrLayershell::WlrLayershell(QObject* parent)
: ProxyWindowBase(parent) : ProxyWindowBase(parent)
, ext(new LayershellWindowExtension(this)) {} , ext(new LayershellWindowExtension(this)) {}
QQuickWindow* WlrLayershell::createWindow(QObject* oldInstance) { QQuickWindow* WlrLayershell::retrieveWindow(QObject* oldInstance) {
auto* old = qobject_cast<WlrLayershell*>(oldInstance); auto* old = qobject_cast<WlrLayershell*>(oldInstance);
QQuickWindow* window = nullptr; auto* window = old == nullptr ? nullptr : old->disownWindow();
if (old == nullptr || old->window == nullptr) {
window = new QQuickWindow();
} else {
window = old->disownWindow();
if (window != nullptr) {
if (this->ext->attach(window)) { if (this->ext->attach(window)) {
return window; return window;
} else { } else {
window->deleteLater(); window->deleteLater();
window = new QQuickWindow();
} }
} }
return this->createQQuickWindow();
}
QQuickWindow* WlrLayershell::createQQuickWindow() {
auto* window = new QQuickWindow();
if (!this->ext->attach(window)) { if (!this->ext->attach(window)) {
qWarning() << "Could not attach Layershell extension to new QQUickWindow. Layer will not " qWarning() << "Could not attach Layershell extension to new QQuickWindow. Layer will not "
"behave correctly."; "behave correctly.";
} }
return window; return window;
} }
void WlrLayershell::setupWindow() { void WlrLayershell::connectWindow() {
this->ProxyWindowBase::setupWindow(); this->ProxyWindowBase::connectWindow();
// clang-format off // clang-format off
QObject::connect(this->ext, &LayershellWindowExtension::layerChanged, this, &WlrLayershell::layerChanged); QObject::connect(this->ext, &LayershellWindowExtension::layerChanged, this, &WlrLayershell::layerChanged);
@ -61,6 +62,15 @@ void WlrLayershell::setupWindow() {
this->updateAutoExclusion(); this->updateAutoExclusion();
} }
bool WlrLayershell::deleteOnInvisible() const {
// Qt windows behave weirdly when geometry is modified and setVisible(false)
// is subsequently called in the same frame.
// It will attach buffers to the wayland surface unconditionally before
// the surface recieves a configure event, causing a protocol error.
// To remedy this we forcibly disallow window reuse.
return true;
}
void WlrLayershell::setWidth(qint32 width) { void WlrLayershell::setWidth(qint32 width) {
this->mWidth = width; this->mWidth = width;

View File

@ -61,8 +61,10 @@ class WlrLayershell: public ProxyWindowBase {
public: public:
explicit WlrLayershell(QObject* parent = nullptr); explicit WlrLayershell(QObject* parent = nullptr);
QQuickWindow* createWindow(QObject* oldInstance) override; QQuickWindow* retrieveWindow(QObject* oldInstance) override;
void setupWindow() override; QQuickWindow* createQQuickWindow() override;
void connectWindow() override;
[[nodiscard]] bool deleteOnInvisible() const override;
void setWidth(qint32 width) override; void setWidth(qint32 width) override;
void setHeight(qint32 height) override; void setHeight(qint32 height) override;

View File

@ -78,7 +78,6 @@ bool LayershellWindowExtension::attach(QWindow* window) {
waylandWindow->setShellIntegration(layershellIntegration); waylandWindow->setShellIntegration(layershellIntegration);
} }
this->setParent(window);
window->setProperty("layershell_ext", QVariant::fromValue(this)); window->setProperty("layershell_ext", QVariant::fromValue(this));
return true; return true;
} }