all: refactor windows code out of core

There are still some links from core to window but its now separate
enough to fix PanelWindow in qml tooling.
This commit is contained in:
outfoxxed 2024-10-28 16:18:41 -07:00
parent 1adad9e822
commit 4e48c6eefb
Signed by untrusted user: outfoxxed
GPG key ID: 4C88A185FB89301E
45 changed files with 1171 additions and 1142 deletions

View file

@ -1,22 +1,16 @@
find_package(CLI11 CONFIG REQUIRED)
qt_add_library(quickshell-core STATIC
main.cpp
plugin.cpp
shell.cpp
variants.cpp
rootwrapper.cpp
proxywindow.cpp
reload.cpp
rootwrapper.cpp
qmlglobal.cpp
qmlscreen.cpp
region.cpp
persistentprops.cpp
windowinterface.cpp
floatingwindow.cpp
panelinterface.cpp
popupwindow.cpp
singleton.cpp
generation.cpp
scan.cpp
@ -43,32 +37,20 @@ qt_add_library(quickshell-core STATIC
paths.cpp
instanceinfo.cpp
common.cpp
ipc.cpp
)
if (CRASH_REPORTER)
set(CRASH_REPORTER_DEF 1)
else()
set(CRASH_REPORTER_DEF 0)
endif()
if (DISTRIBUTOR_DEBUGINFO_AVAILABLE)
set(DEBUGINFO_AVAILABLE 1)
else()
set(DEBUGINFO_AVAILABLE 0)
endif()
add_library(quickshell-build INTERFACE)
configure_file(build.hpp.in build.hpp @ONLY ESCAPE_QUOTES)
target_include_directories(quickshell-build INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(quickshell-core PRIVATE quickshell-build)
qt_add_qml_module(quickshell-core URI Quickshell VERSION 0.1)
qt_add_qml_module(quickshell-core
URI Quickshell
VERSION 0.1
IMPORTS Quickshell._Window
)
target_link_libraries(quickshell-core PRIVATE ${QT_DEPS} CLI11::CLI11)
target_link_libraries(quickshell-core PRIVATE ${QT_DEPS} Qt6::QuickPrivate CLI11::CLI11)
qs_pch(quickshell-core)
qs_pch(quickshell-coreplugin)
target_link_libraries(quickshell PRIVATE quickshell-coreplugin)

View file

@ -1,12 +0,0 @@
#pragma once
// NOLINTBEGIN
#define GIT_REVISION "@GIT_REVISION@"
#define DISTRIBUTOR "@DISTRIBUTOR@"
#define DISTRIBUTOR_DEBUGINFO_AVAILABLE @DEBUGINFO_AVAILABLE@
#define CRASH_REPORTER @CRASH_REPORTER_DEF@
#define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
#define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)"
#define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@"
#define BUILD_CONFIGURATION "@QS_BUILD_OPTIONS@"
// NOLINTEND

View file

@ -1,69 +0,0 @@
#include "floatingwindow.hpp"
#include <qobject.h>
#include <qqmlengine.h>
#include <qqmllist.h>
#include <qquickitem.h>
#include <qtypes.h>
#include "proxywindow.hpp"
#include "windowinterface.hpp"
void ProxyFloatingWindow::setWidth(qint32 width) {
if (this->window == nullptr || !this->window->isVisible()) {
this->ProxyWindowBase::setWidth(width);
}
}
void ProxyFloatingWindow::setHeight(qint32 height) {
if (this->window == nullptr || !this->window->isVisible()) {
this->ProxyWindowBase::setHeight(height);
}
}
// FloatingWindowInterface
FloatingWindowInterface::FloatingWindowInterface(QObject* parent)
: WindowInterface(parent)
, window(new ProxyFloatingWindow(this)) {
// clang-format off
QObject::connect(this->window, &ProxyWindowBase::windowConnected, this, &FloatingWindowInterface::windowConnected);
QObject::connect(this->window, &ProxyWindowBase::visibleChanged, this, &FloatingWindowInterface::visibleChanged);
QObject::connect(this->window, &ProxyWindowBase::backerVisibilityChanged, this, &FloatingWindowInterface::backingWindowVisibleChanged);
QObject::connect(this->window, &ProxyWindowBase::heightChanged, this, &FloatingWindowInterface::heightChanged);
QObject::connect(this->window, &ProxyWindowBase::widthChanged, this, &FloatingWindowInterface::widthChanged);
QObject::connect(this->window, &ProxyWindowBase::screenChanged, this, &FloatingWindowInterface::screenChanged);
QObject::connect(this->window, &ProxyWindowBase::windowTransformChanged, this, &FloatingWindowInterface::windowTransformChanged);
QObject::connect(this->window, &ProxyWindowBase::colorChanged, this, &FloatingWindowInterface::colorChanged);
QObject::connect(this->window, &ProxyWindowBase::maskChanged, this, &FloatingWindowInterface::maskChanged);
// clang-format on
}
void FloatingWindowInterface::onReload(QObject* oldInstance) {
QQmlEngine::setContextForObject(this->window, QQmlEngine::contextForObject(this));
auto* old = qobject_cast<FloatingWindowInterface*>(oldInstance);
this->window->reload(old != nullptr ? old->window : nullptr);
}
QQmlListProperty<QObject> FloatingWindowInterface::data() { return this->window->data(); }
ProxyWindowBase* FloatingWindowInterface::proxyWindow() const { return this->window; }
QQuickItem* FloatingWindowInterface::contentItem() const { return this->window->contentItem(); }
bool FloatingWindowInterface::isBackingWindowVisible() const {
return this->window->isVisibleDirect();
}
// NOLINTBEGIN
#define proxyPair(type, get, set) \
type FloatingWindowInterface::get() const { return this->window->get(); } \
void FloatingWindowInterface::set(type value) { this->window->set(value); }
proxyPair(bool, isVisible, setVisible);
proxyPair(qint32, width, setWidth);
proxyPair(qint32, height, setHeight);
proxyPair(QuickshellScreenInfo*, screen, setScreen);
proxyPair(QColor, color, setColor);
proxyPair(PendingRegion*, mask, setMask);
#undef proxyPair
// NOLINTEND

View file

@ -1,58 +0,0 @@
#pragma once
#include <qobject.h>
#include <qtmetamacros.h>
#include "proxywindow.hpp"
class ProxyFloatingWindow: public ProxyWindowBase {
Q_OBJECT;
public:
explicit ProxyFloatingWindow(QObject* parent = nullptr): ProxyWindowBase(parent) {}
// Setting geometry while the window is visible makes the content item shrinks but not the window
// which is awful so we disable it for floating windows.
void setWidth(qint32 width) override;
void setHeight(qint32 height) override;
};
///! Standard toplevel operating system window that looks like any other application.
class FloatingWindowInterface: public WindowInterface {
Q_OBJECT;
QML_NAMED_ELEMENT(FloatingWindow);
public:
explicit FloatingWindowInterface(QObject* parent = nullptr);
void onReload(QObject* oldInstance) override;
[[nodiscard]] ProxyWindowBase* proxyWindow() const override;
[[nodiscard]] QQuickItem* contentItem() const override;
// NOLINTBEGIN
[[nodiscard]] bool isVisible() const override;
[[nodiscard]] bool isBackingWindowVisible() const override;
void setVisible(bool visible) override;
[[nodiscard]] qint32 width() const override;
void setWidth(qint32 width) override;
[[nodiscard]] qint32 height() const override;
void setHeight(qint32 height) override;
[[nodiscard]] QuickshellScreenInfo* screen() const override;
void setScreen(QuickshellScreenInfo* screen) override;
[[nodiscard]] QColor color() const override;
void setColor(QColor color) override;
[[nodiscard]] PendingRegion* mask() const override;
void setMask(PendingRegion* mask) override;
[[nodiscard]] QQmlListProperty<QObject> data() override;
// NOLINTEND
private:
ProxyFloatingWindow* window;
};

View file

@ -1,125 +0,0 @@
#include "ipc.hpp"
#include <functional>
#include <variant>
#include <qbuffer.h>
#include <qlocalserver.h>
#include <qlocalsocket.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include "generation.hpp"
#include "ipccommand.hpp"
#include "paths.hpp"
namespace qs::ipc {
Q_LOGGING_CATEGORY(logIpc, "quickshell.ipc", QtWarningMsg);
IpcServer::IpcServer(const QString& path) {
QObject::connect(&this->server, &QLocalServer::newConnection, this, &IpcServer::onNewConnection);
QLocalServer::removeServer(path);
if (!this->server.listen(path)) {
qCCritical(logIpc) << "Failed to start IPC server on path" << path;
return;
}
qCInfo(logIpc) << "Started IPC server on path" << path;
}
void IpcServer::start() {
if (auto* run = QsPaths::instance()->instanceRunDir()) {
auto path = run->filePath("ipc.sock");
new IpcServer(path);
} else {
qCCritical(logIpc
) << "Could not start IPC server as the instance runtime path could not be created.";
}
}
void IpcServer::onNewConnection() {
while (auto* connection = this->server.nextPendingConnection()) {
new IpcServerConnection(connection, this);
}
}
IpcServerConnection::IpcServerConnection(QLocalSocket* socket, IpcServer* server)
: QObject(server)
, socket(socket) {
socket->setParent(this);
this->stream.setDevice(socket);
QObject::connect(socket, &QLocalSocket::disconnected, this, &IpcServerConnection::onDisconnected);
QObject::connect(socket, &QLocalSocket::readyRead, this, &IpcServerConnection::onReadyRead);
qCInfo(logIpc) << "New IPC connection" << this;
}
void IpcServerConnection::onDisconnected() {
qCInfo(logIpc) << "IPC connection disconnected" << this;
}
void IpcServerConnection::onReadyRead() {
this->stream.startTransaction();
this->stream.startTransaction();
IpcCommand command;
this->stream >> command;
if (!this->stream.commitTransaction()) return;
std::visit(
[this]<typename Command>(Command& command) {
if constexpr (std::is_same_v<std::monostate, Command>) {
qCCritical(logIpc) << "Received invalid IPC command from" << this;
this->socket->disconnectFromServer();
} else {
command.exec(this);
}
},
command
);
if (!this->stream.commitTransaction()) return;
}
IpcClient::IpcClient(const QString& path) {
QObject::connect(&this->socket, &QLocalSocket::connected, this, &IpcClient::connected);
QObject::connect(&this->socket, &QLocalSocket::disconnected, this, &IpcClient::disconnected);
QObject::connect(&this->socket, &QLocalSocket::errorOccurred, this, &IpcClient::onError);
this->socket.connectToServer(path);
this->stream.setDevice(&this->socket);
}
bool IpcClient::isConnected() const { return this->socket.isValid(); }
void IpcClient::waitForConnected() { this->socket.waitForConnected(); }
void IpcClient::waitForDisconnected() { this->socket.waitForDisconnected(); }
void IpcClient::kill() { this->sendMessage(IpcCommand(IpcKillCommand())); }
void IpcClient::onError(QLocalSocket::LocalSocketError error) {
qCCritical(logIpc) << "Socket Error" << error;
}
int IpcClient::connect(const QString& id, const std::function<void(IpcClient& client)>& callback) {
auto path = QsPaths::ipcPath(id);
auto client = IpcClient(path);
qCDebug(logIpc) << "Connecting to instance" << id << "at" << path;
client.waitForConnected();
if (!client.isConnected()) return -1;
qCDebug(logIpc) << "Connected.";
callback(client);
return 0;
}
void IpcKillCommand::exec(IpcServerConnection* /*unused*/) {
qInfo() << "Exiting due to IPC request.";
EngineGeneration::currentGeneration()->quit();
}
} // namespace qs::ipc

View file

@ -1,220 +0,0 @@
#pragma once
#include <cmath>
#include <functional>
#include <limits>
#include <utility>
#include <variant>
#include <qflags.h>
#include <qlocalserver.h>
#include <qlocalsocket.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qtmetamacros.h>
#include <qtypes.h>
template <typename... Types>
constexpr void assertSerializable() {
// monostate being zero ensures transactional reads wont break
static_assert(
std::is_same_v<std::variant_alternative_t<0, std::variant<Types...>>, std::monostate>,
"Serialization of variants without std::monostate at index 0 is disallowed."
);
static_assert(
sizeof...(Types) <= std::numeric_limits<quint8>::max(),
"Serialization of variants that can't fit the tag in a uint8 is disallowed."
);
}
template <typename... Types>
QDataStream& operator<<(QDataStream& stream, const std::variant<Types...>& variant) {
assertSerializable<Types...>();
if (variant.valueless_by_exception()) {
stream << static_cast<quint8>(0); // must be monostate
} else {
stream << static_cast<quint8>(variant.index());
std::visit([&]<typename T>(const T& value) { stream << value; }, variant);
}
return stream;
}
template <typename... Types>
constexpr bool forEachTypeIndex(const auto& f) {
return [&]<size_t... Index>(std::index_sequence<Index...>) {
return (f(std::in_place_index_t<Index>()) || ...);
}(std::index_sequence_for<Types...>());
}
template <typename... Types>
std::variant<Types...> createIndexedOrMonostate(size_t index, std::variant<Types...>& variant) {
assertSerializable<Types...>();
const auto initialized =
forEachTypeIndex<Types...>([index, &variant]<size_t Index>(std::in_place_index_t<Index>) {
if (index == Index) {
variant.template emplace<Index>();
return true;
} else {
return false;
}
});
if (!initialized) {
variant = std::monostate();
}
return variant;
}
template <typename... Types>
QDataStream& operator>>(QDataStream& stream, std::variant<Types...>& variant) {
assertSerializable<Types...>();
quint8 index = 0;
stream >> index;
createIndexedOrMonostate<Types...>(index, variant);
std::visit([&]<typename T>(T& value) { stream >> value; }, variant);
return stream;
}
template <typename... Types>
QDataStream& streamInValues(QDataStream& stream, const Types&... types) {
return (stream << ... << types);
}
template <typename... Types>
QDataStream& streamOutValues(QDataStream& stream, Types&... types) {
return (stream >> ... >> types);
}
// NOLINTBEGIN
#define DEFINE_SIMPLE_DATASTREAM_OPS(Type, ...) \
inline QDataStream& operator<<(QDataStream& stream, const Type& __VA_OPT__(data)) { \
return streamInValues(stream __VA_OPT__(, __VA_ARGS__)); \
} \
\
inline QDataStream& operator>>(QDataStream& stream, Type& __VA_OPT__(data)) { \
return streamOutValues(stream __VA_OPT__(, __VA_ARGS__)); \
}
// NOLINTEND
DEFINE_SIMPLE_DATASTREAM_OPS(std::monostate);
namespace qs::ipc {
Q_DECLARE_LOGGING_CATEGORY(logIpc);
template <typename T>
class MessageStream {
public:
explicit MessageStream(QDataStream* stream, QLocalSocket* socket)
: stream(stream)
, socket(socket) {}
template <typename V>
MessageStream& operator<<(V value) {
*this->stream << T(value);
this->socket->flush();
return *this;
}
private:
QDataStream* stream;
QLocalSocket* socket;
};
class IpcServer: public QObject {
Q_OBJECT;
public:
explicit IpcServer(const QString& path);
static void start();
private slots:
void onNewConnection();
private:
QLocalServer server;
};
class IpcServerConnection: public QObject {
Q_OBJECT;
public:
explicit IpcServerConnection(QLocalSocket* socket, IpcServer* server);
template <typename T>
void respond(const T& message) {
this->stream << message;
this->socket->flush();
}
template <typename T>
MessageStream<T> responseStream() {
return MessageStream<T>(&this->stream, this->socket);
}
// public for access by nonlocal handlers
QLocalSocket* socket;
QDataStream stream;
private slots:
void onDisconnected();
void onReadyRead();
};
class IpcClient: public QObject {
Q_OBJECT;
public:
explicit IpcClient(const QString& path);
[[nodiscard]] bool isConnected() const;
void waitForConnected();
void waitForDisconnected();
void kill();
template <typename T>
void sendMessage(const T& message) {
this->stream << message;
this->socket.flush();
}
template <typename T>
bool waitForResponse(T& slot) {
while (this->socket.waitForReadyRead(-1)) {
this->stream.startTransaction();
this->stream >> slot;
if (!this->stream.commitTransaction()) continue;
return true;
}
qCCritical(logIpc) << "Error occurred while waiting for response.";
return false;
}
[[nodiscard]] static int
connect(const QString& id, const std::function<void(IpcClient& client)>& callback);
// public for access by nonlocal handlers
QLocalSocket socket;
QDataStream stream;
signals:
void connected();
void disconnected();
private slots:
static void onError(QLocalSocket::LocalSocketError error);
};
} // namespace qs::ipc

View file

@ -1,20 +0,0 @@
#pragma once
#include <variant>
#include "../io/ipccomm.hpp"
#include "ipc.hpp"
namespace qs::ipc {
struct IpcKillCommand: std::monostate {
static void exec(IpcServerConnection* /*unused*/);
};
using IpcCommand = std::variant<
std::monostate,
IpcKillCommand,
qs::io::ipc::comm::QueryMetadataCommand,
qs::io::ipc::comm::StringCallCommand>;
} // namespace qs::ipc

File diff suppressed because it is too large Load diff

View file

@ -1,7 +0,0 @@
#pragma once
namespace qs::launch {
int main(int argc, char** argv); // NOLINT
}

View file

@ -1 +0,0 @@
#include "panelinterface.hpp" // NOLINT

View file

@ -1,161 +0,0 @@
#pragma once
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include "doc.hpp"
#include "windowinterface.hpp"
class Anchors {
Q_GADGET;
Q_PROPERTY(bool left MEMBER mLeft);
Q_PROPERTY(bool right MEMBER mRight);
Q_PROPERTY(bool top MEMBER mTop);
Q_PROPERTY(bool bottom MEMBER mBottom);
QML_VALUE_TYPE(anchors);
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;
bool mBottom = false;
};
class Margins {
Q_GADGET;
Q_PROPERTY(qint32 left MEMBER mLeft);
Q_PROPERTY(qint32 right MEMBER mRight);
Q_PROPERTY(qint32 top MEMBER mTop);
Q_PROPERTY(qint32 bottom MEMBER mBottom);
QML_VALUE_TYPE(margins);
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;
qint32 mBottom = 0;
};
///! Panel exclusion mode
/// See @@PanelWindow.exclusionMode.
namespace ExclusionMode { // NOLINT
Q_NAMESPACE;
QML_ELEMENT;
enum Enum {
/// Respect the exclusion zone of other shell layers and optionally set one
Normal = 0,
/// Ignore exclusion zones of other shell layers. You cannot set an exclusion zone in this mode.
Ignore = 1,
/// Decide the exclusion zone based on the window dimensions and anchors.
///
/// Will attempt to reseve exactly enough space for the window and its margins if
/// exactly 3 anchors are connected.
Auto = 2,
};
Q_ENUM_NS(Enum);
} // namespace ExclusionMode
///! Decorationless window attached to screen edges by anchors.
/// Decorationless window attached to screen edges by anchors.
///
/// #### Example
/// The following snippet creates a white bar attached to the bottom of the screen.
///
/// ```qml
/// PanelWindow {
/// anchors {
/// left: true
/// bottom: true
/// right: true
/// }
///
/// Text {
/// anchors.centerIn: parent
/// text: "Hello!"
/// }
/// }
/// ```
class PanelWindowInterface: public WindowInterface {
// clang-format off
Q_OBJECT;
/// Anchors attach a shell window to the sides of the screen.
/// By default all anchors are disabled to avoid blocking the entire screen due to a misconfiguration.
///
/// > [!INFO] When two opposite anchors are attached at the same time, the corrosponding dimension
/// > (width or height) will be forced to equal the screen width/height.
/// > Margins can be used to create anchored windows that are also disconnected from the monitor sides.
Q_PROPERTY(Anchors anchors READ anchors WRITE setAnchors NOTIFY anchorsChanged);
/// Offsets from the sides of the screen.
///
/// > [!INFO] Only applies to edges with anchors
Q_PROPERTY(Margins margins READ margins WRITE setMargins NOTIFY marginsChanged);
/// The amount of space reserved for the shell layer relative to its anchors.
/// Setting this property sets @@exclusionMode to `ExclusionMode.Normal`.
///
/// > [!INFO] Either 1 or 3 anchors are required for the zone to take effect.
Q_PROPERTY(qint32 exclusiveZone READ exclusiveZone WRITE setExclusiveZone NOTIFY exclusiveZoneChanged);
/// Defaults to `ExclusionMode.Auto`.
Q_PROPERTY(ExclusionMode::Enum exclusionMode READ exclusionMode WRITE setExclusionMode NOTIFY exclusionModeChanged);
/// If the panel should render above standard windows. Defaults to true.
///
/// Note: On Wayland this property corrosponds to @@Quickshell.Wayland.WlrLayershell.layer.
Q_PROPERTY(bool aboveWindows READ aboveWindows WRITE setAboveWindows NOTIFY aboveWindowsChanged);
/// If the panel should accept keyboard focus. Defaults to false.
///
/// Note: On Wayland this property corrosponds to @@Quickshell.Wayland.WlrLayershell.keyboardFocus.
Q_PROPERTY(bool focusable READ focusable WRITE setFocusable NOTIFY focusableChanged);
// clang-format on
QSDOC_NAMED_ELEMENT(PanelWindow);
public:
explicit PanelWindowInterface(QObject* parent = nullptr): WindowInterface(parent) {}
[[nodiscard]] virtual Anchors anchors() const = 0;
virtual void setAnchors(Anchors anchors) = 0;
[[nodiscard]] virtual Margins margins() const = 0;
virtual void setMargins(Margins margins) = 0;
[[nodiscard]] virtual qint32 exclusiveZone() const = 0;
virtual void setExclusiveZone(qint32 exclusiveZone) = 0;
[[nodiscard]] virtual ExclusionMode::Enum exclusionMode() const = 0;
virtual void setExclusionMode(ExclusionMode::Enum exclusionMode) = 0;
[[nodiscard]] virtual bool aboveWindows() const = 0;
virtual void setAboveWindows(bool aboveWindows) = 0;
[[nodiscard]] virtual bool focusable() const = 0;
virtual void setFocusable(bool focusable) = 0;
signals:
void anchorsChanged();
void marginsChanged();
void exclusiveZoneChanged();
void exclusionModeChanged();
void aboveWindowsChanged();
void focusableChanged();
};

View file

@ -17,11 +17,11 @@
#include <qtmetamacros.h>
#include <qwindow.h>
#include "../window/proxywindow.hpp"
#include "../window/windowinterface.hpp"
#include "generation.hpp"
#include "popupanchor.hpp"
#include "proxywindow.hpp"
#include "qsmenu.hpp"
#include "windowinterface.hpp"
namespace qs::menu::platform {

View file

@ -13,7 +13,7 @@
#include <qtclasshelpermacros.h>
#include <qtmetamacros.h>
#include "popupanchor.hpp"
#include "../core/popupanchor.hpp"
#include "qsmenu.hpp"
namespace qs::menu::platform {

View file

@ -7,9 +7,9 @@
#include <qtmetamacros.h>
#include <qwindow.h>
#include "proxywindow.hpp"
#include "../window/proxywindow.hpp"
#include "../window/windowinterface.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

View file

@ -13,8 +13,8 @@
#include <qtmetamacros.h>
#include <qwindow.h>
#include "../window/proxywindow.hpp"
#include "doc.hpp"
#include "proxywindow.hpp"
#include "types.hpp"
///! Adjustment strategy for popups that do not fit on screen.

View file

@ -1,136 +0,0 @@
#include "popupwindow.hpp"
#include <qlogging.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qquickwindow.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();
// clang-format off
QObject::connect(this->window, &QWindow::visibleChanged, this, &ProxyPopupWindow::onVisibleChanged);
QObject::connect(this->window, &QWindow::widthChanged, this, &ProxyPopupWindow::reposition);
QObject::connect(this->window, &QWindow::heightChanged, this, &ProxyPopupWindow::reposition);
// clang-format on
this->window->setFlag(Qt::ToolTip);
}
void ProxyPopupWindow::postCompleteWindow() { this->updateTransientParent(); }
void ProxyPopupWindow::setParentWindow(QObject* parent) {
qWarning() << "PopupWindow.parentWindow is deprecated. Use PopupWindow.anchor.window.";
this->mAnchor.setWindow(parent);
}
QObject* ProxyPopupWindow::parentWindow() const {
qWarning() << "PopupWindow.parentWindow is deprecated. Use PopupWindow.anchor.window.";
return this->mAnchor.window();
}
void ProxyPopupWindow::updateTransientParent() {
auto* bw = this->mAnchor.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();
}
void ProxyPopupWindow::onParentUpdated() { this->updateTransientParent(); }
void ProxyPopupWindow::setScreen(QuickshellScreenInfo* /*unused*/) {
qWarning() << "Cannot set screen of popup window, as that is controlled by the parent window";
}
void ProxyPopupWindow::setVisible(bool visible) {
if (visible == this->wantsVisible) return;
this->wantsVisible = visible;
this->updateVisible();
}
void ProxyPopupWindow::updateVisible() {
auto target = this->wantsVisible && this->mAnchor.window() != nullptr
&& this->mAnchor.proxyWindow()->isVisibleDirect();
if (target && this->window != nullptr && !this->window->isVisible()) {
PopupPositioner::instance()->reposition(&this->mAnchor, this->window);
}
this->ProxyWindowBase::setVisible(target);
}
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();
}
}
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) {
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 {
qWarning() << "PopupWindow.relativeY is deprecated. Use PopupWindow.anchor.rect.y.";
return this->mAnchor.rect().y;
}
PopupAnchor* ProxyPopupWindow::anchor() { return &this->mAnchor; }
void ProxyPopupWindow::reposition() {
if (this->window != nullptr) {
PopupPositioner::instance()->reposition(&this->mAnchor, this->window);
}
}

View file

@ -1,122 +0,0 @@
#pragma once
#include <qobject.h>
#include <qqmlintegration.h>
#include <qquickwindow.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "doc.hpp"
#include "popupanchor.hpp"
#include "proxywindow.hpp"
#include "qmlscreen.hpp"
#include "windowinterface.hpp"
///! Popup window.
/// Popup window that can display in a position relative to a floating
/// or panel window.
///
/// #### Example
/// The following snippet creates a panel with a popup centered over it.
///
/// ```qml
/// PanelWindow {
/// id: toplevel
///
/// anchors {
/// bottom: true
/// left: true
/// right: true
/// }
///
/// PopupWindow {
/// parentWindow: toplevel
/// relativeX: parentWindow.width / 2 - width / 2
/// relativeY: parentWindow.height
/// width: 500
/// height: 500
/// visible: true
/// }
/// }
/// ```
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.
///
/// This may be modified to move the window to the given screen.
QSDOC_PROPERTY_OVERRIDE(QuickshellScreenInfo* screen READ screen NOTIFY screenChanged);
// clang-format on
QML_NAMED_ELEMENT(PopupWindow);
public:
explicit ProxyPopupWindow(QObject* parent = nullptr);
void completeWindow() override;
void postCompleteWindow() override;
void setScreen(QuickshellScreenInfo* screen) override;
void setVisible(bool visible) override;
[[nodiscard]] QObject* parentWindow() const;
void setParentWindow(QObject* parent);
[[nodiscard]] qint32 relativeX() const;
void setRelativeX(qint32 x);
[[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 reposition();
private:
QQuickWindow* parentBackingWindow();
void updateTransientParent();
void updateVisible();
PopupAnchor mAnchor {this};
bool wantsVisible = false;
};

View file

@ -1,362 +0,0 @@
#include "proxywindow.hpp"
#include <private/qquickwindow_p.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qqmlcontext.h>
#include <qqmlengine.h>
#include <qqmllist.h>
#include <qquickitem.h>
#include <qquickwindow.h>
#include <qregion.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qvariant.h>
#include <qwindow.h>
#include "generation.hpp"
#include "qmlglobal.hpp"
#include "qmlscreen.hpp"
#include "region.hpp"
#include "reload.hpp"
#include "windowinterface.hpp"
ProxyWindowBase::ProxyWindowBase(QObject* parent)
: Reloadable(parent)
, mContentItem(new QQuickItem()) {
QQmlEngine::setObjectOwnership(this->mContentItem, QQmlEngine::CppOwnership);
this->mContentItem->setParent(this);
this->mContentItem->setProperty("__qs_proxywindow", QVariant::fromValue(this));
// clang-format off
QObject::connect(this, &ProxyWindowBase::widthChanged, this, &ProxyWindowBase::onWidthChanged);
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);
QObject::connect(this, &ProxyWindowBase::xChanged, this, &ProxyWindowBase::windowTransformChanged);
QObject::connect(this, &ProxyWindowBase::yChanged, this, &ProxyWindowBase::windowTransformChanged);
QObject::connect(this, &ProxyWindowBase::widthChanged, this, &ProxyWindowBase::windowTransformChanged);
QObject::connect(this, &ProxyWindowBase::heightChanged, this, &ProxyWindowBase::windowTransformChanged);
QObject::connect(this, &ProxyWindowBase::backerVisibilityChanged, this, &ProxyWindowBase::windowTransformChanged);
// clang-format on
}
ProxyWindowBase::~ProxyWindowBase() { this->deleteWindow(); }
void ProxyWindowBase::onReload(QObject* oldInstance) {
this->window = this->retrieveWindow(oldInstance);
auto wasVisible = this->window != nullptr && this->window->isVisible();
if (this->window == nullptr) this->window = this->createQQuickWindow();
// The qml engine will leave the WindowInterface as owner of everything
// nested in an item, so we have to make sure the interface's children
// are also reloaded.
// Reparenting from the interface does not work reliably, so instead
// we check if the parent is one, as it proxies reloads to here.
if (auto* w = qobject_cast<WindowInterface*>(this->parent())) {
for (auto* child: w->children()) {
if (child == this) continue;
auto* oldInterfaceParent = oldInstance == nullptr ? nullptr : oldInstance->parent();
Reloadable::reloadRecursive(child, oldInterfaceParent);
}
}
Reloadable::reloadChildrenRecursive(this, oldInstance);
this->connectWindow();
this->completeWindow();
this->reloadComplete = true;
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 (this->window != nullptr) emit this->windowDestroyed();
if (auto* window = this->disownWindow()) {
if (auto* generation = EngineGeneration::findObjectGeneration(this)) {
generation->deregisterIncubationController(window->incubationController());
}
window->deleteLater();
}
}
QQuickWindow* ProxyWindowBase::disownWindow() {
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)) {
// 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.
generation->registerIncubationController(this->window->incubationController());
}
// clang-format off
QObject::connect(this->window, &QWindow::visibilityChanged, this, &ProxyWindowBase::visibleChanged);
QObject::connect(this->window, &QWindow::xChanged, this, &ProxyWindowBase::xChanged);
QObject::connect(this->window, &QWindow::yChanged, this, &ProxyWindowBase::yChanged);
QObject::connect(this->window, &QWindow::widthChanged, this, &ProxyWindowBase::widthChanged);
QObject::connect(this->window, &QWindow::heightChanged, this, &ProxyWindowBase::heightChanged);
QObject::connect(this->window, &QWindow::screenChanged, this, &ProxyWindowBase::screenChanged);
QObject::connect(this->window, &QQuickWindow::colorChanged, this, &ProxyWindowBase::colorChanged);
// clang-format on
}
void ProxyWindowBase::completeWindow() {
if (this->mScreen != nullptr && this->window->screen() != this->mScreen) {
if (this->window->isVisible()) this->window->setVisible(false);
this->window->setScreen(this->mScreen);
} else if (this->mScreen == nullptr) {
this->mScreen = this->window->screen();
}
this->setWidth(this->mWidth);
this->setHeight(this->mHeight);
this->setColor(this->mColor);
this->updateMask();
// notify initial x and y positions
emit this->xChanged();
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();
}
bool ProxyWindowBase::deleteOnInvisible() const { return false; }
QQuickWindow* ProxyWindowBase::backingWindow() const { return this->window; }
QQuickItem* ProxyWindowBase::contentItem() const { return this->mContentItem; }
bool ProxyWindowBase::isVisible() const {
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();
}
void ProxyWindowBase::setVisible(bool visible) {
this->mVisible = visible;
if (this->reloadComplete) this->setVisibleDirect(visible);
}
void ProxyWindowBase::setVisibleDirect(bool visible) {
if (this->deleteOnInvisible()) {
if (visible == this->isVisibleDirect()) return;
if (visible) {
this->createWindow();
this->polishItems();
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) {
if (visible) this->polishItems();
this->window->setVisible(visible);
emit this->backerVisibilityChanged();
}
}
void ProxyWindowBase::polishItems() {
// Due to QTBUG-126704, layouts in invisible windows don't update their dimensions.
// Usually this isn't an issue, but it is when the size of a window is based on the size
// of its content, and that content is in a layout.
//
// This hack manually polishes the item tree right before showing the window so it will
// always be created with the correct size.
QQuickWindowPrivate::get(this->window)->polishItems();
}
qint32 ProxyWindowBase::x() const {
if (this->window == nullptr) return 0;
else return this->window->x();
}
qint32 ProxyWindowBase::y() const {
if (this->window == nullptr) return 0;
else return this->window->y();
}
qint32 ProxyWindowBase::width() const {
if (this->window == nullptr) return this->mWidth;
else return this->window->width();
}
void ProxyWindowBase::setWidth(qint32 width) {
this->mWidth = width;
if (this->window == nullptr) {
emit this->widthChanged();
} else this->window->setWidth(width);
}
qint32 ProxyWindowBase::height() const {
if (this->window == nullptr) return this->mHeight;
else return this->window->height();
}
void ProxyWindowBase::setHeight(qint32 height) {
this->mHeight = height;
if (this->window == nullptr) {
emit this->heightChanged();
} else this->window->setHeight(height);
}
void ProxyWindowBase::setScreen(QuickshellScreenInfo* screen) {
if (this->mScreen != nullptr) {
QObject::disconnect(this->mScreen, nullptr, this, nullptr);
}
auto* qscreen = screen == nullptr ? nullptr : screen->screen;
if (qscreen == this->mScreen) return;
if (qscreen != nullptr) {
QObject::connect(qscreen, &QObject::destroyed, this, &ProxyWindowBase::onScreenDestroyed);
}
if (this->window == nullptr) {
emit this->screenChanged();
} else {
auto reshow = this->isVisibleDirect();
if (reshow) this->setVisibleDirect(false);
if (this->window != nullptr) this->window->setScreen(qscreen);
if (reshow) this->setVisibleDirect(true);
}
if (qscreen) this->mScreen = qscreen;
else this->mScreen = this->window->screen();
}
void ProxyWindowBase::onScreenDestroyed() { this->mScreen = nullptr; }
QuickshellScreenInfo* ProxyWindowBase::screen() const {
QScreen* qscreen = nullptr;
if (this->window == nullptr) {
if (this->mScreen != nullptr) qscreen = this->mScreen;
} else {
qscreen = this->window->screen();
}
return QuickshellTracked::instance()->screenInfo(qscreen);
}
QColor ProxyWindowBase::color() const { return this->mColor; }
void ProxyWindowBase::setColor(QColor color) {
this->mColor = color;
if (this->window == nullptr) {
if (color != this->mColor) emit this->colorChanged();
} else {
auto premultiplied = QColor::fromRgbF(
color.redF() * color.alphaF(),
color.greenF() * color.alphaF(),
color.blueF() * color.alphaF(),
color.alphaF()
);
this->window->setColor(premultiplied);
}
}
PendingRegion* ProxyWindowBase::mask() const { return this->mMask; }
void ProxyWindowBase::setMask(PendingRegion* mask) {
if (mask == this->mMask) return;
if (this->mMask != nullptr) {
QObject::disconnect(this->mMask, nullptr, this, nullptr);
}
this->mMask = mask;
if (mask != nullptr) {
mask->setParent(this);
QObject::connect(mask, &QObject::destroyed, this, &ProxyWindowBase::onMaskDestroyed);
QObject::connect(mask, &PendingRegion::changed, this, &ProxyWindowBase::maskChanged);
}
emit this->maskChanged();
}
void ProxyWindowBase::onMaskChanged() {
if (this->window != nullptr) this->updateMask();
}
void ProxyWindowBase::onMaskDestroyed() {
this->mMask = nullptr;
emit this->maskChanged();
}
void ProxyWindowBase::updateMask() {
QRegion mask;
if (this->mMask != nullptr) {
// if left as the default, dont combine it with the whole window area, leave it as is.
if (this->mMask->mIntersection == Intersection::Combine) {
mask = this->mMask->build();
} else {
auto windowRegion = QRegion(QRect(0, 0, this->width(), this->height()));
mask = this->mMask->applyTo(windowRegion);
}
}
this->window->setFlag(Qt::WindowTransparentForInput, this->mMask != nullptr && mask.isEmpty());
this->window->setMask(mask);
}
QQmlListProperty<QObject> ProxyWindowBase::data() {
return this->mContentItem->property("data").value<QQmlListProperty<QObject>>();
}
void ProxyWindowBase::onWidthChanged() { this->mContentItem->setWidth(this->width()); }
void ProxyWindowBase::onHeightChanged() { this->mContentItem->setHeight(this->height()); }
QObject* ProxyWindowAttached::window() const { return this->mWindow; }
QQuickItem* ProxyWindowAttached::contentItem() const { return this->mWindow->contentItem(); }

View file

@ -1,153 +0,0 @@
#pragma once
#include <qcolor.h>
#include <qcontainerfwd.h>
#include <qevent.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qqmllist.h>
#include <qqmlparserstatus.h>
#include <qquickitem.h>
#include <qquickwindow.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "qmlglobal.hpp"
#include "qmlscreen.hpp"
#include "region.hpp"
#include "reload.hpp"
#include "windowinterface.hpp"
// Proxy to an actual window exposing a limited property set with the ability to
// transfer it to a new window.
///! Base class for reloadable windows
///
/// [ShellWindow]: ../shellwindow
/// [FloatingWindow]: ../floatingwindow
class ProxyWindowBase: public Reloadable {
Q_OBJECT;
/// The QtQuick window backing this window.
///
/// > [!WARNING] Do not expect values set via this property to work correctly.
/// > Values set this way will almost certainly misbehave across a reload, possibly
/// > even without one.
/// >
/// > Use **only** if you know what you are doing.
Q_PROPERTY(QQuickWindow* _backingWindow READ backingWindow);
Q_PROPERTY(QQuickItem* contentItem READ contentItem CONSTANT);
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged);
Q_PROPERTY(qint32 width READ width WRITE setWidth NOTIFY widthChanged);
Q_PROPERTY(qint32 height READ height WRITE setHeight NOTIFY heightChanged);
Q_PROPERTY(QuickshellScreenInfo* screen READ screen WRITE setScreen NOTIFY screenChanged);
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged);
Q_PROPERTY(PendingRegion* mask READ mask WRITE setMask NOTIFY maskChanged);
Q_PROPERTY(QObject* windowTransform READ windowTransform NOTIFY windowTransformChanged);
Q_PROPERTY(bool backingWindowVisible READ isVisibleDirect NOTIFY backerVisibilityChanged);
Q_PROPERTY(QQmlListProperty<QObject> data READ data);
Q_CLASSINFO("DefaultProperty", "data");
public:
explicit ProxyWindowBase(QObject* parent = nullptr);
~ProxyWindowBase() override;
ProxyWindowBase(ProxyWindowBase&) = delete;
ProxyWindowBase(ProxyWindowBase&&) = delete;
void operator=(ProxyWindowBase&) = delete;
void operator=(ProxyWindowBase&&) = delete;
void onReload(QObject* oldInstance) override;
void createWindow();
void deleteWindow();
// Disown the backing window and delete all its children.
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]] QQuickItem* contentItem() const;
[[nodiscard]] virtual bool isVisible() const;
[[nodiscard]] virtual bool isVisibleDirect() const;
virtual void setVisible(bool visible);
virtual void setVisibleDirect(bool visible);
[[nodiscard]] virtual qint32 x() const;
[[nodiscard]] virtual qint32 y() const;
[[nodiscard]] virtual qint32 width() const;
virtual void setWidth(qint32 width);
[[nodiscard]] virtual qint32 height() const;
virtual void setHeight(qint32 height);
[[nodiscard]] virtual QuickshellScreenInfo* screen() const;
virtual void setScreen(QuickshellScreenInfo* screen);
[[nodiscard]] QColor color() const;
virtual void setColor(QColor color);
[[nodiscard]] PendingRegion* mask() const;
virtual void setMask(PendingRegion* mask);
[[nodiscard]] QObject* windowTransform() const { return nullptr; } // NOLINT
[[nodiscard]] QQmlListProperty<QObject> data();
signals:
void windowConnected();
void windowDestroyed();
void visibleChanged();
void backerVisibilityChanged();
void xChanged();
void yChanged();
void widthChanged();
void heightChanged();
void windowTransformChanged();
void screenChanged();
void colorChanged();
void maskChanged();
protected slots:
virtual void onWidthChanged();
virtual void onHeightChanged();
void onMaskChanged();
void onMaskDestroyed();
void onScreenDestroyed();
protected:
bool mVisible = true;
qint32 mWidth = 100;
qint32 mHeight = 100;
QScreen* mScreen = nullptr;
QColor mColor = Qt::white;
PendingRegion* mMask = nullptr;
QQuickWindow* window = nullptr;
QQuickItem* mContentItem = nullptr;
bool reloadComplete = false;
private:
void polishItems();
void updateMask();
};
class ProxyWindowAttached: public QsWindowAttached {
Q_OBJECT;
public:
explicit ProxyWindowAttached(ProxyWindowBase* window)
: QsWindowAttached(window)
, mWindow(window) {}
[[nodiscard]] QObject* window() const override;
[[nodiscard]] QQuickItem* contentItem() const override;
private:
ProxyWindowBase* mWindow;
};

