add hyprland socket support

This commit is contained in:
2025-12-09 00:09:14 +01:00
parent de6ece13fa
commit 2570445aef
9 changed files with 227 additions and 33 deletions

22
.clang-format Normal file
View File

@@ -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

View File

@@ -20,6 +20,7 @@ target_sources(bar_lib
PUBLIC PUBLIC
src/bar.cpp src/bar.cpp
src/widgets/clock.cpp src/widgets/clock.cpp
src/services/hyprland.cpp
) )
include_directories(bar_lib PRIVATE include_directories(bar_lib PRIVATE
include include

View File

@@ -1,17 +1,19 @@
#pragma once #pragma once
#include <gtkmm.h>
#include <gtk4-layer-shell/gtk4-layer-shell.h> #include <gtk4-layer-shell/gtk4-layer-shell.h>
#include <gtkmm.h>
#include "services/hyprland.hpp"
#include "widgets/clock.hpp" #include "widgets/clock.hpp"
class Bar : public Gtk::Window
class Bar : public Gtk::Window { {
public: public:
Bar(); Bar();
protected: protected:
Clock clock; Clock clock;
HyprlandService hyprland;
Gtk::CenterBox main_box{}; Gtk::CenterBox main_box{};
void setup_ui(); void setup_ui();

View File

@@ -0,0 +1,28 @@
#pragma once
#include <glibmm.h>
#include <sigc++/sigc++.h>
#include <string>
class HyprlandService
{
public:
HyprlandService();
~HyprlandService();
// Setup the connection
void start();
// Signal that emits (EventName, EventData)
// Example: emits ("workspace", "1")
sigc::signal<void(std::string, std::string)> 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();
};

View File

@@ -1,12 +1,13 @@
#pragma once #pragma once
#include <gtkmm/label.h>
#include <gtkmm.h>
#include <gtk4-layer-shell/gtk4-layer-shell.h> #include <gtk4-layer-shell/gtk4-layer-shell.h>
#include <gtkmm.h>
#include <gtkmm/label.h>
#include "interface/updateable.ipp" #include "interface/updateable.ipp"
class Clock : public Gtk::Label, public IUpdatable { class Clock : public Gtk::Label, public IUpdatable
public: {
public:
bool onUpdate(); bool onUpdate();
}; };

View File

@@ -1,6 +1,7 @@
#include "bar.hpp" #include "bar.hpp"
int main(int argc, char* argv[]) { int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.example.mybar"); auto app = Gtk::Application::create("org.example.mybar");
return app->make_window_and_run<Bar>(argc, argv); return app->make_window_and_run<Bar>(argc, argv);

View File

@@ -1,14 +1,16 @@
#include <ctime> #include "bar.hpp"
#include <gtkmm/enums.h> #include <gtkmm/enums.h>
#include <gtkmm/label.h> #include <gtkmm/label.h>
#include "bar.hpp" #include "services/hyprland.hpp"
#include "glibmm/main.h"
#include "sigc++/functors/mem_fun.h"
#include "widgets/clock.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_init_for_window(this->gobj());
gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_TOP, true); gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_TOP, true);
@@ -26,18 +28,22 @@ Bar::Bar() {
Glib::signal_timeout().connect( Glib::signal_timeout().connect(
sigc::mem_fun( sigc::mem_fun(
this->clock, this->clock,
&Clock::onUpdate &Clock::onUpdate),
), 1000);
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 left_box{Gtk::Orientation::HORIZONTAL};
Gtk::Box center_box{Gtk::Orientation::HORIZONTAL}; Gtk::Box center_box{Gtk::Orientation::HORIZONTAL};
Gtk::Box right_box{Gtk::Orientation::HORIZONTAL}; Gtk::Box right_box{Gtk::Orientation::HORIZONTAL};
main_box.set_start_widget(left_box); main_box.set_start_widget(left_box);
main_box.set_center_widget(center_box); main_box.set_center_widget(center_box);
main_box.set_end_widget(right_box); main_box.set_end_widget(right_box);
@@ -45,24 +51,22 @@ void Bar::setup_ui() {
Gtk::Label labelLeft("labelLeft"); Gtk::Label labelLeft("labelLeft");
Gtk::Label labelRight("labelRight"); Gtk::Label labelRight("labelRight");
left_box.append(labelLeft); left_box.append(labelLeft);
center_box.append(clock); center_box.append(clock);
right_box.append(labelRight); right_box.append(labelRight);
} }
void Bar::load_css() { void Bar::load_css()
{
auto css_provider = Gtk::CssProvider::create(); auto css_provider = Gtk::CssProvider::create();
css_provider->load_from_data(R"( css_provider->load_from_data(R"(
window { background-color: #222; color: #fff; } window { background-color: #222; color: #fff; }
#clock-label { font-weight: bold; font-family: monospace; } #clock-label { font-weight: bold; font-family: monospace; }
)"); )");
Gtk::StyleContext::add_provider_for_display( Gtk::StyleContext::add_provider_for_display(
Gdk::Display::get_default(), Gdk::Display::get_default(),
css_provider, css_provider,
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
);
} }

134
src/services/hyprland.cpp Normal file
View File

@@ -0,0 +1,134 @@
#include "services/hyprland.hpp"
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
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<int>(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";
}

View File

@@ -3,13 +3,14 @@
#include <chrono> #include <chrono>
#include <iomanip> #include <iomanip>
bool Clock::onUpdate() { bool Clock::onUpdate()
{
auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::stringstream ss; std::stringstream ss;
ss << std::put_time(std::localtime(&now), "%H:%M:%S"); ss << std::put_time(std::localtime(&now), "%H:%M:%S");
set_text(ss.str()); set_text(ss.str());
return true; return true;
} }