forked from quickshell/quickshell
i3/sway: add support for the I3 and Sway IPC
sway: add urgent and focused dispatchers to workspaces flake: add sway toggle WIP sway: add monitor status sway: handle multiple ipc events in one line sway: reuse socket connection for dispatches & better command type handling WIP sway: add associated monitor to a workspace i3/sway: update to allow for i3 compatibility i3/sway: manage setting the focused monitors i3/sway: fix multi monitor crash i3/sway: fix linting errors i3/sway: update nix package flag naming to i3 i3/sway: add documentation, fix module.md and impl monitorFor i3/sway: handle more workspace ipc events i3/sway: fix review i3/sway: fix crash due to newline breaking up an IPC message i3/sway: handle broken messages by forwarding to the next magic sequence i3/sway: break loop when buffer is empty i3/sway: fix monitor focus & focused monitor signal not being emitted i3/sway: use datastreams instead of qbytearrays for socket reading i3/sway: fix lint issues i3/sway: drop second socket connection, remove dispatch return value, recreate IPC connection on fatal error i3/sway: handle run_command responses i3/sway: remove reconnection on unknown event i3/sway: fix formatting, lint & avoid writing to socket if connection is not open
This commit is contained in:
parent
84ce47b6d3
commit
31adcaac76
12
BUILD.md
12
BUILD.md
|
@ -182,13 +182,23 @@ To disable: `-DHYPRLAND_GLOBAL_SHORTCUTS=OFF`
|
|||
[hyprland-global-shortcuts-v1]: https://github.com/hyprwm/hyprland-protocols/blob/main/protocols/hyprland-global-shortcuts-v1.xml
|
||||
|
||||
#### Hyprland Focus Grab
|
||||
Enables windows to grab focus similarly to a context menu undr hyprland through the
|
||||
Enables windows to grab focus similarly to a context menu under hyprland through the
|
||||
[hyprland-focus-grab-v1] protocol. This feature has no extra dependencies.
|
||||
|
||||
To disable: `-DHYPRLAND_FOCUS_GRAB=OFF`
|
||||
|
||||
[hyprland-focus-grab-v1]: https://github.com/hyprwm/hyprland-protocols/blob/main/protocols/hyprland-focus-grab-v1.xml
|
||||
|
||||
### i3/Sway
|
||||
Enables i3 and Sway specific features, does not have any dependency on Wayland or x11.
|
||||
|
||||
To disable: `-DI3=OFF`
|
||||
|
||||
#### i3/Sway IPC
|
||||
Enables interfacing with i3 and Sway's IPC.
|
||||
|
||||
To disable: `-DI3_IPC=OFF`
|
||||
|
||||
## Building
|
||||
*For developers and prospective contributors: See [CONTRIBUTING.md](CONTRIBUTING.md).*
|
||||
|
||||
|
|
|
@ -55,6 +55,8 @@ boption(HYPRLAND " Hyprland" ON REQUIRES WAYLAND)
|
|||
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(I3 " I3/Sway" ON)
|
||||
boption(I3_IPC " I3/Sway IPC" ON REQUIRES I3)
|
||||
boption(X11 "X11" ON)
|
||||
boption(SERVICE_STATUS_NOTIFIER "System Tray" ON)
|
||||
boption(SERVICE_PIPEWIRE "PipeWire" ON)
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
withPipewire ? true,
|
||||
withPam ? true,
|
||||
withHyprland ? true,
|
||||
withI3 ? true,
|
||||
}: buildStdenv.mkDerivation {
|
||||
pname = "quickshell${lib.optionalString debug "-debug"}";
|
||||
version = "0.1.0";
|
||||
|
@ -81,6 +82,7 @@
|
|||
(lib.cmakeBool "SERVICE_PIPEWIRE" withPipewire)
|
||||
(lib.cmakeBool "SERVICE_PAM" withPam)
|
||||
(lib.cmakeBool "HYPRLAND" withHyprland)
|
||||
(lib.cmakeBool "I3" withI3)
|
||||
];
|
||||
|
||||
# How to get debuginfo in gdb from a release build:
|
||||
|
|
|
@ -11,6 +11,10 @@ qt_add_qml_module(quickshell-x11
|
|||
DEPENDENCIES QtQuick
|
||||
)
|
||||
|
||||
if(I3)
|
||||
add_subdirectory(i3)
|
||||
endif()
|
||||
|
||||
install_qml_module(quickshell-x11)
|
||||
|
||||
add_library(quickshell-x11-init OBJECT init.cpp)
|
||||
|
|
23
src/x11/i3/CMakeLists.txt
Normal file
23
src/x11/i3/CMakeLists.txt
Normal file
|
@ -0,0 +1,23 @@
|
|||
qt_add_library(quickshell-i3 STATIC)
|
||||
|
||||
target_link_libraries(quickshell-i3 PRIVATE ${QT_DEPS})
|
||||
|
||||
set(I3_MODULES)
|
||||
|
||||
if (I3_IPC)
|
||||
add_subdirectory(ipc)
|
||||
list(APPEND I3_MODULES Quickshell.I3._Ipc)
|
||||
endif()
|
||||
|
||||
qt_add_qml_module(quickshell-i3
|
||||
URI Quickshell.I3
|
||||
VERSION 0.1
|
||||
IMPORTS ${I3_MODULES}
|
||||
)
|
||||
|
||||
install_qml_module(quickshell-i3)
|
||||
|
||||
qs_pch(quickshell-i3)
|
||||
qs_pch(quickshell-i3plugin)
|
||||
|
||||
target_link_libraries(quickshell PRIVATE quickshell-i3plugin)
|
22
src/x11/i3/ipc/CMakeLists.txt
Normal file
22
src/x11/i3/ipc/CMakeLists.txt
Normal file
|
@ -0,0 +1,22 @@
|
|||
qt_add_library(quickshell-i3-ipc STATIC
|
||||
connection.cpp
|
||||
qml.cpp
|
||||
workspace.cpp
|
||||
monitor.cpp
|
||||
)
|
||||
|
||||
qt_add_qml_module(quickshell-i3-ipc
|
||||
URI Quickshell.I3._Ipc
|
||||
VERSION 0.1
|
||||
DEPENDENCIES QtQml
|
||||
)
|
||||
|
||||
qs_add_module_deps_light(quickshell-i3-ipc Quickshell)
|
||||
|
||||
install_qml_module(quickshell-i3-ipc)
|
||||
|
||||
target_link_libraries(quickshell-i3-ipc PRIVATE Qt::Quick)
|
||||
|
||||
qs_module_pch(quickshell-i3-ipc SET large)
|
||||
|
||||
target_link_libraries(quickshell PRIVATE quickshell-i3-ipcplugin)
|
542
src/x11/i3/ipc/connection.cpp
Normal file
542
src/x11/i3/ipc/connection.cpp
Normal file
|
@ -0,0 +1,542 @@
|
|||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <tuple>
|
||||
|
||||
#include <bit>
|
||||
#include <qbytearray.h>
|
||||
#include <qbytearrayview.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdatastream.h>
|
||||
#include <qjsonarray.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qjsonobject.h>
|
||||
#include <qjsonvalue.h>
|
||||
#include <qlocalsocket.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qsysinfo.h>
|
||||
#include <qtenvironmentvariables.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../../core/model.hpp"
|
||||
#include "../../../core/qmlscreen.hpp"
|
||||
#include "connection.hpp"
|
||||
#include "monitor.hpp"
|
||||
#include "workspace.hpp"
|
||||
|
||||
Q_LOGGING_CATEGORY(logI3Ipc, "quickshell.I3.ipc", QtWarningMsg);
|
||||
Q_LOGGING_CATEGORY(logI3IpcEvents, "quickshell.I3.ipc.events", QtWarningMsg);
|
||||
|
||||
namespace qs::i3::ipc {
|
||||
|
||||
void I3Ipc::makeRequest(const QByteArray& request) {
|
||||
if (!this->valid) {
|
||||
qCWarning(logI3IpcEvents) << "IPC connection is not open, ignoring request.";
|
||||
return;
|
||||
}
|
||||
this->liveEventSocket.write(request);
|
||||
this->liveEventSocket.flush();
|
||||
}
|
||||
|
||||
void I3Ipc::dispatch(const QString& payload) {
|
||||
auto message = I3Ipc::buildRequestMessage(EventCode::RunCommand, payload.toLocal8Bit());
|
||||
|
||||
this->makeRequest(message);
|
||||
}
|
||||
|
||||
QByteArray I3Ipc::buildRequestMessage(EventCode cmd, const QByteArray& payload) {
|
||||
auto payloadLength = static_cast<quint32>(payload.length());
|
||||
|
||||
auto type = QByteArray(std::bit_cast<std::array<char, 4>>(cmd).data(), 4);
|
||||
auto len = QByteArray(std::bit_cast<std::array<char, 4>>(payloadLength).data(), 4);
|
||||
|
||||
return MAGIC.data() + len + type + payload;
|
||||
}
|
||||
|
||||
I3Ipc::I3Ipc() {
|
||||
auto sock = qEnvironmentVariable("I3SOCK");
|
||||
|
||||
if (sock.isEmpty()) {
|
||||
qCWarning(logI3Ipc) << "$I3SOCK is unset. Trying $SWAYSOCK.";
|
||||
|
||||
sock = qEnvironmentVariable("SWAYSOCK");
|
||||
|
||||
if (sock.isEmpty()) {
|
||||
qCWarning(logI3Ipc) << "$SWAYSOCK and I3SOCK are unset. Cannot connect to socket.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->mSocketPath = sock;
|
||||
|
||||
// clang-format off
|
||||
QObject::connect(&this->liveEventSocket, &QLocalSocket::errorOccurred, this, &I3Ipc::eventSocketError);
|
||||
QObject::connect(&this->liveEventSocket, &QLocalSocket::stateChanged, this, &I3Ipc::eventSocketStateChanged);
|
||||
QObject::connect(&this->liveEventSocket, &QLocalSocket::readyRead, this, &I3Ipc::eventSocketReady);
|
||||
QObject::connect(&this->liveEventSocket, &QLocalSocket::connected, this, &I3Ipc::subscribe);
|
||||
// clang-format on
|
||||
|
||||
this->liveEventSocketDs.setDevice(&this->liveEventSocket);
|
||||
this->liveEventSocketDs.setByteOrder(static_cast<QDataStream::ByteOrder>(QSysInfo::ByteOrder));
|
||||
|
||||
this->liveEventSocket.connectToServer(this->mSocketPath);
|
||||
}
|
||||
|
||||
void I3Ipc::subscribe() {
|
||||
auto payload = QByteArray(R"(["workspace","output"])");
|
||||
auto message = I3Ipc::buildRequestMessage(EventCode::Subscribe, payload);
|
||||
|
||||
this->makeRequest(message);
|
||||
|
||||
this->refreshWorkspaces();
|
||||
this->refreshMonitors();
|
||||
}
|
||||
|
||||
void I3Ipc::eventSocketReady() {
|
||||
for (auto& [type, data]: this->parseResponse()) {
|
||||
this->event.mCode = type;
|
||||
this->event.mData = data;
|
||||
|
||||
this->onEvent(&this->event);
|
||||
emit this->rawEvent(&this->event);
|
||||
}
|
||||
}
|
||||
|
||||
void I3Ipc::reconnectIPC() {
|
||||
qCWarning(logI3Ipc) << "Fatal IPC error occured, recreating connection";
|
||||
this->liveEventSocket.disconnectFromServer();
|
||||
this->liveEventSocket.connectToServer(this->mSocketPath);
|
||||
}
|
||||
|
||||
QVector<Event> I3Ipc::parseResponse() {
|
||||
QVector<std::tuple<EventCode, QJsonDocument>> events;
|
||||
const int magicLen = 6;
|
||||
|
||||
while (!this->liveEventSocketDs.atEnd()) {
|
||||
this->liveEventSocketDs.startTransaction();
|
||||
this->liveEventSocketDs.startTransaction();
|
||||
|
||||
std::array<char, 6> buffer = {};
|
||||
qint32 size = 0;
|
||||
qint32 type = EventCode::Unknown;
|
||||
|
||||
this->liveEventSocketDs.readRawData(buffer.data(), magicLen);
|
||||
this->liveEventSocketDs >> size;
|
||||
this->liveEventSocketDs >> type;
|
||||
|
||||
if (!this->liveEventSocketDs.commitTransaction()) break;
|
||||
|
||||
QByteArray payload(size, Qt::Uninitialized);
|
||||
|
||||
this->liveEventSocketDs.readRawData(payload.data(), size);
|
||||
|
||||
if (!this->liveEventSocketDs.commitTransaction()) break;
|
||||
|
||||
if (strncmp(buffer.data(), MAGIC.data(), 6) != 0) {
|
||||
qCWarning(logI3Ipc) << "No magic sequence found in string.";
|
||||
this->reconnectIPC();
|
||||
break;
|
||||
};
|
||||
|
||||
if (I3IpcEvent::intToEvent(type) == EventCode::Unknown) {
|
||||
qCWarning(logI3Ipc) << "Received unknown event";
|
||||
break;
|
||||
}
|
||||
|
||||
QJsonParseError e;
|
||||
|
||||
auto data = QJsonDocument::fromJson(payload, &e);
|
||||
if (e.error != QJsonParseError::NoError) {
|
||||
qCWarning(logI3Ipc) << "Invalid JSON value:" << e.errorString();
|
||||
break;
|
||||
} else {
|
||||
events.push_back(std::tuple(I3IpcEvent::intToEvent(type), data));
|
||||
}
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
void I3Ipc::eventSocketError(QLocalSocket::LocalSocketError error) const {
|
||||
if (!this->valid) {
|
||||
qCWarning(logI3Ipc) << "Unable to connect to I3 socket:" << error;
|
||||
} else {
|
||||
qCWarning(logI3Ipc) << "I3 socket error:" << error;
|
||||
}
|
||||
}
|
||||
|
||||
void I3Ipc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) {
|
||||
if (state == QLocalSocket::ConnectedState) {
|
||||
qCInfo(logI3Ipc) << "I3 event socket connected.";
|
||||
emit this->connected();
|
||||
} else if (state == QLocalSocket::UnconnectedState && this->valid) {
|
||||
qCWarning(logI3Ipc) << "I3 event socket disconnected.";
|
||||
}
|
||||
|
||||
this->valid = state == QLocalSocket::ConnectedState;
|
||||
}
|
||||
|
||||
QString I3Ipc::socketPath() const { return this->mSocketPath; }
|
||||
I3Workspace* I3Ipc::focusedWorkspace() const { return this->mFocusedWorkspace; }
|
||||
I3Monitor* I3Ipc::focusedMonitor() const { return this->mFocusedMonitor; }
|
||||
|
||||
void I3Ipc::setFocusedWorkspace(I3Workspace* workspace) {
|
||||
if (workspace == this->mFocusedWorkspace) return;
|
||||
|
||||
if (this->mFocusedWorkspace != nullptr) {
|
||||
this->mFocusedWorkspace->setFocus(false);
|
||||
QObject::disconnect(this->mFocusedWorkspace, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
this->mFocusedWorkspace = workspace;
|
||||
|
||||
if (workspace != nullptr) {
|
||||
if (auto* monitor = this->mFocusedWorkspace->monitor()) {
|
||||
monitor->setFocusedWorkspace(this->mFocusedWorkspace);
|
||||
}
|
||||
|
||||
QObject::connect(workspace, &QObject::destroyed, this, &I3Ipc::onFocusedWorkspaceDestroyed);
|
||||
workspace->setFocus(true);
|
||||
this->setFocusedMonitor(workspace->monitor());
|
||||
}
|
||||
|
||||
emit this->focusedWorkspaceChanged();
|
||||
}
|
||||
|
||||
void I3Ipc::setFocusedMonitor(I3Monitor* monitor) {
|
||||
if (monitor == this->mFocusedMonitor) return;
|
||||
|
||||
if (this->mFocusedMonitor != nullptr) {
|
||||
this->mFocusedMonitor->setFocus(false);
|
||||
QObject::disconnect(this->mFocusedMonitor, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
this->mFocusedMonitor = monitor;
|
||||
|
||||
if (monitor != nullptr) {
|
||||
monitor->setFocus(true);
|
||||
QObject::connect(monitor, &QObject::destroyed, this, &I3Ipc::onFocusedMonitorDestroyed);
|
||||
}
|
||||
|
||||
emit this->focusedMonitorChanged();
|
||||
}
|
||||
|
||||
void I3Ipc::onFocusedWorkspaceDestroyed() {
|
||||
this->mFocusedWorkspace = nullptr;
|
||||
emit this->focusedWorkspaceChanged();
|
||||
}
|
||||
|
||||
void I3Ipc::onFocusedMonitorDestroyed() {
|
||||
this->mFocusedMonitor = nullptr;
|
||||
emit this->focusedMonitorChanged();
|
||||
}
|
||||
|
||||
I3Ipc* I3Ipc::instance() {
|
||||
static I3Ipc* instance = nullptr; // NOLINT
|
||||
|
||||
if (instance == nullptr) {
|
||||
instance = new I3Ipc();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void I3Ipc::refreshWorkspaces() {
|
||||
this->makeRequest(I3Ipc::buildRequestMessage(EventCode::GetWorkspaces));
|
||||
}
|
||||
|
||||
void I3Ipc::handleGetWorkspacesEvent(I3IpcEvent* event) {
|
||||
auto data = event->mData;
|
||||
|
||||
auto workspaces = data.array();
|
||||
|
||||
const auto& mList = this->mWorkspaces.valueList();
|
||||
auto names = QVector<QString>();
|
||||
|
||||
qCDebug(logI3Ipc) << "There are" << workspaces.toVariantList().length() << "workspaces";
|
||||
for (auto entry: workspaces) {
|
||||
auto object = entry.toObject().toVariantMap();
|
||||
auto name = object["name"].toString();
|
||||
|
||||
auto workspaceIter = std::find_if(mList.begin(), mList.end(), [name](const I3Workspace* m) {
|
||||
return m->name() == name;
|
||||
});
|
||||
|
||||
auto* workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter;
|
||||
auto existed = workspace != nullptr;
|
||||
|
||||
if (workspace == nullptr) {
|
||||
workspace = new I3Workspace(this);
|
||||
}
|
||||
|
||||
workspace->updateFromObject(object);
|
||||
|
||||
if (workspace->focused()) {
|
||||
this->setFocusedWorkspace(workspace);
|
||||
}
|
||||
|
||||
if (!existed) {
|
||||
this->mWorkspaces.insertObject(workspace);
|
||||
}
|
||||
|
||||
names.push_back(name);
|
||||
}
|
||||
|
||||
auto removedWorkspaces = QVector<I3Workspace*>();
|
||||
|
||||
for (auto* workspace: mList) {
|
||||
if (!names.contains(workspace->name())) {
|
||||
removedWorkspaces.push_back(workspace);
|
||||
}
|
||||
}
|
||||
|
||||
qCDebug(logI3Ipc) << "Removing" << removedWorkspaces.length() << "deleted workspaces.";
|
||||
|
||||
for (auto* workspace: removedWorkspaces) {
|
||||
this->mWorkspaces.removeObject(workspace);
|
||||
delete workspace;
|
||||
}
|
||||
}
|
||||
|
||||
void I3Ipc::refreshMonitors() {
|
||||
this->makeRequest(I3Ipc::buildRequestMessage(EventCode::GetOutputs));
|
||||
}
|
||||
|
||||
void I3Ipc::handleGetOutputsEvent(I3IpcEvent* event) {
|
||||
auto data = event->mData;
|
||||
|
||||
auto monitors = data.array();
|
||||
const auto& mList = this->mMonitors.valueList();
|
||||
auto names = QVector<QString>();
|
||||
|
||||
qCDebug(logI3Ipc) << "There are" << monitors.toVariantList().length() << "monitors";
|
||||
|
||||
for (auto elem: monitors) {
|
||||
auto object = elem.toObject().toVariantMap();
|
||||
auto name = object["name"].toString();
|
||||
|
||||
auto monitorIter = std::find_if(mList.begin(), mList.end(), [name](const I3Monitor* m) {
|
||||
return m->name() == name;
|
||||
});
|
||||
|
||||
auto* monitor = monitorIter == mList.end() ? nullptr : *monitorIter;
|
||||
auto existed = monitor != nullptr;
|
||||
|
||||
if (monitor == nullptr) {
|
||||
monitor = new I3Monitor(this);
|
||||
}
|
||||
|
||||
monitor->updateFromObject(object);
|
||||
|
||||
if (monitor->focused()) {
|
||||
this->setFocusedMonitor(monitor);
|
||||
}
|
||||
|
||||
if (!existed) {
|
||||
this->mMonitors.insertObject(monitor);
|
||||
}
|
||||
|
||||
names.push_back(name);
|
||||
}
|
||||
|
||||
auto removedMonitors = QVector<I3Monitor*>();
|
||||
|
||||
for (auto* monitor: mList) {
|
||||
if (!names.contains(monitor->name())) {
|
||||
removedMonitors.push_back(monitor);
|
||||
}
|
||||
}
|
||||
|
||||
qCDebug(logI3Ipc) << "Removing" << removedMonitors.length() << "disconnected monitors.";
|
||||
|
||||
for (auto* monitor: removedMonitors) {
|
||||
this->mMonitors.removeObject(monitor);
|
||||
delete monitor;
|
||||
}
|
||||
}
|
||||
|
||||
void I3Ipc::onEvent(I3IpcEvent* event) {
|
||||
switch (event->mCode) {
|
||||
case EventCode::Workspace: this->handleWorkspaceEvent(event); return;
|
||||
case EventCode::Output:
|
||||
/// I3 only sends an "unspecified" event, so we have to query the data changes ourselves
|
||||
qCInfo(logI3Ipc) << "Refreshing Monitors...";
|
||||
this->refreshMonitors();
|
||||
return;
|
||||
case EventCode::Subscribe: qCInfo(logI3Ipc) << "Connected to IPC"; return;
|
||||
case EventCode::GetOutputs: this->handleGetOutputsEvent(event); return;
|
||||
case EventCode::GetWorkspaces: this->handleWorkspaceEvent(event); return;
|
||||
case EventCode::RunCommand: I3Ipc::handleRunCommand(event); return;
|
||||
case EventCode::Unknown:
|
||||
qCWarning(logI3Ipc) << "Unknown event:" << event->type() << event->data();
|
||||
return;
|
||||
default: qCWarning(logI3Ipc) << "Unhandled event:" << event->type();
|
||||
}
|
||||
}
|
||||
|
||||
void I3Ipc::handleRunCommand(I3IpcEvent* event) {
|
||||
for (auto r: event->mData.array()) {
|
||||
auto obj = r.toObject();
|
||||
const bool success = obj["success"].toBool();
|
||||
|
||||
if (!success) {
|
||||
const QString error = obj["error"].toString();
|
||||
qCWarning(logI3Ipc) << "Error occured while running command:" << error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void I3Ipc::handleWorkspaceEvent(I3IpcEvent* event) {
|
||||
// If a workspace doesn't exist, and is being switch to, no focus change event is emited,
|
||||
// only the init one, which does not contain the previously focused workspace
|
||||
auto change = event->mData["change"];
|
||||
|
||||
if (change == "init") {
|
||||
qCInfo(logI3IpcEvents) << "New workspace has been created";
|
||||
|
||||
auto workspaceData = event->mData["current"];
|
||||
|
||||
auto* workspace = this->findWorkspaceByID(workspaceData["id"].toInt(-1));
|
||||
|
||||
if (workspace == nullptr) {
|
||||
workspace = new I3Workspace(this);
|
||||
}
|
||||
|
||||
if (workspaceData.isObject()) {
|
||||
workspace->updateFromObject(workspaceData.toObject().toVariantMap());
|
||||
}
|
||||
|
||||
this->mWorkspaces.insertObject(workspace);
|
||||
qCInfo(logI3Ipc) << "Added workspace" << workspace->name() << "to list";
|
||||
} else if (change == "focus") {
|
||||
auto oldData = event->mData["old"];
|
||||
auto newData = event->mData["current"];
|
||||
auto oldName = oldData["name"].toString();
|
||||
auto newName = newData["name"].toString();
|
||||
|
||||
qCInfo(logI3IpcEvents) << "Focus changed: " << oldName << "->" << newName;
|
||||
|
||||
if (auto* oldWorkspace = this->findWorkspaceByName(oldName)) {
|
||||
oldWorkspace->updateFromObject(oldData.toObject().toVariantMap());
|
||||
}
|
||||
|
||||
auto* newWorkspace = this->findWorkspaceByName(newName);
|
||||
|
||||
if (newWorkspace == nullptr) {
|
||||
newWorkspace = new I3Workspace(this);
|
||||
}
|
||||
|
||||
newWorkspace->updateFromObject(newData.toObject().toVariantMap());
|
||||
this->setFocusedWorkspace(newWorkspace);
|
||||
} else if (change == "empty") {
|
||||
auto name = event->mData["current"]["name"].toString();
|
||||
|
||||
auto* oldWorkspace = this->findWorkspaceByName(name);
|
||||
|
||||
if (oldWorkspace != nullptr) {
|
||||
qCInfo(logI3Ipc) << "Deleting" << oldWorkspace->id() << name;
|
||||
|
||||
if (this->mFocusedWorkspace == oldWorkspace) {
|
||||
this->setFocusedWorkspace(nullptr);
|
||||
}
|
||||
|
||||
this->workspaces()->removeObject(oldWorkspace);
|
||||
|
||||
delete oldWorkspace;
|
||||
} else {
|
||||
qCInfo(logI3Ipc) << "Workspace" << name << "has already been deleted";
|
||||
}
|
||||
} else if (change == "move" || change == "rename" || change == "urgent") {
|
||||
auto name = event->mData["current"]["name"].toString();
|
||||
|
||||
auto* workspace = this->findWorkspaceByName(name);
|
||||
|
||||
if (workspace != nullptr) {
|
||||
auto data = event->mData["current"].toObject().toVariantMap();
|
||||
|
||||
workspace->updateFromObject(data);
|
||||
} else {
|
||||
qCWarning(logI3Ipc) << "Workspace" << name << "doesn't exist";
|
||||
}
|
||||
} else if (change == "reload") {
|
||||
qCInfo(logI3Ipc) << "Refreshing Workspaces...";
|
||||
this->refreshWorkspaces();
|
||||
}
|
||||
}
|
||||
|
||||
I3Monitor* I3Ipc::monitorFor(QuickshellScreenInfo* screen) {
|
||||
if (screen == nullptr) return nullptr;
|
||||
|
||||
return this->findMonitorByName(screen->name());
|
||||
}
|
||||
|
||||
I3Workspace* I3Ipc::findWorkspaceByID(qint32 id) {
|
||||
auto list = this->mWorkspaces.valueList();
|
||||
auto workspaceIter =
|
||||
std::find_if(list.begin(), list.end(), [id](const I3Workspace* m) { return m->id() == id; });
|
||||
|
||||
return workspaceIter == list.end() ? nullptr : *workspaceIter;
|
||||
}
|
||||
|
||||
I3Workspace* I3Ipc::findWorkspaceByName(const QString& name) {
|
||||
auto list = this->mWorkspaces.valueList();
|
||||
auto workspaceIter = std::find_if(list.begin(), list.end(), [name](const I3Workspace* m) {
|
||||
return m->name() == name;
|
||||
});
|
||||
|
||||
return workspaceIter == list.end() ? nullptr : *workspaceIter;
|
||||
}
|
||||
|
||||
I3Monitor* I3Ipc::findMonitorByName(const QString& name) {
|
||||
auto list = this->mMonitors.valueList();
|
||||
auto monitorIter = std::find_if(list.begin(), list.end(), [name](const I3Monitor* m) {
|
||||
return m->name() == name;
|
||||
});
|
||||
|
||||
return monitorIter == list.end() ? nullptr : *monitorIter;
|
||||
}
|
||||
|
||||
ObjectModel<I3Monitor>* I3Ipc::monitors() { return &this->mMonitors; }
|
||||
ObjectModel<I3Workspace>* I3Ipc::workspaces() { return &this->mWorkspaces; }
|
||||
|
||||
QString I3IpcEvent::type() const { return I3IpcEvent::eventToString(this->mCode); }
|
||||
QString I3IpcEvent::data() const { return QString::fromUtf8(this->mData.toJson()); }
|
||||
|
||||
EventCode I3IpcEvent::intToEvent(quint32 raw) {
|
||||
if ((EventCode::Workspace <= raw && raw <= EventCode::Input)
|
||||
|| (EventCode::RunCommand <= raw && raw <= EventCode::GetTree))
|
||||
{
|
||||
return static_cast<EventCode>(raw);
|
||||
} else {
|
||||
return EventCode::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
QString I3IpcEvent::eventToString(EventCode event) {
|
||||
switch (event) {
|
||||
case EventCode::RunCommand: return "run_command"; break;
|
||||
case EventCode::GetWorkspaces: return "get_workspaces"; break;
|
||||
case EventCode::Subscribe: return "subscribe"; break;
|
||||
case EventCode::GetOutputs: return "get_outputs"; break;
|
||||
case EventCode::GetTree: return "get_tree"; break;
|
||||
|
||||
case EventCode::Output: return "output"; break;
|
||||
case EventCode::Workspace: return "workspace"; break;
|
||||
case EventCode::Mode: return "mode"; break;
|
||||
case EventCode::Window: return "window"; break;
|
||||
case EventCode::BarconfigUpdate: return "barconfig_update"; break;
|
||||
case EventCode::Binding: return "binding"; break;
|
||||
case EventCode::Shutdown: return "shutdown"; break;
|
||||
case EventCode::Tick: return "tick"; break;
|
||||
case EventCode::BarStateUpdate: return "bar_state_update"; break;
|
||||
case EventCode::Input: return "input"; break;
|
||||
|
||||
case EventCode::Unknown: return "unknown"; break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace qs::i3::ipc
|
151
src/x11/i3/ipc/connection.hpp
Normal file
151
src/x11/i3/ipc/connection.hpp
Normal file
|
@ -0,0 +1,151 @@
|
|||
#pragma once
|
||||
|
||||
#include <qbytearrayview.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qjsonobject.h>
|
||||
#include <qlocalsocket.h>
|
||||
#include <qobject.h>
|
||||
#include <qqml.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../../core/model.hpp"
|
||||
#include "../../../core/qmlscreen.hpp"
|
||||
|
||||
namespace qs::i3::ipc {
|
||||
|
||||
class I3Workspace;
|
||||
class I3Monitor;
|
||||
} // namespace qs::i3::ipc
|
||||
|
||||
Q_DECLARE_OPAQUE_POINTER(qs::i3::ipc::I3Workspace*);
|
||||
Q_DECLARE_OPAQUE_POINTER(qs::i3::ipc::I3Monitor*);
|
||||
|
||||
namespace qs::i3::ipc {
|
||||
|
||||
constexpr std::string MAGIC = "i3-ipc";
|
||||
|
||||
enum EventCode {
|
||||
RunCommand = 0,
|
||||
GetWorkspaces = 1,
|
||||
Subscribe = 2,
|
||||
GetOutputs = 3,
|
||||
GetTree = 4,
|
||||
|
||||
Workspace = 0x80000000,
|
||||
Output = 0x80000001,
|
||||
Mode = 0x80000002,
|
||||
Window = 0x80000003,
|
||||
BarconfigUpdate = 0x80000004,
|
||||
Binding = 0x80000005,
|
||||
Shutdown = 0x80000006,
|
||||
Tick = 0x80000007,
|
||||
BarStateUpdate = 0x80000014,
|
||||
Input = 0x80000015,
|
||||
Unknown = 999,
|
||||
};
|
||||
|
||||
using Event = std::tuple<EventCode, QJsonDocument>;
|
||||
|
||||
///! I3/Sway IPC Events
|
||||
/// Emitted by @@I3.rawEvent(s)
|
||||
class I3IpcEvent: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
/// The name of the event
|
||||
Q_PROPERTY(QString type READ type CONSTANT);
|
||||
/// The payload of the event in JSON format.
|
||||
Q_PROPERTY(QString data READ data CONSTANT);
|
||||
|
||||
QML_NAMED_ELEMENT(I3Event);
|
||||
QML_UNCREATABLE("I3IpcEvents cannot be created.");
|
||||
|
||||
public:
|
||||
I3IpcEvent(QObject* parent): QObject(parent) {}
|
||||
|
||||
[[nodiscard]] QString type() const;
|
||||
[[nodiscard]] QString data() const;
|
||||
|
||||
EventCode mCode = EventCode::Unknown;
|
||||
QJsonDocument mData;
|
||||
|
||||
static EventCode intToEvent(uint32_t raw);
|
||||
static QString eventToString(EventCode event);
|
||||
};
|
||||
|
||||
class I3Ipc: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
static I3Ipc* instance();
|
||||
|
||||
[[nodiscard]] QString socketPath() const;
|
||||
|
||||
void makeRequest(const QByteArray& request);
|
||||
void dispatch(const QString& payload);
|
||||
|
||||
static QByteArray buildRequestMessage(EventCode cmd, const QByteArray& payload = QByteArray());
|
||||
|
||||
I3Workspace* findWorkspaceByName(const QString& name);
|
||||
I3Monitor* findMonitorByName(const QString& name);
|
||||
I3Workspace* findWorkspaceByID(qint32 id);
|
||||
|
||||
void setFocusedWorkspace(I3Workspace* workspace);
|
||||
void setFocusedMonitor(I3Monitor* monitor);
|
||||
|
||||
void refreshWorkspaces();
|
||||
void refreshMonitors();
|
||||
|
||||
I3Monitor* monitorFor(QuickshellScreenInfo* screen);
|
||||
|
||||
[[nodiscard]] I3Monitor* focusedMonitor() const;
|
||||
[[nodiscard]] I3Workspace* focusedWorkspace() const;
|
||||
[[nodiscard]] ObjectModel<I3Monitor>* monitors();
|
||||
[[nodiscard]] ObjectModel<I3Workspace>* workspaces();
|
||||
signals:
|
||||
void connected();
|
||||
void rawEvent(I3IpcEvent* event);
|
||||
void focusedWorkspaceChanged();
|
||||
void focusedMonitorChanged();
|
||||
|
||||
private slots:
|
||||
void eventSocketError(QLocalSocket::LocalSocketError error) const;
|
||||
void eventSocketStateChanged(QLocalSocket::LocalSocketState state);
|
||||
void eventSocketReady();
|
||||
void subscribe();
|
||||
|
||||
void onFocusedWorkspaceDestroyed();
|
||||
void onFocusedMonitorDestroyed();
|
||||
|
||||
private:
|
||||
explicit I3Ipc();
|
||||
|
||||
void onEvent(I3IpcEvent* event);
|
||||
|
||||
void handleWorkspaceEvent(I3IpcEvent* event);
|
||||
void handleGetWorkspacesEvent(I3IpcEvent* event);
|
||||
void handleGetOutputsEvent(I3IpcEvent* event);
|
||||
static void handleRunCommand(I3IpcEvent* event);
|
||||
|
||||
void reconnectIPC();
|
||||
|
||||
QVector<std::tuple<EventCode, QJsonDocument>> parseResponse();
|
||||
|
||||
QLocalSocket liveEventSocket;
|
||||
QDataStream liveEventSocketDs;
|
||||
|
||||
QString mSocketPath;
|
||||
|
||||
bool valid = false;
|
||||
|
||||
ObjectModel<I3Monitor> mMonitors {this};
|
||||
ObjectModel<I3Workspace> mWorkspaces {this};
|
||||
|
||||
I3IpcEvent event {this};
|
||||
|
||||
I3Workspace* mFocusedWorkspace = nullptr;
|
||||
I3Monitor* mFocusedMonitor = nullptr;
|
||||
};
|
||||
|
||||
} // namespace qs::i3::ipc
|
111
src/x11/i3/ipc/monitor.cpp
Normal file
111
src/x11/i3/ipc/monitor.cpp
Normal file
|
@ -0,0 +1,111 @@
|
|||
#include "monitor.hpp"
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qstring.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "workspace.hpp"
|
||||
|
||||
namespace qs::i3::ipc {
|
||||
|
||||
qint32 I3Monitor::id() const { return this->mId; };
|
||||
QString I3Monitor::name() const { return this->mName; };
|
||||
bool I3Monitor::power() const { return this->mPower; };
|
||||
I3Workspace* I3Monitor::focusedWorkspace() const { return this->mFocusedWorkspace; };
|
||||
qint32 I3Monitor::x() const { return this->mX; };
|
||||
qint32 I3Monitor::y() const { return this->mY; };
|
||||
qint32 I3Monitor::width() const { return this->mWidth; };
|
||||
qint32 I3Monitor::height() const { return this->mHeight; };
|
||||
qreal I3Monitor::scale() const { return this->mScale; };
|
||||
bool I3Monitor::focused() const { return this->mFocused; };
|
||||
QVariantMap I3Monitor::lastIpcObject() const { return this->mLastIpcObject; };
|
||||
|
||||
void I3Monitor::updateFromObject(const QVariantMap& obj) {
|
||||
auto id = obj.value("id").value<qint32>();
|
||||
auto name = obj.value("name").value<QString>();
|
||||
auto power = obj.value("power").value<bool>();
|
||||
auto activeWorkspaceId = obj.value("current_workspace").value<QString>();
|
||||
auto rect = obj.value("rect").toMap();
|
||||
auto x = rect.value("x").value<qint32>();
|
||||
auto y = rect.value("y").value<qint32>();
|
||||
auto width = rect.value("width").value<qint32>();
|
||||
auto height = rect.value("height").value<qint32>();
|
||||
auto scale = obj.value("scale").value<qreal>();
|
||||
auto focused = obj.value("focused").value<bool>();
|
||||
|
||||
if (id != this->mId) {
|
||||
this->mId = id;
|
||||
emit this->idChanged();
|
||||
}
|
||||
|
||||
if (name != this->mName) {
|
||||
this->mName = name;
|
||||
emit this->nameChanged();
|
||||
}
|
||||
|
||||
if (power != this->mPower) {
|
||||
this->mPower = power;
|
||||
this->powerChanged();
|
||||
}
|
||||
|
||||
if (activeWorkspaceId != this->mFocusedWorkspaceName) {
|
||||
auto* workspace = this->ipc->findWorkspaceByName(activeWorkspaceId);
|
||||
if (activeWorkspaceId.isEmpty() || workspace == nullptr) { // is null when output is disabled
|
||||
this->mFocusedWorkspace = nullptr;
|
||||
this->mFocusedWorkspaceName = "";
|
||||
} else {
|
||||
this->mFocusedWorkspaceName = activeWorkspaceId;
|
||||
this->mFocusedWorkspace = workspace;
|
||||
}
|
||||
emit this->focusedWorkspaceChanged();
|
||||
};
|
||||
|
||||
if (x != this->mX) {
|
||||
this->mX = x;
|
||||
emit this->xChanged();
|
||||
}
|
||||
|
||||
if (y != this->mY) {
|
||||
this->mY = y;
|
||||
emit this->yChanged();
|
||||
}
|
||||
|
||||
if (width != this->mWidth) {
|
||||
this->mWidth = width;
|
||||
emit this->widthChanged();
|
||||
}
|
||||
|
||||
if (height != this->mHeight) {
|
||||
this->mHeight = height;
|
||||
emit this->heightChanged();
|
||||
}
|
||||
|
||||
if (scale != this->mScale) {
|
||||
this->mScale = scale;
|
||||
emit this->scaleChanged();
|
||||
}
|
||||
|
||||
if (focused != this->mFocused) {
|
||||
this->mFocused = focused;
|
||||
emit this->focusedChanged();
|
||||
}
|
||||
|
||||
if (obj != this->mLastIpcObject) {
|
||||
this->mLastIpcObject = obj;
|
||||
emit this->lastIpcObjectChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void I3Monitor::setFocus(bool focused) {
|
||||
this->mFocused = focused;
|
||||
emit this->focusedChanged();
|
||||
}
|
||||
|
||||
void I3Monitor::setFocusedWorkspace(I3Workspace* workspace) {
|
||||
this->mFocusedWorkspace = workspace;
|
||||
this->mFocusedWorkspaceName = workspace->name();
|
||||
emit this->focusedWorkspaceChanged();
|
||||
};
|
||||
|
||||
} // namespace qs::i3::ipc
|
100
src/x11/i3/ipc/monitor.hpp
Normal file
100
src/x11/i3/ipc/monitor.hpp
Normal file
|
@ -0,0 +1,100 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
|
||||
#include "connection.hpp"
|
||||
|
||||
namespace qs::i3::ipc {
|
||||
|
||||
///! I3/Sway monitors
|
||||
class I3Monitor: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
/// The ID of this monitor
|
||||
Q_PROPERTY(qint32 id READ id NOTIFY idChanged);
|
||||
/// The name of this monitor
|
||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged);
|
||||
/// Wether this monitor is turned on or not
|
||||
Q_PROPERTY(bool power READ power NOTIFY powerChanged);
|
||||
|
||||
/// The current workspace
|
||||
Q_PROPERTY(qs::i3::ipc::I3Workspace* focusedWorkspace READ focusedWorkspace NOTIFY
|
||||
focusedWorkspaceChanged);
|
||||
|
||||
/// The X coordinate of this monitor inside the monitor layout
|
||||
Q_PROPERTY(qint32 x READ x NOTIFY xChanged);
|
||||
|
||||
/// The Y coordinate of this monitor inside the monitor layout
|
||||
Q_PROPERTY(qint32 y READ y NOTIFY yChanged);
|
||||
|
||||
/// The width in pixels of this monitor
|
||||
Q_PROPERTY(qint32 width READ width NOTIFY widthChanged);
|
||||
|
||||
/// The height in pixels of this monitor
|
||||
Q_PROPERTY(qint32 height READ height NOTIFY heightChanged);
|
||||
|
||||
/// The scaling factor of this monitor, 1 means it runs at native resolution
|
||||
Q_PROPERTY(qreal scale READ scale NOTIFY scaleChanged);
|
||||
|
||||
/// Whether this monitor is currently in focus
|
||||
Q_PROPERTY(bool focused READ focused NOTIFY focusedChanged);
|
||||
|
||||
/// Last JSON returned for this monitor, as a JavaScript object.
|
||||
///
|
||||
/// This updates every time Quickshell receives an `output` event from i3/Sway
|
||||
Q_PROPERTY(QVariantMap lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged);
|
||||
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("I3Monitors must be retrieved from the I3Ipc object.");
|
||||
|
||||
public:
|
||||
explicit I3Monitor(I3Ipc* ipc): QObject(ipc), ipc(ipc) {}
|
||||
|
||||
[[nodiscard]] qint32 id() const;
|
||||
[[nodiscard]] QString name() const;
|
||||
[[nodiscard]] bool power() const;
|
||||
[[nodiscard]] I3Workspace* focusedWorkspace() const;
|
||||
[[nodiscard]] qint32 x() const;
|
||||
[[nodiscard]] qint32 y() const;
|
||||
[[nodiscard]] qint32 width() const;
|
||||
[[nodiscard]] qint32 height() const;
|
||||
[[nodiscard]] qreal scale() const;
|
||||
[[nodiscard]] bool focused() const;
|
||||
[[nodiscard]] QVariantMap lastIpcObject() const;
|
||||
|
||||
void updateFromObject(const QVariantMap& obj);
|
||||
|
||||
void setFocusedWorkspace(I3Workspace* workspace);
|
||||
void setFocus(bool focus);
|
||||
signals:
|
||||
void idChanged();
|
||||
void nameChanged();
|
||||
void powerChanged();
|
||||
void focusedWorkspaceChanged();
|
||||
void xChanged();
|
||||
void yChanged();
|
||||
void widthChanged();
|
||||
void heightChanged();
|
||||
void scaleChanged();
|
||||
void lastIpcObjectChanged();
|
||||
void focusedChanged();
|
||||
|
||||
private:
|
||||
I3Ipc* ipc;
|
||||
|
||||
qint32 mId = -1;
|
||||
QString mName;
|
||||
bool mPower = false;
|
||||
qint32 mX = 0;
|
||||
qint32 mY = 0;
|
||||
qint32 mWidth = 0;
|
||||
qint32 mHeight = 0;
|
||||
qreal mScale = 1;
|
||||
bool mFocused = false;
|
||||
QVariantMap mLastIpcObject;
|
||||
|
||||
I3Workspace* mFocusedWorkspace = nullptr;
|
||||
QString mFocusedWorkspaceName; // use for faster change detection
|
||||
};
|
||||
|
||||
} // namespace qs::i3::ipc
|
55
src/x11/i3/ipc/qml.cpp
Normal file
55
src/x11/i3/ipc/qml.cpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
#include "qml.hpp"
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qstring.h>
|
||||
|
||||
#include "../../../core/model.hpp"
|
||||
#include "../../../core/qmlscreen.hpp"
|
||||
#include "connection.hpp"
|
||||
#include "workspace.hpp"
|
||||
|
||||
namespace qs::i3::ipc {
|
||||
|
||||
I3IpcQml::I3IpcQml() {
|
||||
auto* instance = I3Ipc::instance();
|
||||
|
||||
QObject::connect(instance, &I3Ipc::rawEvent, this, &I3IpcQml::rawEvent);
|
||||
QObject::connect(instance, &I3Ipc::connected, this, &I3IpcQml::connected);
|
||||
QObject::connect(
|
||||
instance,
|
||||
&I3Ipc::focusedWorkspaceChanged,
|
||||
this,
|
||||
&I3IpcQml::focusedWorkspaceChanged
|
||||
);
|
||||
QObject::connect(instance, &I3Ipc::focusedMonitorChanged, this, &I3IpcQml::focusedMonitorChanged);
|
||||
}
|
||||
|
||||
void I3IpcQml::dispatch(const QString& request) { I3Ipc::instance()->dispatch(request); }
|
||||
|
||||
void I3IpcQml::refreshMonitors() { I3Ipc::instance()->refreshMonitors(); }
|
||||
|
||||
void I3IpcQml::refreshWorkspaces() { I3Ipc::instance()->refreshWorkspaces(); }
|
||||
|
||||
QString I3IpcQml::socketPath() { return I3Ipc::instance()->socketPath(); }
|
||||
|
||||
ObjectModel<I3Monitor>* I3IpcQml::monitors() { return I3Ipc::instance()->monitors(); }
|
||||
|
||||
ObjectModel<I3Workspace>* I3IpcQml::workspaces() { return I3Ipc::instance()->workspaces(); }
|
||||
|
||||
I3Workspace* I3IpcQml::focusedWorkspace() { return I3Ipc::instance()->focusedWorkspace(); }
|
||||
|
||||
I3Monitor* I3IpcQml::focusedMonitor() { return I3Ipc::instance()->focusedMonitor(); }
|
||||
|
||||
I3Workspace* I3IpcQml::findWorkspaceByName(const QString& name) {
|
||||
return I3Ipc::instance()->findWorkspaceByName(name);
|
||||
}
|
||||
|
||||
I3Monitor* I3IpcQml::findMonitorByName(const QString& name) {
|
||||
return I3Ipc::instance()->findMonitorByName(name);
|
||||
}
|
||||
|
||||
I3Monitor* I3IpcQml::monitorFor(QuickshellScreenInfo* screen) {
|
||||
return I3Ipc::instance()->monitorFor(screen);
|
||||
}
|
||||
|
||||
} // namespace qs::i3::ipc
|
74
src/x11/i3/ipc/qml.hpp
Normal file
74
src/x11/i3/ipc/qml.hpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#include <qjsonarray.h>
|
||||
#include <qobject.h>
|
||||
|
||||
#include "../../../core/doc.hpp"
|
||||
#include "../../../core/qmlscreen.hpp"
|
||||
#include "connection.hpp"
|
||||
|
||||
namespace qs::i3::ipc {
|
||||
|
||||
///! I3/Sway IPC integration
|
||||
class I3IpcQml: public QObject {
|
||||
Q_OBJECT;
|
||||
// clang-format off
|
||||
/// Path to the I3 socket
|
||||
Q_PROPERTY(QString socketPath READ socketPath CONSTANT);
|
||||
|
||||
Q_PROPERTY(qs::i3::ipc::I3Workspace* focusedWorkspace READ focusedWorkspace NOTIFY focusedWorkspaceChanged);
|
||||
Q_PROPERTY(qs::i3::ipc::I3Monitor* focusedMonitor READ focusedMonitor NOTIFY focusedMonitorChanged);
|
||||
/// All I3 monitors.
|
||||
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::i3::ipc::I3Monitor>*);
|
||||
Q_PROPERTY(UntypedObjectModel* monitors READ monitors CONSTANT);
|
||||
/// All I3 workspaces.
|
||||
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::i3::ipc::I3Workspace>*);
|
||||
Q_PROPERTY(UntypedObjectModel* workspaces READ workspaces CONSTANT);
|
||||
// clang-format on
|
||||
QML_NAMED_ELEMENT(I3);
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
explicit I3IpcQml();
|
||||
|
||||
/// Execute an [I3/Sway command](https://i3wm.org/docs/userguide.html#list_of_commands)
|
||||
Q_INVOKABLE static void dispatch(const QString& request);
|
||||
|
||||
/// Refresh monitor information.
|
||||
Q_INVOKABLE static void refreshMonitors();
|
||||
|
||||
/// Refresh workspace information.
|
||||
Q_INVOKABLE static void refreshWorkspaces();
|
||||
|
||||
/// Find an I3Workspace using its name, returns null if the workspace doesn't exist.
|
||||
Q_INVOKABLE static I3Workspace* findWorkspaceByName(const QString& name);
|
||||
|
||||
/// Find an I3Monitor using its name, returns null if the monitor doesn't exist.
|
||||
Q_INVOKABLE static I3Monitor* findMonitorByName(const QString& name);
|
||||
|
||||
/// Return the i3/Sway monitor associated with `screen`
|
||||
Q_INVOKABLE static I3Monitor* monitorFor(QuickshellScreenInfo* screen);
|
||||
|
||||
/// The path to the I3 or Sway socket currently being used
|
||||
[[nodiscard]] static QString socketPath();
|
||||
|
||||
/// All I3Monitors
|
||||
[[nodiscard]] static ObjectModel<I3Monitor>* monitors();
|
||||
|
||||
/// All I3Workspaces
|
||||
[[nodiscard]] static ObjectModel<I3Workspace>* workspaces();
|
||||
|
||||
/// The currently focused Workspace
|
||||
[[nodiscard]] static I3Workspace* focusedWorkspace();
|
||||
|
||||
/// The currently focused Monitor
|
||||
[[nodiscard]] static I3Monitor* focusedMonitor();
|
||||
|
||||
signals:
|
||||
void rawEvent(I3IpcEvent* event);
|
||||
void connected();
|
||||
void focusedWorkspaceChanged();
|
||||
void focusedMonitorChanged();
|
||||
};
|
||||
|
||||
} // namespace qs::i3::ipc
|
73
src/x11/i3/ipc/workspace.cpp
Normal file
73
src/x11/i3/ipc/workspace.cpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
#include "workspace.hpp"
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qstring.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "monitor.hpp"
|
||||
|
||||
namespace qs::i3::ipc {
|
||||
|
||||
qint32 I3Workspace ::id() const { return this->mId; }
|
||||
QString I3Workspace::name() const { return this->mName; }
|
||||
qint32 I3Workspace ::num() const { return this->mNum; }
|
||||
bool I3Workspace ::urgent() const { return this->mUrgent; }
|
||||
bool I3Workspace::focused() const { return this->mFocused; }
|
||||
I3Monitor* I3Workspace::monitor() const { return this->mMonitor; }
|
||||
QVariantMap I3Workspace::lastIpcObject() const { return this->mLastIpcObject; }
|
||||
|
||||
void I3Workspace::updateFromObject(const QVariantMap& obj) {
|
||||
auto id = obj.value("id").value<qint32>();
|
||||
auto name = obj.value("name").value<QString>();
|
||||
auto num = obj.value("num").value<qint32>();
|
||||
auto urgent = obj.value("urgent").value<bool>();
|
||||
auto focused = obj.value("focused").value<bool>();
|
||||
auto monitorName = obj.value("output").value<QString>();
|
||||
|
||||
if (id != this->mId) {
|
||||
this->mId = id;
|
||||
emit this->idChanged();
|
||||
}
|
||||
|
||||
if (name != this->mName) {
|
||||
this->mName = name;
|
||||
emit this->nameChanged();
|
||||
}
|
||||
|
||||
if (num != this->mNum) {
|
||||
this->mNum = num;
|
||||
emit this->numChanged();
|
||||
}
|
||||
|
||||
if (urgent != this->mUrgent) {
|
||||
this->mUrgent = urgent;
|
||||
emit this->urgentChanged();
|
||||
}
|
||||
|
||||
if (focused != this->mFocused) {
|
||||
this->mFocused = focused;
|
||||
emit this->focusedChanged();
|
||||
}
|
||||
|
||||
if (obj != this->mLastIpcObject) {
|
||||
this->mLastIpcObject = obj;
|
||||
emit this->lastIpcObjectChanged();
|
||||
}
|
||||
|
||||
if (monitorName != this->mMonitorName) {
|
||||
auto* monitor = this->ipc->findMonitorByName(monitorName);
|
||||
if (monitorName.isEmpty() || monitor == nullptr) { // is null when output is disabled
|
||||
this->mMonitor = nullptr;
|
||||
this->mMonitorName = "";
|
||||
} else {
|
||||
this->mMonitorName = monitorName;
|
||||
this->mMonitor = monitor;
|
||||
}
|
||||
emit this->monitorChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void I3Workspace::setFocus(bool focus) { this->mFocused = focus; }
|
||||
|
||||
} // namespace qs::i3::ipc
|
73
src/x11/i3/ipc/workspace.hpp
Normal file
73
src/x11/i3/ipc/workspace.hpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
#pragma once
|
||||
|
||||
#include "connection.hpp"
|
||||
|
||||
namespace qs::i3::ipc {
|
||||
|
||||
///! I3/Sway workspaces
|
||||
class I3Workspace: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
/// The ID of this workspace, it is unique for i3/Sway launch
|
||||
Q_PROPERTY(qint32 id READ id NOTIFY idChanged);
|
||||
|
||||
/// The name of this workspace
|
||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged);
|
||||
|
||||
/// The number of this workspace
|
||||
Q_PROPERTY(qint32 num READ num NOTIFY numChanged);
|
||||
|
||||
/// If a window in this workspace has an urgent notification
|
||||
Q_PROPERTY(bool urgent READ urgent NOTIFY urgentChanged);
|
||||
|
||||
/// If this workspace is the one currently in focus
|
||||
Q_PROPERTY(bool focused READ focused NOTIFY focusedChanged);
|
||||
|
||||
/// The monitor this workspace is being displayed on
|
||||
Q_PROPERTY(qs::i3::ipc::I3Monitor* monitor READ monitor NOTIFY monitorChanged);
|
||||
|
||||
/// Last JSON returned for this workspace, as a JavaScript object.
|
||||
///
|
||||
/// This updates every time we receive a `workspace` event from i3/Sway
|
||||
Q_PROPERTY(QVariantMap lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged);
|
||||
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("I3Workspaces must be retrieved from the I3 object.");
|
||||
|
||||
public:
|
||||
I3Workspace(qs::i3::ipc::I3Ipc* ipc): QObject(ipc), ipc(ipc) {}
|
||||
|
||||
[[nodiscard]] qint32 id() const;
|
||||
[[nodiscard]] QString name() const;
|
||||
[[nodiscard]] qint32 num() const;
|
||||
[[nodiscard]] bool urgent() const;
|
||||
[[nodiscard]] bool focused() const;
|
||||
[[nodiscard]] I3Monitor* monitor() const;
|
||||
[[nodiscard]] QVariantMap lastIpcObject() const;
|
||||
|
||||
void updateFromObject(const QVariantMap& obj);
|
||||
void setFocus(bool focus);
|
||||
|
||||
signals:
|
||||
void idChanged();
|
||||
void nameChanged();
|
||||
void urgentChanged();
|
||||
void focusedChanged();
|
||||
void numChanged();
|
||||
void monitorChanged();
|
||||
void lastIpcObjectChanged();
|
||||
|
||||
private:
|
||||
I3Ipc* ipc;
|
||||
|
||||
qint32 mId = -1;
|
||||
QString mName;
|
||||
qint32 mNum = -1;
|
||||
bool mFocused = false;
|
||||
bool mUrgent = false;
|
||||
|
||||
QVariantMap mLastIpcObject;
|
||||
I3Monitor* mMonitor = nullptr;
|
||||
QString mMonitorName;
|
||||
};
|
||||
} // namespace qs::i3::ipc
|
9
src/x11/i3/module.md
Normal file
9
src/x11/i3/module.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
name = "Quickshell.I3"
|
||||
description = "I3 specific Quickshell types"
|
||||
headers = [
|
||||
"ipc/connection.hpp",
|
||||
"ipc/qml.hpp",
|
||||
"ipc/workspace.hpp",
|
||||
"ipc/monitor.hpp",
|
||||
]
|
||||
-----
|
Loading…
Reference in a new issue