View file

@ -1,9 +1,8 @@
function (qs_test name)
add_executable(${name} ${ARGN})
target_link_libraries(${name} PRIVATE ${QT_DEPS} Qt6::Test quickshell-core)
target_link_libraries(${name} PRIVATE ${QT_DEPS} Qt6::Test quickshell-core quickshell-window)
add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $<TARGET_FILE:${name}>)
endfunction()
qs_test(popupwindow popupwindow.cpp)
qs_test(transformwatcher transformwatcher.cpp)
qs_test(ringbuffer ringbuf.cpp)

View file

@ -1,180 +0,0 @@
#include "popupwindow.hpp"
#include <qquickwindow.h>
#include <qsignalspy.h>
#include <qtest.h>
#include <qtestcase.h>
#include <qwindow.h>
#include "../popupwindow.hpp"
#include "../proxywindow.hpp"
void TestPopupWindow::initiallyVisible() { // NOLINT
auto parent = ProxyWindowBase();
auto popup = ProxyPopupWindow();
popup.setParentWindow(&parent);
popup.setVisible(true);
parent.reload();
popup.reload();
QVERIFY(popup.isVisible());
QVERIFY(popup.backingWindow()->isVisible());
QCOMPARE(popup.backingWindow()->transientParent(), parent.backingWindow());
}
void TestPopupWindow::reloadReparent() { // NOLINT
// first generation
auto parent = ProxyWindowBase();
auto popup = ProxyPopupWindow();
auto* win2 = new QQuickWindow();
win2->setVisible(true);
parent.setVisible(true);
popup.setParentWindow(&parent);
popup.setVisible(true);
parent.reload();
popup.reload();
// second generation
auto newParent = ProxyWindowBase();
auto newPopup = ProxyPopupWindow();
newPopup.setParentWindow(&newParent);
newPopup.setVisible(true);
auto* oldWindow = popup.backingWindow();
auto* oldTransientParent = oldWindow->transientParent();
auto spy = QSignalSpy(oldWindow, &QWindow::visibleChanged);
newParent.reload(&parent);
newPopup.reload(&popup);
QVERIFY(newPopup.isVisible());
QVERIFY(newPopup.backingWindow()->isVisible());
QCOMPARE(newPopup.backingWindow()->transientParent(), oldTransientParent);
QCOMPARE(newPopup.backingWindow()->transientParent(), newParent.backingWindow());
QCOMPARE(spy.length(), 0);
}
void TestPopupWindow::reloadUnparent() { // NOLINT
// first generation
auto parent = ProxyWindowBase();
auto popup = ProxyPopupWindow();
popup.setParentWindow(&parent);
popup.setVisible(true);
parent.reload();
popup.reload();
// second generation
auto newPopup = ProxyPopupWindow();
// parent not set
newPopup.setVisible(true);
newPopup.reload(&popup);
QVERIFY(!newPopup.isVisible());
QVERIFY(!newPopup.backingWindow()->isVisible());
QCOMPARE(newPopup.backingWindow()->transientParent(), nullptr);
}
void TestPopupWindow::invisibleWithoutParent() { // NOLINT
auto popup = ProxyPopupWindow();
popup.setVisible(true);
popup.reload();
QVERIFY(!popup.isVisible());
}
void TestPopupWindow::moveWithParent() { // NOLINT
auto parent = ProxyWindowBase();
auto popup = ProxyPopupWindow();
popup.setParentWindow(&parent);
popup.setRelativeX(10);
popup.setRelativeY(10);
popup.setVisible(true);
parent.reload();
popup.reload();
QCOMPARE(popup.x(), parent.x() + 10);
QCOMPARE(popup.y(), parent.y() + 10);
parent.backingWindow()->setX(10);
parent.backingWindow()->setY(10);
QCOMPARE(popup.x(), parent.x() + 10);
QCOMPARE(popup.y(), parent.y() + 10);
}
void TestPopupWindow::attachParentLate() { // NOLINT
auto parent = ProxyWindowBase();
auto popup = ProxyPopupWindow();
popup.setVisible(true);
parent.reload();
popup.reload();
QVERIFY(!popup.isVisible());
popup.setParentWindow(&parent);
QVERIFY(popup.isVisible());
QVERIFY(popup.backingWindow()->isVisible());
QCOMPARE(popup.backingWindow()->transientParent(), parent.backingWindow());
}
void TestPopupWindow::reparentLate() { // NOLINT
auto parent = ProxyWindowBase();
auto popup = ProxyPopupWindow();
popup.setParentWindow(&parent);
popup.setVisible(true);
parent.reload();
popup.reload();
QCOMPARE(popup.x(), parent.x());
QCOMPARE(popup.y(), parent.y());
auto parent2 = ProxyWindowBase();
parent2.reload();
parent2.backingWindow()->setX(10);
parent2.backingWindow()->setY(10);
popup.setParentWindow(&parent2);
QVERIFY(popup.isVisible());
QVERIFY(popup.backingWindow()->isVisible());
QCOMPARE(popup.backingWindow()->transientParent(), parent2.backingWindow());
QCOMPARE(popup.x(), parent2.x());
QCOMPARE(popup.y(), parent2.y());
}
void TestPopupWindow::xMigrationFix() { // NOLINT
auto parent = ProxyWindowBase();
auto popup = ProxyPopupWindow();
popup.setParentWindow(&parent);
popup.setVisible(true);
parent.reload();
popup.reload();
QCOMPARE(popup.x(), parent.x());
popup.setVisible(false);
popup.setVisible(true);
QCOMPARE(popup.x(), parent.x());
}
QTEST_MAIN(TestPopupWindow);

