Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,17 @@ To disable: `-DHYPRLAND_FOCUS_GRAB=OFF`

[hyprland-focus-grab-v1]: https://github.com/hyprwm/hyprland-protocols/blob/main/protocols/hyprland-focus-grab-v1.xml

### Niri
This feature enables niri specific integrations. It requires wayland support
but has no extra dependencies.

To disable: `-DNIRI=OFF`

#### Niri IPC
Enables interfacing with niri's IPC.

To disable: `-DNIRI_IPC=OFF`

### i3/Sway
Enables i3 and Sway specific features, does not have any dependency on Wayland or x11.

Expand Down
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ boption(HYPRLAND_IPC " Hyprland IPC" ON REQUIRES HYPRLAND)
boption(HYPRLAND_GLOBAL_SHORTCUTS " Hyprland Global Shortcuts" ON REQUIRES HYPRLAND)
boption(HYPRLAND_FOCUS_GRAB " Hyprland Focus Grabbing" ON REQUIRES HYPRLAND)
boption(HYPRLAND_SURFACE_EXTENSIONS " Hyprland Surface Extensions" ON REQUIRES HYPRLAND)
boption(NIRI " Niri" ON REQUIRES WAYLAND)
boption(NIRI_IPC " Niri IPC" ON REQUIRES NIRI)
boption(SCREENCOPY " Screencopy" ON REQUIRES WAYLAND)
boption(SCREENCOPY_ICC " Image Copy Capture" ON REQUIRES WAYLAND)
boption(SCREENCOPY_WLR " Wlroots Screencopy" ON REQUIRES WAYLAND)
Expand Down
8 changes: 8 additions & 0 deletions changelog/next.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## New Features

### Niri IPC module

Added the `Quickshell.Niri` module, providing live niri compositor state over IPC:
workspaces, windows, monitors, screencasts, overview state, and keyboard layouts,
plus action dispatch (`Niri.dispatch`) and a raw event stream (`Niri.rawEvent`).

## Other Changes

- Added support for Hyprland's new workspace address format.
Expand Down
4 changes: 4 additions & 0 deletions src/wayland/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ if (HYPRLAND)
add_subdirectory(hyprland)
endif()

if (NIRI)
add_subdirectory(niri)
endif()

add_subdirectory(background_effect)
list(APPEND WAYLAND_MODULES Quickshell.Wayland._BackgroundEffect)

Expand Down
23 changes: 23 additions & 0 deletions src/wayland/niri/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
qt_add_library(quickshell-niri STATIC)

target_link_libraries(quickshell-niri PRIVATE ${QT_DEPS})

set(NIRI_MODULES)

if (NIRI_IPC)
add_subdirectory(ipc)
list(APPEND NIRI_MODULES Quickshell.Niri._Ipc)
endif()

qt_add_qml_module(quickshell-niri
URI Quickshell.Niri
VERSION 0.1
IMPORTS ${NIRI_MODULES}
)

qs_add_module_deps_light(quickshell-io Quickshell)
install_qml_module(quickshell-niri)

# intentionally no pch as the module is empty

target_link_libraries(quickshell PRIVATE quickshell-niriplugin)
24 changes: 24 additions & 0 deletions src/wayland/niri/ipc/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
qt_add_library(quickshell-niri-ipc STATIC
cast.cpp
connection.cpp
monitor.cpp
workspace.cpp
window.cpp
qml.cpp
)

qt_add_qml_module(quickshell-niri-ipc
URI Quickshell.Niri._Ipc
VERSION 0.1
DEPENDENCIES QtQuick
)

qs_add_module_deps_light(quickshell-niri-ipc Quickshell)

install_qml_module(quickshell-niri-ipc)

target_link_libraries(quickshell-niri-ipc PRIVATE Qt::Quick quickshell-core)

qs_module_pch(quickshell-niri-ipc SET large)

target_link_libraries(quickshell PRIVATE quickshell-niri-ipcplugin)
48 changes: 48 additions & 0 deletions src/wayland/niri/ipc/cast.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "cast.hpp"

#include <qcontainerfwd.h>
#include <qobject.h>
#include <qproperty.h>
#include <qtypes.h>
#include <qvariant.h>

#include "connection.hpp"

namespace qs::niri::ipc {

NiriCast::NiriCast(NiriIpc* ipc): QObject(ipc), ipc(ipc) {}

void NiriCast::updateFromObject(const QVariantMap& object) {
Qt::beginPropertyUpdateGroup();

// Stream ID cannot be updated after creation.
if (this->bStreamId.value() == 0) {
this->bStreamId = object.value("stream_id").value<qint64>();
}

this->bSessionId = object.value("session_id").value<qint64>();
this->bKind = object.value("kind").value<QString>();
this->bDynamicTarget = object.value("is_dynamic_target").value<bool>();
this->bActive = object.value("is_active").value<bool>();
this->bPid = object.value("pid").value<qint32>();
auto pwNodeId = object.value("pw_node_id");
this->bPwNodeId = pwNodeId.isNull() ? -1 : pwNodeId.value<qint32>();

// The target is an enum: Nothing, Output {name}, or Window {id}.
auto target = object.value("target").toMap();

if (auto output = target.value("Output").toMap(); !output.isEmpty()) {
this->bTargetOutput = output.value("name").value<QString>();
this->bTargetWindowId = 0;
} else if (auto window = target.value("Window").toMap(); !window.isEmpty()) {
this->bTargetOutput = QString();
this->bTargetWindowId = window.value("id").value<qint64>();
} else {
this->bTargetOutput = QString();
this->bTargetWindowId = 0;
}

Qt::endPropertyUpdateGroup();
}

} // namespace qs::niri::ipc
81 changes: 81 additions & 0 deletions src/wayland/niri/ipc/cast.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#pragma once

