forked from quickshell/quickshell
		
	hyprland/ipc: add hyprland ipc
Only monitors and workspaces are fully tracked for now.
This commit is contained in:
		
							parent
							
								
									be237b6ab5
								
							
						
					
					
						commit
						d14ca70984
					
				
					 12 changed files with 1171 additions and 0 deletions
				
			
		| 
						 | 
					@ -16,6 +16,7 @@ option(WAYLAND_WLR_LAYERSHELL "Support the zwlr_layer_shell_v1 wayland protocol"
 | 
				
			||||||
option(WAYLAND_SESSION_LOCK "Support the ext_session_lock_v1 wayland protocol" ON)
 | 
					option(WAYLAND_SESSION_LOCK "Support the ext_session_lock_v1 wayland protocol" ON)
 | 
				
			||||||
option(X11 "Enable X11 support" ON)
 | 
					option(X11 "Enable X11 support" ON)
 | 
				
			||||||
option(HYPRLAND "Support hyprland specific features" ON)
 | 
					option(HYPRLAND "Support hyprland specific features" ON)
 | 
				
			||||||
 | 
					option(HYPRLAND_IPC "Hyprland IPC" ON)
 | 
				
			||||||
option(HYPRLAND_GLOBAL_SHORTCUTS "Hyprland Global Shortcuts" ON)
 | 
					option(HYPRLAND_GLOBAL_SHORTCUTS "Hyprland Global Shortcuts" ON)
 | 
				
			||||||
option(HYPRLAND_FOCUS_GRAB "Hyprland Focus Grabbing" ON)
 | 
					option(HYPRLAND_FOCUS_GRAB "Hyprland Focus Grabbing" ON)
 | 
				
			||||||
option(SERVICE_STATUS_NOTIFIER "StatusNotifierItem service" ON)
 | 
					option(SERVICE_STATUS_NOTIFIER "StatusNotifierItem service" ON)
 | 
				
			||||||
| 
						 | 
					@ -38,6 +39,7 @@ message(STATUS "    PipeWire: ${SERVICE_PIPEWIRE}")
 | 
				
			||||||
message(STATUS "    Mpris: ${SERVICE_MPRIS}")
 | 
					message(STATUS "    Mpris: ${SERVICE_MPRIS}")
 | 
				
			||||||
message(STATUS "  Hyprland: ${HYPRLAND}")
 | 
					message(STATUS "  Hyprland: ${HYPRLAND}")
 | 
				
			||||||
if (HYPRLAND)
 | 
					if (HYPRLAND)
 | 
				
			||||||
 | 
						message(STATUS "    IPC: ${HYPRLAND_IPC}")
 | 
				
			||||||
	message(STATUS "    Focus Grabbing: ${HYPRLAND_FOCUS_GRAB}")
 | 
						message(STATUS "    Focus Grabbing: ${HYPRLAND_FOCUS_GRAB}")
 | 
				
			||||||
	message(STATUS "    Global Shortcuts: ${HYPRLAND_GLOBAL_SHORTCUTS}")
 | 
						message(STATUS "    Global Shortcuts: ${HYPRLAND_GLOBAL_SHORTCUTS}")
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,11 @@ target_link_libraries(quickshell-hyprland PRIVATE ${QT_DEPS})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set(HYPRLAND_MODULES)
 | 
					set(HYPRLAND_MODULES)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (HYPRLAND_IPC)
 | 
				
			||||||
 | 
						add_subdirectory(ipc)
 | 
				
			||||||
 | 
						list(APPEND HYPRLAND_MODULES Quickshell.Hyprland._Ipc)
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (HYPRLAND_FOCUS_GRAB)
 | 
					if (HYPRLAND_FOCUS_GRAB)
 | 
				
			||||||
	add_subdirectory(focus_grab)
 | 
						add_subdirectory(focus_grab)
 | 
				
			||||||
	list(APPEND HYPRLAND_MODULES Quickshell.Hyprland._FocusGrab)
 | 
						list(APPEND HYPRLAND_MODULES Quickshell.Hyprland._FocusGrab)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/wayland/hyprland/ipc/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/wayland/hyprland/ipc/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					qt_add_library(quickshell-hyprland-ipc STATIC
 | 
				
			||||||
 | 
						connection.cpp
 | 
				
			||||||
 | 
						monitor.cpp
 | 
				
			||||||
 | 
						workspace.cpp
 | 
				
			||||||
 | 
						qml.cpp
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qt_add_qml_module(quickshell-hyprland-ipc
 | 
				
			||||||
 | 
						URI Quickshell.Hyprland._Ipc
 | 
				
			||||||
 | 
						VERSION 0.1
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					target_link_libraries(quickshell-hyprland-ipc PRIVATE ${QT_DEPS})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qs_pch(quickshell-hyprland-ipc)
 | 
				
			||||||
 | 
					qs_pch(quickshell-hyprland-ipcplugin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					target_link_libraries(quickshell PRIVATE quickshell-hyprland-ipcplugin)
 | 
				
			||||||
							
								
								
									
										542
									
								
								src/wayland/hyprland/ipc/connection.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										542
									
								
								src/wayland/hyprland/ipc/connection.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,542 @@
 | 
				
			||||||
 | 
					#include "connection.hpp"
 | 
				
			||||||
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					#include <functional>
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qbytearrayview.h>
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qdir.h>
 | 
				
			||||||
 | 
					#include <qfileinfo.h>
 | 
				
			||||||
 | 
					#include <qjsonarray.h>
 | 
				
			||||||
 | 
					#include <qjsondocument.h>
 | 
				
			||||||
 | 
					#include <qjsonobject.h>
 | 
				
			||||||
 | 
					#include <qlocalsocket.h>
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qtenvironmentvariables.h>
 | 
				
			||||||
 | 
					#include <qtimer.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					#include <qvariant.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../core/model.hpp"
 | 
				
			||||||
 | 
					#include "../../../core/qmlscreen.hpp"
 | 
				
			||||||
 | 
					#include "monitor.hpp"
 | 
				
			||||||
 | 
					#include "workspace.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::hyprland::ipc {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Q_LOGGING_CATEGORY(logHyprlandIpc, "quickshell.hyprland.ipc", QtWarningMsg);
 | 
				
			||||||
 | 
					Q_LOGGING_CATEGORY(logHyprlandIpcEvents, "quickshell.hyprland.ipc.events", QtWarningMsg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HyprlandIpc::HyprlandIpc() {
 | 
				
			||||||
 | 
						auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE");
 | 
				
			||||||
 | 
						if (his.isEmpty()) {
 | 
				
			||||||
 | 
							qWarning() << "$HYPRLAND_INSTANCE_SIGNATURE is unset. Cannot connect to hyprland.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto runtimeDir = qEnvironmentVariable("XDG_RUNTIME_DIR");
 | 
				
			||||||
 | 
						auto hyprlandDir = runtimeDir + "/hypr/" + his;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!QFileInfo(hyprlandDir).isDir()) {
 | 
				
			||||||
 | 
							hyprlandDir = "/tmp/hypr/" + his;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!QFileInfo(hyprlandDir).isDir()) {
 | 
				
			||||||
 | 
							qWarning() << "Unable to find hyprland socket. Cannot connect to hyprland.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->mRequestSocketPath = hyprlandDir + "/.socket.sock";
 | 
				
			||||||
 | 
						this->mEventSocketPath = hyprlandDir + "/.socket2.sock";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// clang-format off
 | 
				
			||||||
 | 
						QObject::connect(&this->eventSocket, &QLocalSocket::errorOccurred, this, &HyprlandIpc::eventSocketError);
 | 
				
			||||||
 | 
						QObject::connect(&this->eventSocket, &QLocalSocket::stateChanged, this, &HyprlandIpc::eventSocketStateChanged);
 | 
				
			||||||
 | 
						QObject::connect(&this->eventSocket, &QLocalSocket::readyRead, this, &HyprlandIpc::eventSocketReady);
 | 
				
			||||||
 | 
						// clang-format on
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Sockets don't appear to be able to send data in the first event loop
 | 
				
			||||||
 | 
						// cycle of the program, so delay it by one. No idea why this is the case.
 | 
				
			||||||
 | 
						QTimer::singleShot(0, [this]() {
 | 
				
			||||||
 | 
							this->eventSocket.connectToServer(this->mEventSocketPath, QLocalSocket::ReadOnly);
 | 
				
			||||||
 | 
							this->refreshMonitors(true);
 | 
				
			||||||
 | 
							this->refreshWorkspaces(true);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString HyprlandIpc::requestSocketPath() const { return this->mRequestSocketPath; }
 | 
				
			||||||
 | 
					QString HyprlandIpc::eventSocketPath() const { return this->mEventSocketPath; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandIpc::eventSocketError(QLocalSocket::LocalSocketError error) const {
 | 
				
			||||||
 | 
						if (!this->valid) {
 | 
				
			||||||
 | 
							qWarning() << "Unable to connect to hyprland event socket:" << error;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							qWarning() << "Hyprland event socket error:" << error;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandIpc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) {
 | 
				
			||||||
 | 
						if (state == QLocalSocket::ConnectedState) {
 | 
				
			||||||
 | 
							qCInfo(logHyprlandIpc) << "Hyprland event socket connected.";
 | 
				
			||||||
 | 
							emit this->connected();
 | 
				
			||||||
 | 
						} else if (state == QLocalSocket::UnconnectedState && this->valid) {
 | 
				
			||||||
 | 
							qCWarning(logHyprlandIpc) << "Hyprland event socket disconnected.";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->valid = state == QLocalSocket::ConnectedState;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandIpc::eventSocketReady() {
 | 
				
			||||||
 | 
						while (true) {
 | 
				
			||||||
 | 
							auto rawEvent = this->eventSocket.readLine();
 | 
				
			||||||
 | 
							if (rawEvent.isEmpty()) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// remove trailing \n
 | 
				
			||||||
 | 
							rawEvent.truncate(rawEvent.length() - 1);
 | 
				
			||||||
 | 
							auto splitIdx = rawEvent.indexOf(">>");
 | 
				
			||||||
 | 
							auto event = QByteArrayView(rawEvent.data(), splitIdx);
 | 
				
			||||||
 | 
							auto data = QByteArrayView(
 | 
				
			||||||
 | 
							    rawEvent.data() + splitIdx + 2,     // NOLINT
 | 
				
			||||||
 | 
							    rawEvent.data() + rawEvent.length() // NOLINT
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
							qCDebug(logHyprlandIpcEvents) << "Received event:" << rawEvent << "parsed as" << event << data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this->event.name = event;
 | 
				
			||||||
 | 
							this->event.data = data;
 | 
				
			||||||
 | 
							this->onEvent(&this->event);
 | 
				
			||||||
 | 
							emit this->rawEvent(&this->event);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandIpc::makeRequest(
 | 
				
			||||||
 | 
					    const QByteArray& request,
 | 
				
			||||||
 | 
					    const std::function<void(bool, QByteArray)>& callback
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						auto* requestSocket = new QLocalSocket(this);
 | 
				
			||||||
 | 
						qCDebug(logHyprlandIpc) << "Making request:" << request;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto connectedCallback = [this, request, requestSocket, callback]() {
 | 
				
			||||||
 | 
							auto responseCallback = [requestSocket, callback]() {
 | 
				
			||||||
 | 
								auto response = requestSocket->readAll();
 | 
				
			||||||
 | 
								callback(true, std::move(response));
 | 
				
			||||||
 | 
								delete requestSocket;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							QObject::connect(requestSocket, &QLocalSocket::readyRead, this, responseCallback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							requestSocket->write(request);
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto errorCallback = [=](QLocalSocket::LocalSocketError error) {
 | 
				
			||||||
 | 
							qCWarning(logHyprlandIpc) << "Error making request:" << error << "request:" << request;
 | 
				
			||||||
 | 
							requestSocket->deleteLater();
 | 
				
			||||||
 | 
							callback(false, {});
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(requestSocket, &QLocalSocket::connected, this, connectedCallback);
 | 
				
			||||||
 | 
						QObject::connect(requestSocket, &QLocalSocket::errorOccurred, this, errorCallback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requestSocket->connectToServer(this->mRequestSocketPath);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandIpc::dispatch(const QString& request) {
 | 
				
			||||||
 | 
						this->makeRequest(
 | 
				
			||||||
 | 
						    ("dispatch " + request).toUtf8(),
 | 
				
			||||||
 | 
						    [request](bool success, const QByteArray& response) {
 | 
				
			||||||
 | 
							    if (!success) {
 | 
				
			||||||
 | 
								    qCWarning(logHyprlandIpc) << "Failed to request dispatch of" << request;
 | 
				
			||||||
 | 
								    return;
 | 
				
			||||||
 | 
							    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							    if (response != "ok") {
 | 
				
			||||||
 | 
								    qCWarning(logHyprlandIpc)
 | 
				
			||||||
 | 
								        << "Dispatch request" << request << "failed with error" << response;
 | 
				
			||||||
 | 
							    }
 | 
				
			||||||
 | 
						    }
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ObjectModel<HyprlandMonitor>* HyprlandIpc::monitors() { return &this->mMonitors; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ObjectModel<HyprlandWorkspace>* HyprlandIpc::workspaces() { return &this->mWorkspaces; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QVector<QByteArrayView> HyprlandIpc::parseEventArgs(QByteArrayView event, quint16 count) {
 | 
				
			||||||
 | 
						auto args = QVector<QByteArrayView>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (auto i = 0; i < count - 1; i++) {
 | 
				
			||||||
 | 
							auto splitIdx = event.indexOf(',');
 | 
				
			||||||
 | 
							if (splitIdx == -1) break;
 | 
				
			||||||
 | 
							args.push_back(event.sliced(0, splitIdx));
 | 
				
			||||||
 | 
							event = event.sliced(splitIdx + 1);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!event.isEmpty()) {
 | 
				
			||||||
 | 
							args.push_back(event);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return args;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QVector<QString> HyprlandIpcEvent::parse(qint32 argumentCount) const {
 | 
				
			||||||
 | 
						auto args = QVector<QString>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (auto arg: this->parseView(argumentCount)) {
 | 
				
			||||||
 | 
							args.push_back(QString::fromUtf8(arg));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return args;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QVector<QByteArrayView> HyprlandIpcEvent::parseView(qint32 argumentCount) const {
 | 
				
			||||||
 | 
						return HyprlandIpc::parseEventArgs(this->data, argumentCount);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString HyprlandIpcEvent::nameStr() const { return QString::fromUtf8(this->name); }
 | 
				
			||||||
 | 
					QString HyprlandIpcEvent::dataStr() const { return QString::fromUtf8(this->data); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HyprlandIpc* HyprlandIpc::instance() {
 | 
				
			||||||
 | 
						static HyprlandIpc* instance = nullptr; // NOLINT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (instance == nullptr) {
 | 
				
			||||||
 | 
							instance = new HyprlandIpc();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return instance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
 | 
				
			||||||
 | 
						if (event->name == "configreloaded") {
 | 
				
			||||||
 | 
							this->refreshMonitors(true);
 | 
				
			||||||
 | 
							this->refreshWorkspaces(true);
 | 
				
			||||||
 | 
						} else if (event->name == "monitoraddedv2") {
 | 
				
			||||||
 | 
							auto args = event->parseView(3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto id = args.at(0).toInt();
 | 
				
			||||||
 | 
							auto name = QString::fromUtf8(args.at(1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// hyprland will often reference the monitor before creation, in which case
 | 
				
			||||||
 | 
							// it will already exist.
 | 
				
			||||||
 | 
							auto* monitor = this->findMonitorByName(name, false);
 | 
				
			||||||
 | 
							auto existed = monitor != nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (monitor == nullptr) {
 | 
				
			||||||
 | 
								monitor = new HyprlandMonitor(this);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							qCDebug(logHyprlandIpc) << "Monitor added with id" << id << "name" << name
 | 
				
			||||||
 | 
							                        << "preemptively created:" << existed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							monitor->updateInitial(id, name, QString::fromUtf8(args.at(2)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!existed) {
 | 
				
			||||||
 | 
								this->mMonitors.insertObject(monitor);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// refresh even if it already existed because workspace focus might have changed.
 | 
				
			||||||
 | 
							this->refreshMonitors(false);
 | 
				
			||||||
 | 
						} else if (event->name == "monitorremoved") {
 | 
				
			||||||
 | 
							const auto& mList = this->mMonitors.valueList();
 | 
				
			||||||
 | 
							auto name = QString::fromUtf8(event->data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto monitorIter = std::find_if(mList.begin(), mList.end(), [name](const HyprlandMonitor* m) {
 | 
				
			||||||
 | 
								return m->name() == name;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (monitorIter == mList.end()) {
 | 
				
			||||||
 | 
								qCWarning(logHyprlandIpc) << "Got removal for monitor" << name
 | 
				
			||||||
 | 
								                          << "which was not previously tracked.";
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto index = monitorIter - mList.begin();
 | 
				
			||||||
 | 
							auto* monitor = *monitorIter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							qCDebug(logHyprlandIpc) << "Monitor removed with id" << monitor->id() << "name"
 | 
				
			||||||
 | 
							                        << monitor->name();
 | 
				
			||||||
 | 
							this->mMonitors.removeAt(index);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// delete the monitor object in the next event loop cycle so it's likely to
 | 
				
			||||||
 | 
							// still exist when future events reference it after destruction.
 | 
				
			||||||
 | 
							// If we get to the next cycle and things still reference it (unlikely), nulls
 | 
				
			||||||
 | 
							// can make it to the frontend.
 | 
				
			||||||
 | 
							monitor->deleteLater();
 | 
				
			||||||
 | 
						} else if (event->name == "createworkspacev2") {
 | 
				
			||||||
 | 
							auto args = event->parseView(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto id = args.at(0).toInt();
 | 
				
			||||||
 | 
							auto name = QString::fromUtf8(args.at(1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							qCDebug(logHyprlandIpc) << "Workspace created with id" << id << "name" << name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto* workspace = this->findWorkspaceByName(name, false);
 | 
				
			||||||
 | 
							auto existed = workspace != nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (workspace == nullptr) {
 | 
				
			||||||
 | 
								workspace = new HyprlandWorkspace(this);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							workspace->updateInitial(id, name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!existed) {
 | 
				
			||||||
 | 
								this->refreshWorkspaces(false);
 | 
				
			||||||
 | 
								this->mWorkspaces.insertObject(workspace);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if (event->name == "destroyworkspacev2") {
 | 
				
			||||||
 | 
							auto args = event->parseView(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto id = args.at(0).toInt();
 | 
				
			||||||
 | 
							auto name = QString::fromUtf8(args.at(1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const auto& mList = this->mWorkspaces.valueList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto workspaceIter = std::find_if(mList.begin(), mList.end(), [id](const HyprlandWorkspace* m) {
 | 
				
			||||||
 | 
								return m->id() == id;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (workspaceIter == mList.end()) {
 | 
				
			||||||
 | 
								qCWarning(logHyprlandIpc) << "Got removal for workspace id" << id << "name" << name
 | 
				
			||||||
 | 
								                          << "which was not previously tracked.";
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto index = workspaceIter - mList.begin();
 | 
				
			||||||
 | 
							auto* workspace = *workspaceIter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							qCDebug(logHyprlandIpc) << "Workspace removed with id" << id << "name" << name;
 | 
				
			||||||
 | 
							this->mWorkspaces.removeAt(index);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// workspaces have not been observed to be referenced after deletion
 | 
				
			||||||
 | 
							delete workspace;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (auto* monitor: this->mMonitors.valueList()) {
 | 
				
			||||||
 | 
								if (monitor->activeWorkspace() == nullptr) {
 | 
				
			||||||
 | 
									// removing a monitor will cause a new workspace to be created and destroyed after removal,
 | 
				
			||||||
 | 
									// but it won't go back to a real workspace afterwards and just leaves a null, so we
 | 
				
			||||||
 | 
									// re-query monitors if this appears to be the case.
 | 
				
			||||||
 | 
									this->refreshMonitors(false);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if (event->name == "focusedmon") {
 | 
				
			||||||
 | 
							auto args = event->parseView(2);
 | 
				
			||||||
 | 
							auto name = QString::fromUtf8(args.at(0));
 | 
				
			||||||
 | 
							auto workspaceName = QString::fromUtf8(args.at(1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							HyprlandWorkspace* workspace = nullptr;
 | 
				
			||||||
 | 
							if (workspaceName != "?") { // what the fuck
 | 
				
			||||||
 | 
								workspace = this->findWorkspaceByName(workspaceName, false);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto* monitor = this->findMonitorByName(name, true);
 | 
				
			||||||
 | 
							this->setFocusedMonitor(monitor);
 | 
				
			||||||
 | 
							monitor->setActiveWorkspace(workspace);
 | 
				
			||||||
 | 
						} else if (event->name == "workspacev2") {
 | 
				
			||||||
 | 
							auto args = event->parseView(2);
 | 
				
			||||||
 | 
							auto id = args.at(0).toInt();
 | 
				
			||||||
 | 
							auto name = QString::fromUtf8(args.at(1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (this->mFocusedMonitor != nullptr) {
 | 
				
			||||||
 | 
								auto* workspace = this->findWorkspaceByName(name, true, id);
 | 
				
			||||||
 | 
								this->mFocusedMonitor->setActiveWorkspace(workspace);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if (event->name == "moveworkspacev2") {
 | 
				
			||||||
 | 
							auto args = event->parseView(3);
 | 
				
			||||||
 | 
							auto id = args.at(0).toInt();
 | 
				
			||||||
 | 
							auto name = QString::fromUtf8(args.at(1));
 | 
				
			||||||
 | 
							auto monitorName = QString::fromUtf8(args.at(2));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto* workspace = this->findWorkspaceByName(name, true, id);
 | 
				
			||||||
 | 
							auto* monitor = this->findMonitorByName(monitorName, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							workspace->setMonitor(monitor);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HyprlandWorkspace*
 | 
				
			||||||
 | 
					HyprlandIpc::findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id) {
 | 
				
			||||||
 | 
						const auto& mList = this->mWorkspaces.valueList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto workspaceIter = std::find_if(mList.begin(), mList.end(), [name](const HyprlandWorkspace* m) {
 | 
				
			||||||
 | 
							return m->name() == name;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (workspaceIter != mList.end()) {
 | 
				
			||||||
 | 
							return *workspaceIter;
 | 
				
			||||||
 | 
						} else if (createIfMissing) {
 | 
				
			||||||
 | 
							qCDebug(logHyprlandIpc) << "Workspace" << name
 | 
				
			||||||
 | 
							                        << "requested before creation, performing early init";
 | 
				
			||||||
 | 
							auto* workspace = new HyprlandWorkspace(this);
 | 
				
			||||||
 | 
							workspace->updateInitial(id, name);
 | 
				
			||||||
 | 
							this->mWorkspaces.insertObject(workspace);
 | 
				
			||||||
 | 
							return workspace;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return nullptr;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandIpc::refreshWorkspaces(bool canCreate) {
 | 
				
			||||||
 | 
						if (this->requestingWorkspaces) return;
 | 
				
			||||||
 | 
						this->requestingWorkspaces = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->makeRequest("j/workspaces", [this, canCreate](bool success, const QByteArray& resp) {
 | 
				
			||||||
 | 
							this->requestingWorkspaces = false;
 | 
				
			||||||
 | 
							if (!success) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							qCDebug(logHyprlandIpc) << "parsing workspaces response";
 | 
				
			||||||
 | 
							auto json = QJsonDocument::fromJson(resp).array();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const auto& mList = this->mWorkspaces.valueList();
 | 
				
			||||||
 | 
							auto names = QVector<QString>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (auto entry: json) {
 | 
				
			||||||
 | 
								auto object = entry.toObject().toVariantMap();
 | 
				
			||||||
 | 
								auto name = object.value("name").toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								auto workspaceIter =
 | 
				
			||||||
 | 
								    std::find_if(mList.begin(), mList.end(), [name](const HyprlandWorkspace* m) {
 | 
				
			||||||
 | 
									    return m->name() == name;
 | 
				
			||||||
 | 
								    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								auto* workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter;
 | 
				
			||||||
 | 
								auto existed = workspace != nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (workspace == nullptr) {
 | 
				
			||||||
 | 
									if (!canCreate) continue;
 | 
				
			||||||
 | 
									workspace = new HyprlandWorkspace(this);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								workspace->updateFromObject(object);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!existed) {
 | 
				
			||||||
 | 
									this->mWorkspaces.insertObject(workspace);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								names.push_back(name);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto removedWorkspaces = QVector<HyprlandWorkspace*>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (auto* workspace: mList) {
 | 
				
			||||||
 | 
								if (!names.contains(workspace->name())) {
 | 
				
			||||||
 | 
									removedWorkspaces.push_back(workspace);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (auto* workspace: removedWorkspaces) {
 | 
				
			||||||
 | 
								this->mWorkspaces.removeObject(workspace);
 | 
				
			||||||
 | 
								delete workspace;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HyprlandMonitor*
 | 
				
			||||||
 | 
					HyprlandIpc::findMonitorByName(const QString& name, bool createIfMissing, qint32 id) {
 | 
				
			||||||
 | 
						const auto& mList = this->mMonitors.valueList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto monitorIter = std::find_if(mList.begin(), mList.end(), [name](const HyprlandMonitor* m) {
 | 
				
			||||||
 | 
							return m->name() == name;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (monitorIter != mList.end()) {
 | 
				
			||||||
 | 
							return *monitorIter;
 | 
				
			||||||
 | 
						} else if (createIfMissing) {
 | 
				
			||||||
 | 
							qCDebug(logHyprlandIpc) << "Monitor" << name
 | 
				
			||||||
 | 
							                        << "requested before creation, performing early init";
 | 
				
			||||||
 | 
							auto* monitor = new HyprlandMonitor(this);
 | 
				
			||||||
 | 
							monitor->updateInitial(id, name, "");
 | 
				
			||||||
 | 
							this->mMonitors.insertObject(monitor);
 | 
				
			||||||
 | 
							return monitor;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return nullptr;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HyprlandMonitor* HyprlandIpc::focusedMonitor() const { return this->mFocusedMonitor; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HyprlandMonitor* HyprlandIpc::monitorFor(QuickshellScreenInfo* screen) {
 | 
				
			||||||
 | 
						// Wayland monitors appear after hyprland ones are created and disappear after destruction
 | 
				
			||||||
 | 
						// so simply not doing any preemptive creation is enough.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (screen == nullptr) return nullptr;
 | 
				
			||||||
 | 
						return this->findMonitorByName(screen->name(), false);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandIpc::setFocusedMonitor(HyprlandMonitor* monitor) {
 | 
				
			||||||
 | 
						if (monitor == this->mFocusedMonitor) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->mFocusedMonitor != nullptr) {
 | 
				
			||||||
 | 
							QObject::disconnect(this->mFocusedMonitor, nullptr, this, nullptr);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->mFocusedMonitor = monitor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (monitor != nullptr) {
 | 
				
			||||||
 | 
							QObject::connect(monitor, &QObject::destroyed, this, &HyprlandIpc::onFocusedMonitorDestroyed);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						emit this->focusedMonitorChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandIpc::onFocusedMonitorDestroyed() {
 | 
				
			||||||
 | 
						this->mFocusedMonitor = nullptr;
 | 
				
			||||||
 | 
						emit this->focusedMonitorChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandIpc::refreshMonitors(bool canCreate) {
 | 
				
			||||||
 | 
						if (this->requestingMonitors) return;
 | 
				
			||||||
 | 
						this->requestingMonitors = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->makeRequest("j/monitors", [this, canCreate](bool success, const QByteArray& resp) {
 | 
				
			||||||
 | 
							this->requestingMonitors = false;
 | 
				
			||||||
 | 
							if (!success) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							qCDebug(logHyprlandIpc) << "parsing monitors response";
 | 
				
			||||||
 | 
							auto json = QJsonDocument::fromJson(resp).array();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const auto& mList = this->mMonitors.valueList();
 | 
				
			||||||
 | 
							auto ids = QVector<qint32>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (auto entry: json) {
 | 
				
			||||||
 | 
								auto object = entry.toObject().toVariantMap();
 | 
				
			||||||
 | 
								auto id = object.value("id").toInt();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								auto monitorIter = std::find_if(mList.begin(), mList.end(), [id](const HyprlandMonitor* m) {
 | 
				
			||||||
 | 
									return m->id() == id;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								auto* monitor = monitorIter == mList.end() ? nullptr : *monitorIter;
 | 
				
			||||||
 | 
								auto existed = monitor != nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (monitor == nullptr) {
 | 
				
			||||||
 | 
									if (!canCreate) continue;
 | 
				
			||||||
 | 
									monitor = new HyprlandMonitor(this);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								monitor->updateFromObject(object);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!existed) {
 | 
				
			||||||
 | 
									this->mMonitors.insertObject(monitor);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ids.push_back(id);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto removedMonitors = QVector<HyprlandMonitor*>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (auto* monitor: mList) {
 | 
				
			||||||
 | 
								if (!ids.contains(monitor->id())) {
 | 
				
			||||||
 | 
									removedMonitors.push_back(monitor);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (auto* monitor: removedMonitors) {
 | 
				
			||||||
 | 
								this->mMonitors.removeObject(monitor);
 | 
				
			||||||
 | 
								// see comment in onEvent
 | 
				
			||||||
 | 
								monitor->deleteLater();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::hyprland::ipc
 | 
				
			||||||
							
								
								
									
										123
									
								
								src/wayland/hyprland/ipc/connection.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/wayland/hyprland/ipc/connection.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,123 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <functional>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qbytearrayview.h>
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qhash.h>
 | 
				
			||||||
 | 
					#include <qlocalsocket.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../core/model.hpp"
 | 
				
			||||||
 | 
					#include "../../../core/qmlscreen.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::hyprland::ipc {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HyprlandMonitor;
 | 
				
			||||||
 | 
					class HyprlandWorkspace;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::hyprland::ipc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Q_DECLARE_OPAQUE_POINTER(qs::hyprland::ipc::HyprlandWorkspace*);
 | 
				
			||||||
 | 
					Q_DECLARE_OPAQUE_POINTER(qs::hyprland::ipc::HyprlandMonitor*);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::hyprland::ipc {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///! Live Hyprland IPC event.
 | 
				
			||||||
 | 
					/// Live Hyprland IPC event. Holding this object after the
 | 
				
			||||||
 | 
					/// signal handler exits is undefined as the event instance
 | 
				
			||||||
 | 
					/// is reused.
 | 
				
			||||||
 | 
					class HyprlandIpcEvent: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						/// The name of the event.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString name READ nameStr CONSTANT);
 | 
				
			||||||
 | 
						/// The unparsed data of the event.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString data READ dataStr CONSTANT);
 | 
				
			||||||
 | 
						QML_NAMED_ELEMENT(HyprlandEvent);
 | 
				
			||||||
 | 
						QML_UNCREATABLE("HyprlandIpcEvents cannot be created.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						HyprlandIpcEvent(QObject* parent): QObject(parent) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Parse this event with a known number of arguments.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Argument count is required as some events can contain commas
 | 
				
			||||||
 | 
						/// in the last argument, which can be ignored as long as the count is known.
 | 
				
			||||||
 | 
						Q_INVOKABLE [[nodiscard]] QVector<QString> parse(qint32 argumentCount) const;
 | 
				
			||||||
 | 
						[[nodiscard]] QVector<QByteArrayView> parseView(qint32 argumentCount) const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QString nameStr() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QString dataStr() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void reset();
 | 
				
			||||||
 | 
						QByteArrayView name;
 | 
				
			||||||
 | 
						QByteArrayView data;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HyprlandIpc: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						static HyprlandIpc* instance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QString requestSocketPath() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QString eventSocketPath() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void
 | 
				
			||||||
 | 
						makeRequest(const QByteArray& request, const std::function<void(bool, QByteArray)>& callback);
 | 
				
			||||||
 | 
						void dispatch(const QString& request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] HyprlandMonitor* monitorFor(QuickshellScreenInfo* screen);
 | 
				
			||||||
 | 
						[[nodiscard]] HyprlandMonitor* focusedMonitor() const;
 | 
				
			||||||
 | 
						void setFocusedMonitor(HyprlandMonitor* monitor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] ObjectModel<HyprlandMonitor>* monitors();
 | 
				
			||||||
 | 
						[[nodiscard]] ObjectModel<HyprlandWorkspace>* workspaces();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// No byId because these preemptively create objects. The given id is set if created.
 | 
				
			||||||
 | 
						HyprlandWorkspace* findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id = 0);
 | 
				
			||||||
 | 
						HyprlandMonitor* findMonitorByName(const QString& name, bool createIfMissing, qint32 id = -1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// canCreate avoids making ghost workspaces when the connection races
 | 
				
			||||||
 | 
						void refreshWorkspaces(bool canCreate);
 | 
				
			||||||
 | 
						void refreshMonitors(bool canCreate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The last argument may contain commas, so the count is required.
 | 
				
			||||||
 | 
						[[nodiscard]] static QVector<QByteArrayView> parseEventArgs(QByteArrayView event, quint16 count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						void connected();
 | 
				
			||||||
 | 
						void rawEvent(HyprlandIpcEvent* event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void focusedMonitorChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private slots:
 | 
				
			||||||
 | 
						void eventSocketError(QLocalSocket::LocalSocketError error) const;
 | 
				
			||||||
 | 
						void eventSocketStateChanged(QLocalSocket::LocalSocketState state);
 | 
				
			||||||
 | 
						void eventSocketReady();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void onFocusedMonitorDestroyed();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						explicit HyprlandIpc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void onEvent(HyprlandIpcEvent* event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QLocalSocket eventSocket;
 | 
				
			||||||
 | 
						QString mRequestSocketPath;
 | 
				
			||||||
 | 
						QString mEventSocketPath;
 | 
				
			||||||
 | 
						bool valid = false;
 | 
				
			||||||
 | 
						bool requestingMonitors = false;
 | 
				
			||||||
 | 
						bool requestingWorkspaces = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ObjectModel<HyprlandMonitor> mMonitors {this};
 | 
				
			||||||
 | 
						ObjectModel<HyprlandWorkspace> mWorkspaces {this};
 | 
				
			||||||
 | 
						HyprlandMonitor* mFocusedMonitor = nullptr;
 | 
				
			||||||
 | 
						//HyprlandWorkspace* activeWorkspace = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						HyprlandIpcEvent event {this};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::hyprland::ipc
 | 
				
			||||||
							
								
								
									
										136
									
								
								src/wayland/hyprland/ipc/monitor.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								src/wayland/hyprland/ipc/monitor.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,136 @@
 | 
				
			||||||
 | 
					#include "monitor.hpp"
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "workspace.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::hyprland::ipc {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qint32 HyprlandMonitor::id() const { return this->mId; }
 | 
				
			||||||
 | 
					QString HyprlandMonitor::name() const { return this->mName; }
 | 
				
			||||||
 | 
					QString HyprlandMonitor::description() const { return this->mDescription; }
 | 
				
			||||||
 | 
					qint32 HyprlandMonitor::x() const { return this->mX; }
 | 
				
			||||||
 | 
					qint32 HyprlandMonitor::y() const { return this->mY; }
 | 
				
			||||||
 | 
					qint32 HyprlandMonitor::width() const { return this->mWidth; }
 | 
				
			||||||
 | 
					qint32 HyprlandMonitor::height() const { return this->mHeight; }
 | 
				
			||||||
 | 
					qreal HyprlandMonitor::scale() const { return this->mScale; }
 | 
				
			||||||
 | 
					QVariantMap HyprlandMonitor::lastIpcObject() const { return this->mLastIpcObject; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandMonitor::updateInitial(qint32 id, QString name, QString description) {
 | 
				
			||||||
 | 
						if (id != this->mId) {
 | 
				
			||||||
 | 
							this->mId = id;
 | 
				
			||||||
 | 
							emit this->idChanged();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (name != this->mName) {
 | 
				
			||||||
 | 
							this->mName = std::move(name);
 | 
				
			||||||
 | 
							emit this->nameChanged();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (description != this->mDescription) {
 | 
				
			||||||
 | 
							this->mDescription = std::move(description);
 | 
				
			||||||
 | 
							emit this->descriptionChanged();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandMonitor::updateFromObject(QVariantMap object) {
 | 
				
			||||||
 | 
						auto id = object.value("id").value<qint32>();
 | 
				
			||||||
 | 
						auto name = object.value("name").value<QString>();
 | 
				
			||||||
 | 
						auto description = object.value("description").value<QString>();
 | 
				
			||||||
 | 
						auto x = object.value("x").value<qint32>();
 | 
				
			||||||
 | 
						auto y = object.value("y").value<qint32>();
 | 
				
			||||||
 | 
						auto width = object.value("width").value<qint32>();
 | 
				
			||||||
 | 
						auto height = object.value("height").value<qint32>();
 | 
				
			||||||
 | 
						auto scale = object.value("height").value<qint32>();
 | 
				
			||||||
 | 
						auto activeWorkspaceObj = object.value("activeWorkspace").value<QVariantMap>();
 | 
				
			||||||
 | 
						auto activeWorkspaceId = activeWorkspaceObj.value("id").value<qint32>();
 | 
				
			||||||
 | 
						auto activeWorkspaceName = activeWorkspaceObj.value("name").value<QString>();
 | 
				
			||||||
 | 
						auto focused = object.value("focused").value<bool>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (id != this->mId) {
 | 
				
			||||||
 | 
							this->mId = id;
 | 
				
			||||||
 | 
							emit this->idChanged();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (name != this->mName) {
 | 
				
			||||||
 | 
							this->mName = std::move(name);
 | 
				
			||||||
 | 
							emit this->nameChanged();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (description != this->mDescription) {
 | 
				
			||||||
 | 
							this->mDescription = std::move(description);
 | 
				
			||||||
 | 
							emit this->descriptionChanged();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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 (this->mActiveWorkspace == nullptr || this->mActiveWorkspace->name() != activeWorkspaceName) {
 | 
				
			||||||
 | 
							auto* workspace = this->ipc->findWorkspaceByName(activeWorkspaceName, true, activeWorkspaceId);
 | 
				
			||||||
 | 
							workspace->setMonitor(this);
 | 
				
			||||||
 | 
							this->setActiveWorkspace(workspace);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->mLastIpcObject = std::move(object);
 | 
				
			||||||
 | 
						emit this->lastIpcObjectChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (focused) {
 | 
				
			||||||
 | 
							this->ipc->setFocusedMonitor(this);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HyprlandWorkspace* HyprlandMonitor::activeWorkspace() const { return this->mActiveWorkspace; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandMonitor::setActiveWorkspace(HyprlandWorkspace* workspace) {
 | 
				
			||||||
 | 
						if (workspace == this->mActiveWorkspace) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->mActiveWorkspace != nullptr) {
 | 
				
			||||||
 | 
							QObject::disconnect(this->mActiveWorkspace, nullptr, this, nullptr);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->mActiveWorkspace = workspace;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (workspace != nullptr) {
 | 
				
			||||||
 | 
							QObject::connect(
 | 
				
			||||||
 | 
							    workspace,
 | 
				
			||||||
 | 
							    &QObject::destroyed,
 | 
				
			||||||
 | 
							    this,
 | 
				
			||||||
 | 
							    &HyprlandMonitor::onActiveWorkspaceDestroyed
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						emit this->activeWorkspaceChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandMonitor::onActiveWorkspaceDestroyed() {
 | 
				
			||||||
 | 
						this->mActiveWorkspace = nullptr;
 | 
				
			||||||
 | 
						emit this->activeWorkspaceChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::hyprland::ipc
 | 
				
			||||||
							
								
								
									
										85
									
								
								src/wayland/hyprland/ipc/monitor.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/wayland/hyprland/ipc/monitor.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,85 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qbytearrayview.h>
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "connection.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::hyprland::ipc {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HyprlandMonitor: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						Q_PROPERTY(qint32 id READ id NOTIFY idChanged);
 | 
				
			||||||
 | 
						Q_PROPERTY(QString name READ name NOTIFY nameChanged);
 | 
				
			||||||
 | 
						Q_PROPERTY(QString description READ description NOTIFY descriptionChanged);
 | 
				
			||||||
 | 
						Q_PROPERTY(qint32 x READ x NOTIFY xChanged);
 | 
				
			||||||
 | 
						Q_PROPERTY(qint32 y READ y NOTIFY yChanged);
 | 
				
			||||||
 | 
						Q_PROPERTY(qint32 width READ width NOTIFY widthChanged);
 | 
				
			||||||
 | 
						Q_PROPERTY(qint32 height READ height NOTIFY heightChanged);
 | 
				
			||||||
 | 
						Q_PROPERTY(qreal scale READ scale NOTIFY scaleChanged);
 | 
				
			||||||
 | 
						/// Last json returned for this monitor, as a javascript object.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// > [!WARNING] This is *not* updated unless the monitor object is fetched again from
 | 
				
			||||||
 | 
						/// > Hyprland. If you need a value that is subject to change and does not have a dedicated
 | 
				
			||||||
 | 
						/// > property, run `HyprlandIpc.refreshMonitors()` and wait for this property to update.
 | 
				
			||||||
 | 
						Q_PROPERTY(QVariantMap lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged);
 | 
				
			||||||
 | 
						/// The currently active workspace on this monitor. May be null.
 | 
				
			||||||
 | 
						Q_PROPERTY(HyprlandWorkspace* activeWorkspace READ activeWorkspace NOTIFY activeWorkspaceChanged);
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_UNCREATABLE("HyprlandMonitors must be retrieved from the HyprlandIpc object.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit HyprlandMonitor(HyprlandIpc* ipc): QObject(ipc), ipc(ipc) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void updateInitial(qint32 id, QString name, QString description);
 | 
				
			||||||
 | 
						void updateFromObject(QVariantMap object);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] qint32 id() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QString name() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QString description() const;
 | 
				
			||||||
 | 
						[[nodiscard]] qint32 x() const;
 | 
				
			||||||
 | 
						[[nodiscard]] qint32 y() const;
 | 
				
			||||||
 | 
						[[nodiscard]] qint32 width() const;
 | 
				
			||||||
 | 
						[[nodiscard]] qint32 height() const;
 | 
				
			||||||
 | 
						[[nodiscard]] qreal scale() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QVariantMap lastIpcObject() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void setActiveWorkspace(HyprlandWorkspace* workspace);
 | 
				
			||||||
 | 
						[[nodiscard]] HyprlandWorkspace* activeWorkspace() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						void idChanged();
 | 
				
			||||||
 | 
						void nameChanged();
 | 
				
			||||||
 | 
						void descriptionChanged();
 | 
				
			||||||
 | 
						void xChanged();
 | 
				
			||||||
 | 
						void yChanged();
 | 
				
			||||||
 | 
						void widthChanged();
 | 
				
			||||||
 | 
						void heightChanged();
 | 
				
			||||||
 | 
						void scaleChanged();
 | 
				
			||||||
 | 
						void lastIpcObjectChanged();
 | 
				
			||||||
 | 
						void activeWorkspaceChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private slots:
 | 
				
			||||||
 | 
						void onActiveWorkspaceDestroyed();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						HyprlandIpc* ipc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qint32 mId = -1;
 | 
				
			||||||
 | 
						QString mName;
 | 
				
			||||||
 | 
						QString mDescription;
 | 
				
			||||||
 | 
						qint32 mX = 0;
 | 
				
			||||||
 | 
						qint32 mY = 0;
 | 
				
			||||||
 | 
						qint32 mWidth = 0;
 | 
				
			||||||
 | 
						qint32 mHeight = 0;
 | 
				
			||||||
 | 
						qreal mScale = 0;
 | 
				
			||||||
 | 
						QVariantMap mLastIpcObject;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						HyprlandWorkspace* mActiveWorkspace = nullptr;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::hyprland::ipc
 | 
				
			||||||
							
								
								
									
										52
									
								
								src/wayland/hyprland/ipc/qml.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/wayland/hyprland/ipc/qml.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,52 @@
 | 
				
			||||||
 | 
					#include "qml.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../core/model.hpp"
 | 
				
			||||||
 | 
					#include "../../../core/qmlscreen.hpp"
 | 
				
			||||||
 | 
					#include "connection.hpp"
 | 
				
			||||||
 | 
					#include "monitor.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::hyprland::ipc {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HyprlandIpcQml::HyprlandIpcQml() {
 | 
				
			||||||
 | 
						auto* instance = HyprlandIpc::instance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(instance, &HyprlandIpc::rawEvent, this, &HyprlandIpcQml::rawEvent);
 | 
				
			||||||
 | 
						QObject::connect(
 | 
				
			||||||
 | 
						    instance,
 | 
				
			||||||
 | 
						    &HyprlandIpc::focusedMonitorChanged,
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    &HyprlandIpcQml::focusedMonitorChanged
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandIpcQml::dispatch(const QString& request) {
 | 
				
			||||||
 | 
						HyprlandIpc::instance()->dispatch(request);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HyprlandMonitor* HyprlandIpcQml::monitorFor(QuickshellScreenInfo* screen) {
 | 
				
			||||||
 | 
						return HyprlandIpc::instance()->monitorFor(screen);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandIpcQml::refreshMonitors() { HyprlandIpc::instance()->refreshMonitors(false); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandIpcQml::refreshWorkspaces() { HyprlandIpc::instance()->refreshWorkspaces(false); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString HyprlandIpcQml::requestSocketPath() { return HyprlandIpc::instance()->requestSocketPath(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString HyprlandIpcQml::eventSocketPath() { return HyprlandIpc::instance()->eventSocketPath(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HyprlandMonitor* HyprlandIpcQml::focusedMonitor() {
 | 
				
			||||||
 | 
						return HyprlandIpc::instance()->focusedMonitor();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ObjectModel<HyprlandMonitor>* HyprlandIpcQml::monitors() {
 | 
				
			||||||
 | 
						return HyprlandIpc::instance()->monitors();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ObjectModel<HyprlandWorkspace>* HyprlandIpcQml::workspaces() {
 | 
				
			||||||
 | 
						return HyprlandIpc::instance()->workspaces();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::hyprland::ipc
 | 
				
			||||||
							
								
								
									
										66
									
								
								src/wayland/hyprland/ipc/qml.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/wayland/hyprland/ipc/qml.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,66 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qbytearrayview.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../../core/model.hpp"
 | 
				
			||||||
 | 
					#include "../../../core/qmlscreen.hpp"
 | 
				
			||||||
 | 
					#include "connection.hpp"
 | 
				
			||||||
 | 
					#include "monitor.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::hyprland::ipc {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HyprlandIpcQml: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						/// Path to the request socket (.socket.sock)
 | 
				
			||||||
 | 
						Q_PROPERTY(QString requestSocketPath READ requestSocketPath CONSTANT);
 | 
				
			||||||
 | 
						/// Path to the event socket (.socket2.sock)
 | 
				
			||||||
 | 
						Q_PROPERTY(QString eventSocketPath READ eventSocketPath CONSTANT);
 | 
				
			||||||
 | 
						/// The currently focused hyprland monitor. May be null.
 | 
				
			||||||
 | 
						Q_PROPERTY(HyprlandMonitor* focusedMonitor READ focusedMonitor NOTIFY focusedMonitorChanged);
 | 
				
			||||||
 | 
						/// All hyprland monitors.
 | 
				
			||||||
 | 
						Q_PROPERTY(ObjectModel<HyprlandMonitor>* monitors READ monitors CONSTANT);
 | 
				
			||||||
 | 
						/// All hyprland workspaces.
 | 
				
			||||||
 | 
						Q_PROPERTY(ObjectModel<HyprlandWorkspace>* workspaces READ workspaces CONSTANT);
 | 
				
			||||||
 | 
						QML_NAMED_ELEMENT(Hyprland);
 | 
				
			||||||
 | 
						QML_SINGLETON;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit HyprlandIpcQml();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Execute a hyprland [dispatcher](https://wiki.hyprland.org/Configuring/Dispatchers).
 | 
				
			||||||
 | 
						Q_INVOKABLE static void dispatch(const QString& request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Get the HyprlandMonitor object that corrosponds to a quickshell screen.
 | 
				
			||||||
 | 
						Q_INVOKABLE static HyprlandMonitor* monitorFor(QuickshellScreenInfo* screen);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Refresh monitor information.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Many actions that will invalidate monitor state don't send events,
 | 
				
			||||||
 | 
						/// so this function is available if required.
 | 
				
			||||||
 | 
						Q_INVOKABLE static void refreshMonitors();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Refresh workspace information.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Many actions that will invalidate workspace state don't send events,
 | 
				
			||||||
 | 
						/// so this function is available if required.
 | 
				
			||||||
 | 
						Q_INVOKABLE static void refreshWorkspaces();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] static QString requestSocketPath();
 | 
				
			||||||
 | 
						[[nodiscard]] static QString eventSocketPath();
 | 
				
			||||||
 | 
						[[nodiscard]] static HyprlandMonitor* focusedMonitor();
 | 
				
			||||||
 | 
						[[nodiscard]] static ObjectModel<HyprlandMonitor>* monitors();
 | 
				
			||||||
 | 
						[[nodiscard]] static ObjectModel<HyprlandWorkspace>* workspaces();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						/// Emitted for every event that comes in through the hyprland event socket (socket2).
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// See [Hyprland Wiki: IPC](https://wiki.hyprland.org/IPC/) for a list of events.
 | 
				
			||||||
 | 
						void rawEvent(HyprlandIpcEvent* event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void focusedMonitorChanged();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::hyprland::ipc
 | 
				
			||||||
							
								
								
									
										79
									
								
								src/wayland/hyprland/ipc/workspace.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/wayland/hyprland/ipc/workspace.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,79 @@
 | 
				
			||||||
 | 
					#include "workspace.hpp"
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "monitor.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::hyprland::ipc {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qint32 HyprlandWorkspace::id() const { return this->mId; }
 | 
				
			||||||
 | 
					QString HyprlandWorkspace::name() const { return this->mName; }
 | 
				
			||||||
 | 
					QVariantMap HyprlandWorkspace::lastIpcObject() const { return this->mLastIpcObject; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandWorkspace::updateInitial(qint32 id, QString name) {
 | 
				
			||||||
 | 
						if (id != this->mId) {
 | 
				
			||||||
 | 
							this->mId = id;
 | 
				
			||||||
 | 
							emit this->idChanged();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (name != this->mName) {
 | 
				
			||||||
 | 
							this->mName = std::move(name);
 | 
				
			||||||
 | 
							emit this->nameChanged();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandWorkspace::updateFromObject(QVariantMap object) {
 | 
				
			||||||
 | 
						auto id = object.value("id").value<qint32>();
 | 
				
			||||||
 | 
						auto name = object.value("name").value<QString>();
 | 
				
			||||||
 | 
						auto monitorId = object.value("monitorID").value<qint32>();
 | 
				
			||||||
 | 
						auto monitorName = object.value("monitor").value<QString>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (id != this->mId) {
 | 
				
			||||||
 | 
							this->mId = id;
 | 
				
			||||||
 | 
							emit this->idChanged();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (name != this->mName) {
 | 
				
			||||||
 | 
							this->mName = std::move(name);
 | 
				
			||||||
 | 
							emit this->nameChanged();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!monitorName.isEmpty()
 | 
				
			||||||
 | 
						    && (this->mMonitor == nullptr || this->mMonitor->name() != monitorName))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto* monitor = this->ipc->findMonitorByName(monitorName, true, monitorId);
 | 
				
			||||||
 | 
							this->setMonitor(monitor);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->mLastIpcObject = std::move(object);
 | 
				
			||||||
 | 
						emit this->lastIpcObjectChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HyprlandMonitor* HyprlandWorkspace::monitor() const { return this->mMonitor; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandWorkspace::setMonitor(HyprlandMonitor* monitor) {
 | 
				
			||||||
 | 
						if (monitor == this->mMonitor) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->mMonitor != nullptr) {
 | 
				
			||||||
 | 
							QObject::disconnect(this->mMonitor, nullptr, this, nullptr);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->mMonitor = monitor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (monitor != nullptr) {
 | 
				
			||||||
 | 
							QObject::connect(monitor, &QObject::destroyed, this, &HyprlandWorkspace::onMonitorDestroyed);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						emit this->monitorChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HyprlandWorkspace::onMonitorDestroyed() {
 | 
				
			||||||
 | 
						this->mMonitor = nullptr;
 | 
				
			||||||
 | 
						emit this->monitorChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::hyprland::ipc
 | 
				
			||||||
							
								
								
									
										59
									
								
								src/wayland/hyprland/ipc/workspace.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/wayland/hyprland/ipc/workspace.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,59 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qbytearrayview.h>
 | 
				
			||||||
 | 
					#include <qjsonobject.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "connection.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::hyprland::ipc {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HyprlandWorkspace: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						Q_PROPERTY(qint32 id READ id NOTIFY idChanged);
 | 
				
			||||||
 | 
						Q_PROPERTY(QString name READ name NOTIFY nameChanged);
 | 
				
			||||||
 | 
						/// Last json returned for this workspace, as a javascript object.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// > [!WARNING] This is *not* updated unless the workspace object is fetched again from
 | 
				
			||||||
 | 
						/// > Hyprland. If you need a value that is subject to change and does not have a dedicated
 | 
				
			||||||
 | 
						/// > property, run `HyprlandIpc.refreshWorkspaces()` and wait for this property to update.
 | 
				
			||||||
 | 
						Q_PROPERTY(QVariantMap lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged);
 | 
				
			||||||
 | 
						Q_PROPERTY(HyprlandMonitor* monitor READ monitor NOTIFY monitorChanged);
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_UNCREATABLE("HyprlandWorkspaces must be retrieved from the HyprlandIpc object.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit HyprlandWorkspace(HyprlandIpc* ipc): QObject(ipc), ipc(ipc) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void updateInitial(qint32 id, QString name);
 | 
				
			||||||
 | 
						void updateFromObject(QVariantMap object);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] qint32 id() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QString name() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QVariantMap lastIpcObject() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void setMonitor(HyprlandMonitor* monitor);
 | 
				
			||||||
 | 
						[[nodiscard]] HyprlandMonitor* monitor() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						void idChanged();
 | 
				
			||||||
 | 
						void nameChanged();
 | 
				
			||||||
 | 
						void lastIpcObjectChanged();
 | 
				
			||||||
 | 
						void monitorChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private slots:
 | 
				
			||||||
 | 
						void onMonitorDestroyed();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						HyprlandIpc* ipc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qint32 mId = -1;
 | 
				
			||||||
 | 
						QString mName;
 | 
				
			||||||
 | 
						QVariantMap mLastIpcObject;
 | 
				
			||||||
 | 
						HyprlandMonitor* mMonitor = nullptr;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::hyprland::ipc
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,10 @@
 | 
				
			||||||
name = "Quickshell.Hyprland"
 | 
					name = "Quickshell.Hyprland"
 | 
				
			||||||
description = "Hyprland specific Quickshell types"
 | 
					description = "Hyprland specific Quickshell types"
 | 
				
			||||||
headers = [
 | 
					headers = [
 | 
				
			||||||
 | 
						"ipc/connection.hpp",
 | 
				
			||||||
 | 
						"ipc/monitor.hpp",
 | 
				
			||||||
 | 
						"ipc/workspace.hpp",
 | 
				
			||||||
 | 
						"ipc/qml.hpp",
 | 
				
			||||||
	"focus_grab/qml.hpp",
 | 
						"focus_grab/qml.hpp",
 | 
				
			||||||
	"global_shortcuts/qml.hpp",
 | 
						"global_shortcuts/qml.hpp",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue