diff --git a/CMakeLists.txt b/CMakeLists.txt index 01adc38..07272b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ target_sources(bar_lib src/app.cpp src/bar/bar.cpp src/widgets/clock.cpp + src/widgets/date.cpp src/widgets/workspaceIndicator.cpp src/widgets/volumeWidget.cpp src/widgets/webWidget.cpp @@ -52,6 +53,7 @@ target_link_libraries(bar bar_lib ${GTKMM_LIBRARIES} ${LAYERSHELL_LIBRARIES} ${W # Copy `resources/bar.css` into the build directory when it changes set(RES_SRC "${CMAKE_CURRENT_SOURCE_DIR}/resources/bar.css") set(RES_DST "${CMAKE_CURRENT_BINARY_DIR}/resources/bar.css") +set(USER_CONFIG_CSS "$ENV{HOME}/.config/bar/bar.css") add_custom_command( OUTPUT ${RES_DST} @@ -62,5 +64,14 @@ add_custom_command( VERBATIM ) -add_custom_target(copy_resources ALL DEPENDS ${RES_DST}) +add_custom_command( + OUTPUT ${USER_CONFIG_CSS} + COMMAND ${CMAKE_COMMAND} -E make_directory "$ENV{HOME}/.config/bar" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${RES_SRC}" "${USER_CONFIG_CSS}" + DEPENDS "${RES_SRC}" + COMMENT "Copy resources/bar.css to ~/.config/bar/bar.css" + VERBATIM +) + +add_custom_target(copy_resources ALL DEPENDS ${RES_DST} ${USER_CONFIG_CSS}) add_dependencies(bar copy_resources) diff --git a/include/bar/bar.hpp b/include/bar/bar.hpp index ddb16e3..8db7014 100644 --- a/include/bar/bar.hpp +++ b/include/bar/bar.hpp @@ -6,6 +6,7 @@ #include "services/hyprland.hpp" #include "services/tray.hpp" #include "widgets/clock.hpp" +#include "widgets/date.hpp" #include "widgets/tray.hpp" #include "widgets/webWidget.hpp" #include "widgets/workspaceIndicator.hpp" @@ -23,6 +24,7 @@ class Bar : public Gtk::Window { private: Clock clock; + Date date; WebWidget homeAssistant {"HA", "Home Assistant", "https://home.rivercry.com"}; TrayService &trayService; diff --git a/include/interface/updateable.ipp b/include/interface/updateable.ipp index 147e455..69196dc 100644 --- a/include/interface/updateable.ipp +++ b/include/interface/updateable.ipp @@ -1,3 +1,5 @@ +#pragma once + class IUpdatable { public: virtual ~IUpdatable() = default; diff --git a/include/services/hyprland.hpp b/include/services/hyprland.hpp index 772abe6..bb254d7 100644 --- a/include/services/hyprland.hpp +++ b/include/services/hyprland.hpp @@ -61,6 +61,7 @@ class HyprlandService { std::string get_socket_path(); void refresh_monitors(); void refresh_workspaces(); + void handle_urgent_window(std::string windowAddress); }; inline void HyprlandService::printMonitor(const Monitor &mon) const { diff --git a/include/widgets/date.hpp b/include/widgets/date.hpp new file mode 100644 index 0000000..765a1e1 --- /dev/null +++ b/include/widgets/date.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include + +#include "interface/updateable.ipp" + +class Date : public Gtk::Label, public IUpdatable { + public: + bool onUpdate(); +}; diff --git a/src/bar/bar.cpp b/src/bar/bar.cpp index 2dc5d81..8fd8b2b 100644 --- a/src/bar/bar.cpp +++ b/src/bar/bar.cpp @@ -1,11 +1,13 @@ #include "bar/bar.hpp" #include "gtk/gtk.h" +#include "widgets/date.hpp" #include "widgets/spacer.hpp" #include "widgets/volumeWidget.hpp" #include "widgets/workspaceIndicator.hpp" #include "helpers/systemHelper.hpp" +#include #include #include #include @@ -33,6 +35,9 @@ Bar::Bar(GdkMonitor *monitor, HyprlandService &hyprlandService, set_child(main_box); + + this->volumeWidget = Gtk::make_managed(); + load_css(); setup_ui(); @@ -41,6 +46,13 @@ Bar::Bar(GdkMonitor *monitor, HyprlandService &hyprlandService, Glib::signal_timeout().connect(sigc::mem_fun(clock, &Clock::onUpdate), 1000); + + date.onUpdate(); + + Glib::signal_timeout().connect(sigc::mem_fun(date, &Date::onUpdate), + 1000); + + } void Bar::setup_ui() { @@ -65,14 +77,11 @@ void Bar::setup_ui() { left_box.append(*workspaceIndicator); clock.set_name("clock-label"); - clock.set_hexpand(false); - clock.set_halign(Gtk::Align::CENTER); - clock.set_valign(Gtk::Align::CENTER); - center_box.append(clock); + center_box.append(this->date); center_box.append(*(new Spacer())); - - volumeWidget = Gtk::make_managed(); - center_box.append(*volumeWidget); + center_box.append(this->clock); + center_box.append(*(new Spacer())); + center_box.append(*this->volumeWidget); trayWidget = Gtk::make_managed(trayService); @@ -83,8 +92,17 @@ void Bar::setup_ui() { void Bar::load_css() { auto css_provider = Gtk::CssProvider::create(); + std::string css_path = "resources/bar.css"; + const char* home = std::getenv("HOME"); + if (home) { + std::filesystem::path config_path = std::filesystem::path(home) / ".config/bar/bar.css"; + if (std::filesystem::exists(config_path)) { + css_path = config_path.string(); + } + } + const std::string css = - SystemHelper::read_file_to_string("resources/bar.css"); + SystemHelper::read_file_to_string(css_path); css_provider->load_from_data(css); Gtk::StyleContext::add_provider_for_display( diff --git a/src/services/hyprland.cpp b/src/services/hyprland.cpp index 5598e43..6eac8a7 100644 --- a/src/services/hyprland.cpp +++ b/src/services/hyprland.cpp @@ -17,6 +17,7 @@ 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; @@ -32,8 +33,11 @@ HyprlandService::~HyprlandService() { } } -void HyprlandService::on_hyprland_event(std::string event, - std::string /*data*/) { +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(); @@ -326,4 +330,58 @@ HyprlandService::getMonitorByIndex(std::size_t index) const { 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); + } + } } \ No newline at end of file diff --git a/src/widgets/date.cpp b/src/widgets/date.cpp new file mode 100644 index 0000000..32c835b --- /dev/null +++ b/src/widgets/date.cpp @@ -0,0 +1,16 @@ +#include "widgets/date.hpp" + +#include +#include + +bool Date::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), "%a, %d.%m.%Y"); + + set_text(ss.str()); + + return true; +} diff --git a/src/widgets/workspaceIndicator.cpp b/src/widgets/workspaceIndicator.cpp index 1d05ce1..64664b9 100644 --- a/src/widgets/workspaceIndicator.cpp +++ b/src/widgets/workspaceIndicator.cpp @@ -31,7 +31,7 @@ WorkspaceIndicator::~WorkspaceIndicator() { } void WorkspaceIndicator::on_workspace_update(int monitorId) { - if (monitorId != monitorId && monitorId != -1) { + if (this->monitorId != monitorId && monitorId != -1) { return; } @@ -81,13 +81,13 @@ void WorkspaceIndicator::rebuild() { label->add_controller(gesture); if (state != nullptr) { - if (state->focused) { - label->add_css_class("workspace-pill-focused"); - } else if (state->active) { - label->add_css_class("workspace-pill-active"); - } - - if (state->urgent) { + if (state->urgent != true) { + if (state->focused) { + label->add_css_class("workspace-pill-focused"); + } else if (state->active) { + label->add_css_class("workspace-pill-active"); + } + } else { label->add_css_class("workspace-pill-urgent"); } }