From 2570445aef9207eddbd3e8ed9dedebc92fb52b3c Mon Sep 17 00:00:00 2001 From: Arif Hasanic Date: Tue, 9 Dec 2025 00:09:14 +0100 Subject: [PATCH] add hyprland socket support --- .clang-format | 22 ++++++ CMakeLists.txt | 1 + include/bar.hpp | 12 +-- include/services/hyprland.hpp | 28 +++++++ include/widgets/clock.hpp | 11 +-- main.cpp | 3 +- src/bar.cpp | 42 ++++++----- src/services/hyprland.cpp | 134 ++++++++++++++++++++++++++++++++++ src/widgets/clock.cpp | 7 +- 9 files changed, 227 insertions(+), 33 deletions(-) create mode 100644 .clang-format create mode 100644 include/services/hyprland.hpp create mode 100644 src/services/hyprland.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5ba963f --- /dev/null +++ b/.clang-format @@ -0,0 +1,22 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +AlignConsecutiveAssignments: true +AlignEscapedNewlines: Left +AlignTrailingComments: Always +BreakBeforeBraces: Allman +ColumnLimit: 0 +MaxEmptyLinesToKeep: 1 +InsertNewlineAtEOF: true +BreakBeforeBinaryOperators: NonAssignment +BinPackArguments: false +PenaltyBreakBeforeFirstCallParameter: 1000 +ContinuationIndentWidth: 4 # Adjust the indent width for continuation lines + +IncludeCategories: + - Regex: '^(<.+>)$' + Priority: 1 + - Regex: '^"(.+\.hpp)"$' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeBlocks: Regroup \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d5b84c..1d8edd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ target_sources(bar_lib PUBLIC src/bar.cpp src/widgets/clock.cpp + src/services/hyprland.cpp ) include_directories(bar_lib PRIVATE include diff --git a/include/bar.hpp b/include/bar.hpp index ea49f46..88cb8cb 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -1,17 +1,19 @@ #pragma once -#include #include +#include +#include "services/hyprland.hpp" #include "widgets/clock.hpp" - -class Bar : public Gtk::Window { -public: +class Bar : public Gtk::Window +{ + public: Bar(); -protected: + protected: Clock clock; + HyprlandService hyprland; Gtk::CenterBox main_box{}; void setup_ui(); diff --git a/include/services/hyprland.hpp b/include/services/hyprland.hpp new file mode 100644 index 0000000..0e1ec43 --- /dev/null +++ b/include/services/hyprland.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +class HyprlandService +{ + public: + HyprlandService(); + ~HyprlandService(); + + // Setup the connection + void start(); + + // Signal that emits (EventName, EventData) + // Example: emits ("workspace", "1") + sigc::signal on_event; + void on_hyprland_event(std::string event, std::string data); + + private: + int m_fd = -1; // File descriptor for the socket + std::string m_buffer; // Buffer to handle partial socket reads + + bool on_socket_read(Glib::IOCondition condition); + void parse_message(const std::string &line); + std::string get_socket_path(); +}; diff --git a/include/widgets/clock.hpp b/include/widgets/clock.hpp index 74aea84..a3eafe2 100644 --- a/include/widgets/clock.hpp +++ b/include/widgets/clock.hpp @@ -1,12 +1,13 @@ #pragma once -#include -#include #include +#include +#include #include "interface/updateable.ipp" -class Clock : public Gtk::Label, public IUpdatable { -public: +class Clock : public Gtk::Label, public IUpdatable +{ + public: bool onUpdate(); -}; \ No newline at end of file +}; diff --git a/main.cpp b/main.cpp index 81f6ed8..c388e52 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,7 @@ #include "bar.hpp" -int main(int argc, char* argv[]) { +int main(int argc, char *argv[]) +{ auto app = Gtk::Application::create("org.example.mybar"); return app->make_window_and_run(argc, argv); diff --git a/src/bar.cpp b/src/bar.cpp index 71d7df9..d63319d 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -1,14 +1,16 @@ -#include +#include "bar.hpp" #include #include -#include "bar.hpp" -#include "glibmm/main.h" -#include "sigc++/functors/mem_fun.h" +#include "services/hyprland.hpp" #include "widgets/clock.hpp" -Bar::Bar() { +#include "glibmm/main.h" +#include "sigc++/functors/mem_fun.h" + +Bar::Bar() +{ gtk_layer_init_for_window(this->gobj()); gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_TOP, true); @@ -26,18 +28,22 @@ Bar::Bar() { Glib::signal_timeout().connect( sigc::mem_fun( - this->clock, - &Clock::onUpdate - ), - 1000 - ); + this->clock, + &Clock::onUpdate), + 1000); + + hyprland.on_event.connect( + sigc::mem_fun(this->hyprland, &HyprlandService::on_hyprland_event)); + + hyprland.start(); } -void Bar::setup_ui() { +void Bar::setup_ui() +{ Gtk::Box left_box{Gtk::Orientation::HORIZONTAL}; Gtk::Box center_box{Gtk::Orientation::HORIZONTAL}; Gtk::Box right_box{Gtk::Orientation::HORIZONTAL}; - + main_box.set_start_widget(left_box); main_box.set_center_widget(center_box); main_box.set_end_widget(right_box); @@ -45,24 +51,22 @@ void Bar::setup_ui() { Gtk::Label labelLeft("labelLeft"); Gtk::Label labelRight("labelRight"); - left_box.append(labelLeft); center_box.append(clock); right_box.append(labelRight); } -void Bar::load_css() { +void Bar::load_css() +{ auto css_provider = Gtk::CssProvider::create(); - + css_provider->load_from_data(R"( window { background-color: #222; color: #fff; } #clock-label { font-weight: bold; font-family: monospace; } )"); - + Gtk::StyleContext::add_provider_for_display( Gdk::Display::get_default(), css_provider, - GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - ); + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); } - diff --git a/src/services/hyprland.cpp b/src/services/hyprland.cpp new file mode 100644 index 0000000..41f0ca9 --- /dev/null +++ b/src/services/hyprland.cpp @@ -0,0 +1,134 @@ +#include "services/hyprland.hpp" + +#include +#include +#include +#include +#include +#include + +HyprlandService::HyprlandService() {} + +HyprlandService::~HyprlandService() +{ + if (m_fd != -1) + { + close(m_fd); + } +} + +void HyprlandService::on_hyprland_event(std::string event, std::string data) +{ + if (event == "workspace") + { + std::cout << "Switched to workspace: " << data << std::endl; + // Update your UI here... + } + else if (event == "activewindow") + { + std::cout << "Active window changed" << std::endl; + } +} + +void HyprlandService::start() +{ + std::string socket_path = get_socket_path(); + if (socket_path.empty()) + return; + + // 1. Create Socket + m_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (m_fd == -1) + { + std::cerr << "[Hyprland] Failed to create socket" << std::endl; + return; + } + + // 2. Connect + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socket_path.c_str(), sizeof(addr.sun_path) - 1); + + if (connect(m_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) + { + std::cerr << "[Hyprland] Failed to connect to " << socket_path << std::endl; + close(m_fd); + m_fd = -1; + return; + } + + std::cout << "[Hyprland] Connected to event socket." << std::endl; + + // 3. Register with GLib Main Loop + // This tells GTK to call 'on_socket_read' whenever there is data to read + Glib::signal_io().connect( + sigc::mem_fun(*this, &HyprlandService::on_socket_read), + m_fd, + Glib::IOCondition::IO_IN | Glib::IOCondition::IO_HUP | Glib::IOCondition::IO_ERR); +} + +bool HyprlandService::on_socket_read(Glib::IOCondition condition) +{ + // Handle disconnection or errors + auto error_mask = Glib::IOCondition::IO_HUP | Glib::IOCondition::IO_ERR; + + // 2. Perform the bitwise AND, then cast to int to check if non-zero + if (static_cast(condition & error_mask) != 0) + { + std::cerr << "[Hyprland] Socket disconnected." << std::endl; + close(m_fd); + m_fd = -1; + return false; + } + // Read data + char buffer[4096]; + ssize_t bytes_read = read(m_fd, buffer, sizeof(buffer) - 1); + + if (bytes_read > 0) + { + buffer[bytes_read] = '\0'; + m_buffer.append(buffer); + + // Process line by line + size_t pos = 0; + while ((pos = m_buffer.find('\n')) != std::string::npos) + { + std::string line = m_buffer.substr(0, pos); + parse_message(line); + m_buffer.erase(0, pos + 1); + } + } + + return true; // Continue listening +} + +void HyprlandService::parse_message(const std::string &line) +{ + // Hyprland events look like: "event>>data" + // Example: "workspace>>1" or "activewindow>>550a12,firefox" + size_t split = line.find(">>"); + if (split != std::string::npos) + { + std::string event_name = line.substr(0, split); + std::string event_data = line.substr(split + 2); + + // Emit the signal + on_event.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 ""; + } + + // Path format: $XDG_RUNTIME_DIR/hypr/$SIGNATURE/.socket2.sock + return std::string(runtime) + "/hypr/" + sig + "/.socket2.sock"; +} diff --git a/src/widgets/clock.cpp b/src/widgets/clock.cpp index 152d712..2a01a67 100644 --- a/src/widgets/clock.cpp +++ b/src/widgets/clock.cpp @@ -3,13 +3,14 @@ #include #include -bool Clock::onUpdate() { +bool Clock::onUpdate() +{ auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); - + std::stringstream ss; ss << std::put_time(std::localtime(&now), "%H:%M:%S"); set_text(ss.str()); return true; -} \ No newline at end of file +}