#include "services/hyprland.hpp" #include #include #include #include #include #include #include #include #include #include #include "helpers/systemHelper.hpp" namespace { const char *kMonitorCommand = "hyprctl monitors -j"; const char *kWorkspaceCommand = "hyprctl workspaces -j"; const char *kClientsCommand = "hyprctl clients -j"; bool is_workspace_event(const std::string &event) { return event.find("workspace") != std::string::npos; } } // namespace HyprlandService::HyprlandService() = default; HyprlandService::~HyprlandService() { if (fd != -1) { close(fd); fd = -1; } } void HyprlandService::on_hyprland_event(std::string event, std::string data) { if (event == "urgent") { handle_urgent_window(data); } if (is_workspace_event(event) || event == "focusedmon" || event == "monitoradded" || event == "monitorremoved") { refresh_monitors(); refresh_workspaces(); } } 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(); refresh_workspaces(); } 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; close(fd); fd = -1; return false; } char buffer[4096]; const ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1); if (bytes_read > 0) { buffer[bytes_read] = '\0'; this->buffer.append(buffer); size_t pos = 0; while ((pos = this->buffer.find('\n')) != std::string::npos) { const std::string line = this->buffer.substr(0, pos); parse_message(line); this->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() { std::string output; try { output = SystemHelper::get_command_output(kMonitorCommand); } catch (const std::exception &ex) { std::cerr << "[Hyprland] Failed to query monitors: " << ex.what() << std::endl; return; } auto monitorsJson = nlohmann::json::parse(output, nullptr, false); if (!monitorsJson.is_array()) { std::cerr << "[Hyprland] Unexpected monitor payload" << std::endl; return; } std::map updated; for (const auto &monitorJson : monitorsJson) { if (!monitorJson.is_object()) { continue; } 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); if (monitorJson.contains("activeWorkspace") && monitorJson["activeWorkspace"].is_object()) { monitor.focusedWorkspaceId = monitorJson["activeWorkspace"].value("id", -1); } if (monitor.id >= 0) { updated.emplace(monitor.id, std::move(monitor)); } } monitors.swap(updated); monitorStateChanged.emit(); } void HyprlandService::refresh_workspaces() { if (monitors.empty()) { return; } std::string output; try { output = SystemHelper::get_command_output(kWorkspaceCommand); } catch (const std::exception &ex) { std::cerr << "[Hyprland] Failed to query workspaces: " << ex.what() << std::endl; return; } auto workspacesJson = nlohmann::json::parse(output, nullptr, false); if (!workspacesJson.is_array()) { std::cerr << "[Hyprland] Unexpected workspace payload" << std::endl; return; } for (auto &pair : monitors) { auto &monitor = pair.second; monitor.workspaceStates.clear(); int focusedSlot = -1; if (monitor.focusedWorkspaceId > 0) { focusedSlot = ((monitor.focusedWorkspaceId - 1) % HyprlandService::kWorkspaceSlotCount) + 1; } for (int slot = 1; slot <= HyprlandService::kWorkspaceSlotCount; ++slot) { WorkspaceState state; state.id = slot; state.hyprId = -1; state.label = std::to_string(slot); state.active = (slot == focusedSlot); state.focused = state.focused; state.urgent = false; monitor.workspaceStates.emplace(slot, state); } } for (const auto &workspaceJson : workspacesJson) { if (!workspaceJson.is_object()) { continue; } const int monitorId = workspaceJson.value("monitorID", -1); const int workspaceId = workspaceJson.value("id", -1); auto monitorIt = monitors.find(monitorId); if (monitorIt == monitors.end() || workspaceId < 0) { continue; } auto &monitor = monitorIt->second; const int slot = ((workspaceId - 1) % HyprlandService::kWorkspaceSlotCount) + 1; auto &workspaceState = monitor.workspaceStates[slot]; workspaceState.id = slot; workspaceState.hyprId = workspaceId; workspaceState.focused = (monitor.focusedWorkspaceId == workspaceId); workspaceState.active = workspaceState.focused || workspaceJson.value("windows", 0) > 0; workspaceState.urgent = false; std::string labelCandidate; if (workspaceJson.contains("name") && workspaceJson["name"].is_string()) { labelCandidate = workspaceJson["name"].get(); } if (labelCandidate.empty() || labelCandidate == std::to_string(workspaceId)) { workspaceState.label = std::to_string(slot); } else { workspaceState.label = labelCandidate; } if (workspaceJson.contains("urgent") && workspaceJson["urgent"].is_boolean()) { workspaceState.urgent = workspaceJson["urgent"].get(); } else if (workspaceJson.contains("hasurgent") && workspaceJson["hasurgent"].is_boolean()) { workspaceState.urgent = workspaceJson["hasurgent"].get(); } } for (const auto &pair : monitors) { workspaceStateChanged.emit(pair.first); } } 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; } const HyprlandService::Monitor *HyprlandService::getMonitorById(int id) const { 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; } 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; } } const HyprlandService::Monitor * HyprlandService::getMonitorByIndex(std::size_t index) const { 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; } void HyprlandService::handle_urgent_window(std::string windowAddress) { std::string output; try { output = SystemHelper::get_command_output(kClientsCommand); } catch (const std::exception &ex) { std::cerr << "[Hyprland] Failed to query clients: " << ex.what() << std::endl; return; } auto clientsJson = nlohmann::json::parse(output, nullptr, false); if (!clientsJson.is_array()) { return; } int workspaceId = -1; for (const auto &client : clientsJson) { if (!client.is_object()) continue; std::string addr = client.value("address", ""); if (addr == "0x" + windowAddress) { if (client.contains("workspace") && client["workspace"].is_object()) { workspaceId = client["workspace"].value("id", -1); } break; } } if (workspaceId == -1) { return; } for (auto &pair : monitors) { auto &monitor = pair.second; bool changed = false; for (auto &wsPair : monitor.workspaceStates) { if (wsPair.second.hyprId == workspaceId) { if (!wsPair.second.urgent) { wsPair.second.urgent = true; changed = true; } } } if (changed) { workspaceStateChanged.emit(monitor.id); } } }