View file

@ -1,18 +0,0 @@
#pragma once
#include <qobject.h>
#include <qtmetamacros.h>
class TestPopupWindow: public QObject {
Q_OBJECT;
private slots:
void initiallyVisible();
void reloadReparent();
void reloadUnparent();
void invisibleWithoutParent();
void moveWithParent();
void attachParentLate();
void reparentLate();
void xMigrationFix();
};

View file

@ -1,34 +0,0 @@
#include "windowinterface.hpp"
#include <qobject.h>
#include <qquickitem.h>
#include <qvariant.h>
#include "proxywindow.hpp"
QsWindowAttached* WindowInterface::qmlAttachedProperties(QObject* object) {
auto* visualRoot = qobject_cast<QQuickItem*>(object);
ProxyWindowBase* proxy = nullptr;
while (visualRoot != nullptr) {
proxy = visualRoot->property("__qs_proxywindow").value<ProxyWindowBase*>();
if (proxy) break;
visualRoot = visualRoot->parentItem();
};
if (!proxy) return nullptr;
auto v = proxy->property("__qs_window_attached");
if (auto* attached = v.value<QsWindowAttached*>()) {
return attached;
}
auto* attached = new ProxyWindowAttached(proxy);
if (attached) {
proxy->setProperty("__qs_window_attached", QVariant::fromValue(attached));
}
return attached;
}

View file

@ -1,155 +0,0 @@
#pragma once
#include <qcolor.h>
#include <qobject.h>
#include <qqmlintegration.h>
#include <qqmllist.h>
#include <qquickitem.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "qmlscreen.hpp"
#include "region.hpp"
#include "reload.hpp"
class ProxyWindowBase;
class QsWindowAttached;
///! Base class of Quickshell windows
/// Base class of Quickshell windows
/// ### Attached properties
/// `QSWindow` can be used as an attached object of anything that subclasses @@QtQuick.Item$.
/// It provides the following properties
/// - `window` - the `QSWindow` object.
/// - `contentItem` - the `contentItem` property of the window.
class WindowInterface: public Reloadable {
Q_OBJECT;
// clang-format off
Q_PROPERTY(QQuickItem* contentItem READ contentItem CONSTANT);
/// If the window should be shown or hidden. Defaults to true.
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged);
/// If the window is currently shown. You should generally prefer [visible](#prop.visible).
///
/// This property is useful for ensuring windows spawn in a specific order, and you should
/// not use it in place of [visible](#prop.visible).
Q_PROPERTY(bool backingWindowVisible READ isBackingWindowVisible NOTIFY backingWindowVisibleChanged);
Q_PROPERTY(qint32 width READ width WRITE setWidth NOTIFY widthChanged);
Q_PROPERTY(qint32 height READ height WRITE setHeight NOTIFY heightChanged);
/// The screen that the window currently occupies.
///
/// This may be modified to move the window to the given screen.
Q_PROPERTY(QuickshellScreenInfo* screen READ screen WRITE setScreen NOTIFY screenChanged);
/// Opaque property that will receive an update when factors that affect the window's position
/// and transform changed.
///
/// This property is intended to be used to force a binding update,
/// along with map[To|From]Item (which is not reactive).
Q_PROPERTY(QObject* windowTransform READ windowTransform NOTIFY windowTransformChanged);
/// The background color of the window. Defaults to white.
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged);
/// The clickthrough mask. Defaults to null.
///
/// If non null then the clickable areas of the window will be determined by the provided region.
///
/// ```qml
/// ShellWindow {
/// // The mask region is set to `rect`, meaning only `rect` is clickable.
/// // All other clicks pass through the window to ones behind it.
/// mask: Region { item: rect }
///
/// Rectangle {
/// id: rect
///
/// anchors.centerIn: parent
/// width: 100
/// height: 100
/// }
/// }
/// ```
///
/// If the provided region's intersection mode is `Combine` (the default),
/// then the region will be used as is. Otherwise it will be applied on top of the window region.
///
/// For example, setting the intersection mode to `Xor` will invert the mask and make everything in
/// the mask region not clickable and pass through clicks inside it through the window.
///
/// ```qml
/// ShellWindow {
/// // The mask region is set to `rect`, but the intersection mode is set to `Xor`.
/// // This inverts the mask causing all clicks inside `rect` to be passed to the window
/// // behind this one.
/// mask: Region { item: rect; intersection: Intersection.Xor }
///
/// Rectangle {
/// id: rect
///
/// anchors.centerIn: parent
/// width: 100
/// height: 100
/// }
/// }
/// ```
Q_PROPERTY(PendingRegion* mask READ mask WRITE setMask NOTIFY maskChanged);
Q_PROPERTY(QQmlListProperty<QObject> data READ data);
// clang-format on
Q_CLASSINFO("DefaultProperty", "data");
QML_NAMED_ELEMENT(QsWindow);
QML_UNCREATABLE("uncreatable base class");
QML_ATTACHED(QsWindowAttached);
public:
explicit WindowInterface(QObject* parent = nullptr): Reloadable(parent) {}
[[nodiscard]] virtual ProxyWindowBase* proxyWindow() const = 0;
[[nodiscard]] virtual QQuickItem* contentItem() const = 0;
[[nodiscard]] virtual bool isVisible() const = 0;
[[nodiscard]] virtual bool isBackingWindowVisible() const = 0;
virtual void setVisible(bool visible) = 0;
[[nodiscard]] virtual qint32 width() const = 0;
virtual void setWidth(qint32 width) = 0;
[[nodiscard]] virtual qint32 height() const = 0;
virtual void setHeight(qint32 height) = 0;
[[nodiscard]] virtual QuickshellScreenInfo* screen() const = 0;
virtual void setScreen(QuickshellScreenInfo* screen) = 0;
[[nodiscard]] QObject* windowTransform() const { return nullptr; } // NOLINT
[[nodiscard]] virtual QColor color() const = 0;
virtual void setColor(QColor color) = 0;
[[nodiscard]] virtual PendingRegion* mask() const = 0;
virtual void setMask(PendingRegion* mask) = 0;
[[nodiscard]] virtual QQmlListProperty<QObject> data() = 0;
static QsWindowAttached* qmlAttachedProperties(QObject* object);
signals:
void windowConnected();
void visibleChanged();
void backingWindowVisibleChanged();
void widthChanged();
void heightChanged();
void screenChanged();
void windowTransformChanged();
void colorChanged();
void maskChanged();
};
class QsWindowAttached: public QObject {
Q_OBJECT;
Q_PROPERTY(QObject* window READ window CONSTANT);
Q_PROPERTY(QQuickItem* contentItem READ contentItem CONSTANT);
QML_ANONYMOUS;
public:
[[nodiscard]] virtual QObject* window() const = 0;
[[nodiscard]] virtual QQuickItem* contentItem() const = 0;
protected:
explicit QsWindowAttached(QObject* parent): QObject(parent) {}
};