#include <qcontainerfwd.h>
#include <qobject.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qtypes.h>

#include "connection.hpp"

namespace qs::niri::ipc {

class NiriCast: public QObject {
Q_OBJECT;
// clang-format off
/// Stream id of the screencast. Uniquely identifies it.
Q_PROPERTY(qint64 streamId READ default NOTIFY streamIdChanged BINDABLE bindableStreamId);
/// Session id of the screencast. A session can have multiple streams.
Q_PROPERTY(qint64 sessionId READ default NOTIFY sessionIdChanged BINDABLE bindableSessionId);
/// Kind of the screencast: "PipeWire" or "WlrScreencopy".
Q_PROPERTY(QString kind READ default NOTIFY kindChanged BINDABLE bindableKind);
/// Name of the screencasted output, or empty if the cast does not target an output.
Q_PROPERTY(QString targetOutput READ default NOTIFY targetOutputChanged BINDABLE bindableTargetOutput);
/// Id of the screencasted window, or 0 if the cast does not target a window.
Q_PROPERTY(qint64 targetWindowId READ default NOTIFY targetWindowIdChanged BINDABLE bindableTargetWindowId);
/// Whether this cast's target can be changed by actions like SetDynamicCastWindow.
Q_PROPERTY(bool dynamicTarget READ default NOTIFY dynamicTargetChanged BINDABLE bindableDynamicTarget);
/// Whether the cast is currently streaming frames.
Q_PROPERTY(bool active READ default NOTIFY activeChanged BINDABLE bindableActive);
/// Process ID of the screencast consumer, or 0 if unknown.
Q_PROPERTY(qint32 pid READ default NOTIFY pidChanged BINDABLE bindablePid);
/// PipeWire node ID of the screencast stream, or -1 if none.
Q_PROPERTY(qint32 pwNodeId READ default NOTIFY pwNodeIdChanged BINDABLE bindablePwNodeId);
// clang-format on
QML_ELEMENT;
QML_UNCREATABLE("NiriCasts must be retrieved from the Niri singleton.");

public:
explicit NiriCast(NiriIpc* ipc);

void updateFromObject(const QVariantMap& object);

[[nodiscard]] QBindable<qint64> bindableStreamId() { return &this->bStreamId; }
[[nodiscard]] QBindable<qint64> bindableSessionId() { return &this->bSessionId; }
[[nodiscard]] QBindable<QString> bindableKind() { return &this->bKind; }
[[nodiscard]] QBindable<QString> bindableTargetOutput() { return &this->bTargetOutput; }
[[nodiscard]] QBindable<qint64> bindableTargetWindowId() { return &this->bTargetWindowId; }
[[nodiscard]] QBindable<bool> bindableDynamicTarget() { return &this->bDynamicTarget; }
[[nodiscard]] QBindable<bool> bindableActive() { return &this->bActive; }
[[nodiscard]] QBindable<qint32> bindablePid() { return &this->bPid; }
[[nodiscard]] QBindable<qint32> bindablePwNodeId() { return &this->bPwNodeId; }

signals:
void streamIdChanged();
void sessionIdChanged();
void kindChanged();
void targetOutputChanged();
void targetWindowIdChanged();
void dynamicTargetChanged();
void activeChanged();
void pidChanged();
void pwNodeIdChanged();

private:
NiriIpc* ipc;

// clang-format off
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(NiriCast, qint64, bStreamId, 0, &NiriCast::streamIdChanged);
Q_OBJECT_BINDABLE_PROPERTY(NiriCast, qint64, bSessionId, &NiriCast::sessionIdChanged);
Q_OBJECT_BINDABLE_PROPERTY(NiriCast, QString, bKind, &NiriCast::kindChanged);
Q_OBJECT_BINDABLE_PROPERTY(NiriCast, QString, bTargetOutput, &NiriCast::targetOutputChanged);
Q_OBJECT_BINDABLE_PROPERTY(NiriCast, qint64, bTargetWindowId, &NiriCast::targetWindowIdChanged);
Q_OBJECT_BINDABLE_PROPERTY(NiriCast, bool, bDynamicTarget, &NiriCast::dynamicTargetChanged);
Q_OBJECT_BINDABLE_PROPERTY(NiriCast, bool, bActive, &NiriCast::activeChanged);
Q_OBJECT_BINDABLE_PROPERTY(NiriCast, qint32, bPid, &NiriCast::pidChanged);
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(NiriCast, qint32, bPwNodeId, -1, &NiriCast::pwNodeIdChanged);
// clang-format on
};

} // namespace qs::niri::ipc
Loading
Loading