#include "services/hyprland.hpp" #include #include #include #include #include #include #include #include #include #include #include "helpers/systemHelper.hpp" HyprlandService::HyprlandService() = default; HyprlandService::~HyprlandService() { if (fd != -1) { close(fd); fd = -1; } // free allocated workspace pointers for (auto &p : this->workspaces) { delete p.second; } this->workspaces.clear(); this->monitors.clear(); } void HyprlandService::on_hyprland_event(std::string event, std::string data) { if (event == "urgent") { onUrgentEvent(data); } if (event == "activewindowv2") { onActiveWindowEvent(data); } if (event == "workspace" || event == "movewindow") { refresh_workspaces(); } // use for // event == "focusedmon" if (event == "monitoradded" || event == "monitorremoved") { refresh_monitors(); } } void HyprlandService::start() { const std::string socket_path = get_socket_path(); if (socket_path.empty()) { return; } fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == -1) { std::cerr << "[Hyprland] Failed to create socket" << std::endl; return; } struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; std::strncpy(addr.sun_path, socket_path.c_str(), sizeof(addr.sun_path) - 1); if (connect(fd, reinterpret_cast(&addr), sizeof(addr)) == -1) { std::cerr << "[Hyprland] Failed to connect to " << socket_path << std::endl; close(fd); fd = -1; return; } std::cout << "[Hyprland] Connected to event socket." << std::endl; Glib::signal_io().connect( sigc::mem_fun(*this, &HyprlandService::on_socket_read), fd, Glib::IOCondition::IO_IN | Glib::IOCondition::IO_HUP | Glib::IOCondition::IO_ERR); refresh_monitors(); } bool HyprlandService::on_socket_read(Glib::IOCondition condition) { const auto error_mask = Glib::IOCondition::IO_HUP | Glib::IOCondition::IO_ERR; if (static_cast(condition & error_mask) != 0) { std::cerr << "[Hyprland] Socket disconnected." << std::endl; if (fd != -1) { close(fd); fd = -1; } return false; } char temp_buffer[4096]; const ssize_t bytes_read = read(fd, temp_buffer, sizeof(temp_buffer)); if (bytes_read <= 0) { // peer closed or error std::cerr << "[Hyprland] Socket read returned " << bytes_read << std::endl; if (fd != -1) { close(fd); fd = -1; } return false; } // append exactly bytes_read bytes (may contain embedded nulls) this->socket_buffer.append(temp_buffer, static_cast(bytes_read)); size_t pos = 0; while ((pos = this->socket_buffer.find('\n')) != std::string::npos) { const std::string line = this->socket_buffer.substr(0, pos); parse_message(line); this->socket_buffer.erase(0, pos + 1); } return true; } void HyprlandService::parse_message(const std::string &line) { const size_t split = line.find(">>"); if (split != std::string::npos) { const std::string event_name = line.substr(0, split); const std::string event_data = line.substr(split + 2); socketEventSignal.emit(event_name, event_data); } } std::string HyprlandService::get_socket_path() { const char *sig = std::getenv("HYPRLAND_INSTANCE_SIGNATURE"); const char *runtime = std::getenv("XDG_RUNTIME_DIR"); if (!sig || !runtime) { std::cerr << "[Hyprland] Environment variables missing!" << std::endl; return ""; } return std::string(runtime) + "/hypr/" + sig + "/.socket2.sock"; } void HyprlandService::refresh_monitors() { // free any previously allocated WorkspaceState objects before rebuilding for (auto &p : this->workspaces) { delete p.second; } this->workspaces.clear(); this->monitors.clear(); std::string output = SystemHelper::get_command_output(kMonitorCommand); auto monitorsJson = nlohmann::json::parse(output, nullptr, false); for (const auto &monitorJson : monitorsJson) { Monitor monitor; monitor.id = monitorJson.value("id", -1); monitor.name = monitorJson.value("name", ""); monitor.x = monitorJson.value("x", 0); monitor.y = monitorJson.value("y", 0); monitor.focusedWorkspaceId = monitorJson["activeWorkspace"].value("id", -1); if (monitor.id >= 0) { this->monitors[monitor.id] = monitor; } for (int slot = 1; slot <= HyprlandService::kWorkspaceSlotCount; ++slot) { WorkspaceState wsState; wsState.focused = false; wsState.active = false; wsState.label = std::to_string(slot); wsState.monitorId = monitor.id; int id = slot + monitor.id * HyprlandService::kWorkspaceSlotCount; wsState.hyprId = id; this->workspaces[id] = new WorkspaceState(wsState); if (monitor.id >= 0) { this->monitors[monitor.id].workspaceStates[slot] = this->workspaces[id]; } } } this->refresh_workspaces(); monitorStateChanged.emit(); } /** * Called every time when workspace changes have been detected. * Used to Update the internal state for the Workspaces, */ void HyprlandService::refresh_workspaces() { std::string output = SystemHelper::get_command_output(kWorkspaceCommand); auto workspacesJson = nlohmann::json::parse(output, nullptr, false); for (auto &[id, ws] : this->workspaces) { ws->focused = false; ws->active = false; } output = SystemHelper::get_command_output(kMonitorCommand); auto monitorsJson = nlohmann::json::parse(output, nullptr, false); for (const auto &monitorJson : monitorsJson) { const int monitorId = monitorJson.value("id", -1); const int focusedWorkspaceId = monitorJson["activeWorkspace"].value("id", -1); // write into the stored monitor (use reference) auto it = this->monitors.find(monitorId); if (it != this->monitors.end()) { it->second.focusedWorkspaceId = focusedWorkspaceId; } } for (const auto &workspaceJson : workspacesJson) { const int workspaceId = workspaceJson.value("id", -1); auto workspaceStateIt = this->workspaces.find(workspaceId); if (workspaceStateIt == this->workspaces.end()) { continue; } WorkspaceState *workspaceState = workspaceStateIt->second; auto mit = this->monitors.find(workspaceState->monitorId); if (mit != this->monitors.end()) { workspaceState->focused = mit->second.focusedWorkspaceId == workspaceId; } else { workspaceState->focused = false; } workspaceState->active = true; } workspaceStateChanged.emit(); } void HyprlandService::switchToWorkspace(int workspaceId) { std::string cmd = "hyprctl dispatch workspace " + std::to_string(workspaceId); try { (void)SystemHelper::get_command_output(cmd.c_str()); } catch (const std::exception &ex) { std::cerr << "[Hyprland] Failed to dispatch workspace command: " << ex.what() << " cmd=" << cmd << std::endl; } } void HyprlandService::onUrgentEvent(std::string windowAddress) { std::string output = SystemHelper::get_command_output(kClientsCommand); auto clientsJson = nlohmann::json::parse(output, nullptr, false); for (const auto &clientJson : clientsJson) { const std::string addr = clientJson.value("address", ""); if (addr == "0x" + windowAddress) { int workspaceId = clientJson["workspace"].value("id", -1); auto it = this->workspaces.find(workspaceId); if (it != this->workspaces.end() && it->second) { if (std::find(it->second->urgentWindows.begin(), it->second->urgentWindows.end(), windowAddress) == it->second->urgentWindows.end()) { it->second->urgentWindows.push_back(windowAddress); workspaceStateChanged.emit(); } } break; } } } void HyprlandService::onActiveWindowEvent(std::string windowAddress) { std::string output = SystemHelper::get_command_output(kClientsCommand); auto clientsJson = nlohmann::json::parse(output, nullptr, false); for (const auto &clientJson : clientsJson) { const std::string addr = clientJson.value("address", ""); if (addr == "0x" + windowAddress) { int workspaceId = clientJson["workspace"]["id"]; auto it = this->workspaces.find(workspaceId); if (it != this->workspaces.end() && it->second) { WorkspaceState *ws = it->second; auto uit = std::find(ws->urgentWindows.begin(), ws->urgentWindows.end(), windowAddress); if (uit != ws->urgentWindows.end()) { ws->urgentWindows.erase(uit); workspaceStateChanged.emit(); } break; } } } } void HyprlandService::printMonitor(const Monitor &mon) const { std::cout << "=== Monitor Info ===\n"; std::cout << "Name: " << mon.name << " (ID: " << mon.id << ")\n"; std::cout << "Position: (" << mon.x << ", " << mon.y << ")\n"; std::cout << "Focused Workspace ID: " << mon.focusedWorkspaceId << "\n"; std::cout << "Workspaces:\n"; if (mon.workspaceStates.empty()) { std::cout << " (None)\n"; return; } for (int slot = 1; slot <= HyprlandService::kWorkspaceSlotCount; ++slot) { const auto it = mon.workspaceStates.find(slot); if (it == mon.workspaceStates.end()) { std::cout << " - [Slot: " << slot << " | HyprID: n/a]" << " Label: | Active: No | Focused: No | " "Urgent: No\n"; continue; } const WorkspaceState *ws = it->second; std::cout << " - [HyprID: " << (ws->hyprId >= 0 ? std::to_string(ws->hyprId) : std::string("n/a")) << "] " << "Label: " << (ws->label.empty() ? "" : ws->label) << " | " << "Active: " << (ws->active ? "Yes" : "No") << " | " << "Focused: " << (ws->focused ? "Yes" : "No") << " | " << "Urgent: " << (ws->urgentWindows.size() > 0 ? "Yes" : "No") << "\n"; } std::cout << "====================\n"; } HyprlandService::Monitor *HyprlandService::getMonitorById(int id) { auto it = monitors.find(id); if (it == monitors.end()) { throw std::runtime_error("Monitor with ID " + std::to_string(id) + " not found."); } return &it->second; } HyprlandService::Monitor *HyprlandService::getMonitorByIndex(std::size_t index) { if (index >= monitors.size()) { throw std::runtime_error("Monitor index out of bounds: " + std::to_string(index)); } auto it = monitors.begin(); std::advance(it, static_cast(index)); return &it->second; }