diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index ae0f1f7f4..c36f68e2d 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -9,6 +9,7 @@ concurrency: jobs: build: strategy: + fail-fast: false matrix: distro: - alpine diff --git a/Dockerfiles/opensuse b/Dockerfiles/opensuse index bdb42fbfb..6ac3e058a 100644 --- a/Dockerfiles/opensuse +++ b/Dockerfiles/opensuse @@ -6,4 +6,4 @@ RUN zypper -n up && \ zypper addrepo https://download.opensuse.org/repositories/X11:Wayland/openSUSE_Tumbleweed/X11:Wayland.repo | echo 'a' && \ zypper -n refresh && \ zypper -n install -t pattern devel_C_C++ && \ - zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel libxkbregistry-devel scdoc playerctl-devel + zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel libxkbregistry-devel scdoc playerctl-devel python3-packaging diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp index 9ce0ec335..11e73d8f6 100644 --- a/include/modules/hyprland/backend.hpp +++ b/include/modules/hyprland/backend.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -25,6 +26,10 @@ class IPC { static std::string getSocket1Reply(const std::string& rq); Json::Value getSocket1JsonReply(const std::string& rq); + static std::filesystem::path getSocketFolder(const char* instanceSig); + + protected: + static std::filesystem::path socketFolder_; private: void startIPC(); diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 0432b8703..6c6e09329 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -100,7 +100,7 @@ class Workspaces : public AModule, public EventHandler { void removeWorkspacesToRemove(); void createWorkspacesToCreate(); std::vector getVisibleWorkspaces(); - void updateWorkspaceStates(const std::vector& visibleWorkspaces); + void updateWorkspaceStates(); bool updateWindowsToCreate(); void extendOrphans(int workspaceId, Json::Value const& clientsJson); diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp index 29c65633b..a39fcd694 100644 --- a/src/modules/hyprland/backend.cpp +++ b/src/modules/hyprland/backend.cpp @@ -15,22 +15,31 @@ namespace waybar::modules::hyprland { -std::filesystem::path getSocketFolder(const char* instanceSig) { +std::filesystem::path IPC::socketFolder_; + +std::filesystem::path IPC::getSocketFolder(const char* instanceSig) { // socket path, specified by EventManager of Hyprland - static std::filesystem::path socketFolder; - if (!socketFolder.empty()) { - return socketFolder; + if (!socketFolder_.empty()) { + spdlog::warn("socketFolder already set, using {}", socketFolder_.c_str()); + return socketFolder_; + } + + const char* xdgRuntimeDirEnv = std::getenv("XDG_RUNTIME_DIR"); + std::filesystem::path xdgRuntimeDir; + // Only set path if env variable is set + if (xdgRuntimeDirEnv) { + xdgRuntimeDir = std::filesystem::path(xdgRuntimeDirEnv); } - std::filesystem::path xdgRuntimeDir = std::filesystem::path(getenv("XDG_RUNTIME_DIR")); if (!xdgRuntimeDir.empty() && std::filesystem::exists(xdgRuntimeDir / "hypr")) { - socketFolder = xdgRuntimeDir / "hypr"; + socketFolder_ = xdgRuntimeDir / "hypr"; } else { spdlog::warn("$XDG_RUNTIME_DIR/hypr does not exist, falling back to /tmp/hypr"); - socketFolder = std::filesystem::path("/tmp") / "hypr"; + socketFolder_ = std::filesystem::path("/tmp") / "hypr"; } - socketFolder = socketFolder / instanceSig; - return socketFolder; + + socketFolder_ = socketFolder_ / instanceSig; + return socketFolder_; } void IPC::startIPC() { @@ -59,7 +68,7 @@ void IPC::startIPC() { addr.sun_family = AF_UNIX; - auto socketPath = getSocketFolder(his) / ".socket2.sock"; + auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock"; strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1); addr.sun_path[sizeof(addr.sun_path) - 1] = 0; @@ -169,7 +178,7 @@ std::string IPC::getSocket1Reply(const std::string& rq) { sockaddr_un serverAddress = {0}; serverAddress.sun_family = AF_UNIX; - std::string socketPath = getSocketFolder(instanceSig) / ".socket.sock"; + std::string socketPath = IPC::getSocketFolder(instanceSig) / ".socket.sock"; // Use snprintf to copy the socketPath string into serverAddress.sun_path if (snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), "%s", socketPath.c_str()) < diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 3b129375c..3209243cb 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -128,10 +128,7 @@ void Workspaces::doUpdate() { removeWorkspacesToRemove(); createWorkspacesToCreate(); - - std::vector visibleWorkspaces = getVisibleWorkspaces(); - - updateWorkspaceStates(visibleWorkspaces); + updateWorkspaceStates(); updateWindowCount(); sortWorkspaces(); @@ -870,7 +867,8 @@ bool Workspaces::updateWindowsToCreate() { return anyWindowCreated; } -void Workspaces::updateWorkspaceStates(const std::vector &visibleWorkspaces) { +void Workspaces::updateWorkspaceStates() { + const std::vector visibleWorkspaces = getVisibleWorkspaces(); auto updatedWorkspaces = gIPC->getSocket1JsonReply("workspaces"); for (auto &workspace : m_workspaces) { workspace->setActive(workspace->name() == m_activeWorkspaceName || diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 086ed5d10..2adde69ca 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -505,10 +505,8 @@ void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) { // that the workspace itself isn't focused. Therefore we need to // check if any of its nodes are focused as well. bool focused = node["focused"].asBool() || - std::any_of(node["nodes"].begin(), node["nodes"].end(), - [](const auto &child) { - return child["focused"].asBool(); - }); + std::any_of(node["nodes"].begin(), node["nodes"].end(), + [](const auto &child) { return child["focused"].asBool(); }); if (focused) { button.show(); diff --git a/subprojects/spdlog.wrap b/subprojects/spdlog.wrap index 69ef566fa..08004c901 100644 --- a/subprojects/spdlog.wrap +++ b/subprojects/spdlog.wrap @@ -1,12 +1,13 @@ [wrap-file] -directory = spdlog-1.11.0 -source_url = https://github.com/gabime/spdlog/archive/v1.11.0.tar.gz -source_filename = v1.11.0.tar.gz -source_hash = ca5cae8d6cac15dae0ec63b21d6ad3530070650f68076f3a4a862ca293a858bb -patch_filename = spdlog_1.11.0-2_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/spdlog_1.11.0-2/get_patch -patch_hash = db1364fe89502ac67f245a6c8c51290a52afd74a51eed26fa9ecb5b3443df57a -wrapdb_version = 1.11.0-2 +directory = spdlog-1.12.0 +source_url = https://github.com/gabime/spdlog/archive/refs/tags/v1.12.0.tar.gz +source_filename = spdlog-1.12.0.tar.gz +source_hash = 4dccf2d10f410c1e2feaff89966bfc49a1abb29ef6f08246335b110e001e09a9 +patch_filename = spdlog_1.12.0-2_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/spdlog_1.12.0-2/get_patch +patch_hash = 9596972d1eb2e0a69cea4a53273ca7bbbcb9b2fa872cd734864fc7232dc2d573 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/spdlog_1.12.0-2/spdlog-1.12.0.tar.gz +wrapdb_version = 1.12.0-2 [provide] spdlog = spdlog_dep diff --git a/test/config.cpp b/test/config.cpp index ad3df0651..c60519ce2 100644 --- a/test/config.cpp +++ b/test/config.cpp @@ -117,3 +117,42 @@ TEST_CASE("Load multiple bar config with include", "[config]") { REQUIRE(data.size() == 4); REQUIRE(data[0]["output"].asString() == "OUT-0"); } + +TEST_CASE("Load Hyprland Workspaces bar config", "[config]") { + waybar::Config conf; + conf.load("test/config/hyprland-workspaces.json"); + + auto& data = conf.getConfig(); + auto hyprland = data[0]["hyprland/workspaces"]; + auto hyprland_window_rewrite = data[0]["hyprland/workspaces"]["window-rewrite"]; + auto hyprland_format_icons = data[0]["hyprland/workspaces"]["format-icons"]; + auto hyprland_persistent_workspaces = data[0]["hyprland/workspaces"]["persistent-workspaces"]; + + REQUIRE(data.isArray()); + REQUIRE(data.size() == 1); + REQUIRE(data[0]["height"].asInt() == 20); + REQUIRE(data[0]["layer"].asString() == "bottom"); + REQUIRE(data[0]["output"].isArray()); + REQUIRE(data[0]["output"][0].asString() == "HDMI-0"); + REQUIRE(data[0]["output"][1].asString() == "DP-0"); + + REQUIRE(hyprland["active-only"].asBool() == true); + REQUIRE(hyprland["all-outputs"].asBool() == false); + REQUIRE(hyprland["move-to-monitor"].asBool() == true); + REQUIRE(hyprland["format"].asString() == "{icon} {windows}"); + REQUIRE(hyprland["format-window-separator"].asString() == " "); + REQUIRE(hyprland["on-scroll-down"].asString() == "hyprctl dispatch workspace e-1"); + REQUIRE(hyprland["on-scroll-up"].asString() == "hyprctl dispatch workspace e+1"); + REQUIRE(hyprland["show-special"].asBool() == true); + REQUIRE(hyprland["window-rewrite-default"].asString() == ""); + REQUIRE(hyprland["window-rewrite-separator"].asString() == " "); + REQUIRE(hyprland_format_icons["1"].asString() == "󰎤"); + REQUIRE(hyprland_format_icons["2"].asString() == "󰎧"); + REQUIRE(hyprland_format_icons["3"].asString() == "󰎪"); + REQUIRE(hyprland_format_icons["default"].asString() == ""); + REQUIRE(hyprland_format_icons["empty"].asString() == "󱓼"); + REQUIRE(hyprland_format_icons["urgent"].asString() == "󱨇"); + REQUIRE(hyprland_persistent_workspaces["1"].asString() == "HDMI-0"); + REQUIRE(hyprland_window_rewrite["title"].asString() == ""); + REQUIRE(hyprland["sort-by"].asString() == "number"); +} diff --git a/test/config/hyprland-workspaces.json b/test/config/hyprland-workspaces.json new file mode 100644 index 000000000..dd733897f --- /dev/null +++ b/test/config/hyprland-workspaces.json @@ -0,0 +1,37 @@ +[ + { + "height": 20, + "layer": "bottom", + "output": [ + "HDMI-0", + "DP-0" + ], + "hyprland/workspaces": { + "active-only": true, + "all-outputs": false, + "show-special": true, + "move-to-monitor": true, + "format": "{icon} {windows}", + "format-window-separator": " ", + "format-icons": { + "1": "󰎤", + "2": "󰎧", + "3": "󰎪", + "default": "", + "empty": "󱓼", + "urgent": "󱨇" + }, + "persistent-workspaces": { + "1": "HDMI-0" + }, + "on-scroll-down": "hyprctl dispatch workspace e-1", + "on-scroll-up": "hyprctl dispatch workspace e+1", + "window-rewrite": { + "title": "" + }, + "window-rewrite-default": "", + "window-rewrite-separator": " ", + "sort-by": "number" + } + } +] diff --git a/test/hyprland/backend.cpp b/test/hyprland/backend.cpp new file mode 100644 index 000000000..e6b209da4 --- /dev/null +++ b/test/hyprland/backend.cpp @@ -0,0 +1,55 @@ +#include +#if __has_include() +#include +#else +#include +#endif + +#include "fixtures/IPCTestFixture.hpp" +#include "modules/hyprland/backend.hpp" + +namespace fs = std::filesystem; +namespace hyprland = waybar::modules::hyprland; + +TEST_CASE_METHOD(IPCTestFixture, "XDGRuntimeDirExists", "[getSocketFolder]") { + // Test case: XDG_RUNTIME_DIR exists and contains "hypr" directory + // Arrange + tempDir = fs::temp_directory_path() / "hypr_test/run/user/1000"; + fs::path expectedPath = tempDir / "hypr" / instanceSig; + fs::create_directories(tempDir / "hypr" / instanceSig); + setenv("XDG_RUNTIME_DIR", tempDir.c_str(), 1); + + // Act + fs::path actualPath = getSocketFolder(instanceSig); + + // Assert expected result + REQUIRE(actualPath == expectedPath); +} + +TEST_CASE_METHOD(IPCTestFixture, "XDGRuntimeDirDoesNotExist", "[getSocketFolder]") { + // Test case: XDG_RUNTIME_DIR does not exist + // Arrange + unsetenv("XDG_RUNTIME_DIR"); + fs::path expectedPath = fs::path("/tmp") / "hypr" / instanceSig; + + // Act + fs::path actualPath = getSocketFolder(instanceSig); + + // Assert expected result + REQUIRE(actualPath == expectedPath); +} + +TEST_CASE_METHOD(IPCTestFixture, "XDGRuntimeDirExistsNoHyprDir", "[getSocketFolder]") { + // Test case: XDG_RUNTIME_DIR exists but does not contain "hypr" directory + // Arrange + fs::path tempDir = fs::temp_directory_path() / "hypr_test/run/user/1000"; + fs::create_directories(tempDir); + setenv("XDG_RUNTIME_DIR", tempDir.c_str(), 1); + fs::path expectedPath = fs::path("/tmp") / "hypr" / instanceSig; + + // Act + fs::path actualPath = getSocketFolder(instanceSig); + + // Assert expected result + REQUIRE(actualPath == expectedPath); +} diff --git a/test/hyprland/fixtures/IPCTestFixture.hpp b/test/hyprland/fixtures/IPCTestFixture.hpp new file mode 100644 index 000000000..3b04b3b0e --- /dev/null +++ b/test/hyprland/fixtures/IPCTestFixture.hpp @@ -0,0 +1,16 @@ +#include "modules/hyprland/backend.hpp" + +namespace fs = std::filesystem; +namespace hyprland = waybar::modules::hyprland; + +class IPCTestFixture : public hyprland::IPC { + public: + IPCTestFixture() : IPC() { IPC::socketFolder_ = ""; } + ~IPCTestFixture() { fs::remove_all(tempDir); } + + protected: + const char* instanceSig = "instance_sig"; + fs::path tempDir = fs::temp_directory_path() / "hypr_test"; + + private: +}; diff --git a/test/hyprland/meson.build b/test/hyprland/meson.build new file mode 100644 index 000000000..533022fc8 --- /dev/null +++ b/test/hyprland/meson.build @@ -0,0 +1,28 @@ +test_inc = include_directories('../../include') + +test_dep = [ + catch2, + fmt, + gtkmm, + jsoncpp, + spdlog, +] + +test_src = files( + '../main.cpp', + 'backend.cpp', + '../../src/modules/hyprland/backend.cpp' +) + +hyprland_test = executable( + 'hyprland_test', + test_src, + dependencies: test_dep, + include_directories: test_inc, +) + +test( + 'hyprland', + hyprland_test, + workdir: meson.project_source_root(), +) diff --git a/test/meson.build b/test/meson.build index 7c9226712..ea430c504 100644 --- a/test/meson.build +++ b/test/meson.build @@ -1,4 +1,5 @@ test_inc = include_directories('../include') + test_dep = [ catch2, fmt, @@ -6,21 +7,13 @@ test_dep = [ jsoncpp, spdlog, ] + test_src = files( 'main.cpp', - 'JsonParser.cpp', - 'SafeSignal.cpp', 'config.cpp', - 'css_reload_helper.cpp', '../src/config.cpp', - '../src/util/css_reload_helper.cpp', ) -if tz_dep.found() - test_dep += tz_dep - test_src += files('date.cpp') -endif - waybar_test = executable( 'waybar_test', test_src, @@ -33,3 +26,6 @@ test( waybar_test, workdir: meson.project_source_root(), ) + +subdir('utils') +subdir('hyprland') diff --git a/test/JsonParser.cpp b/test/utils/JsonParser.cpp similarity index 100% rename from test/JsonParser.cpp rename to test/utils/JsonParser.cpp diff --git a/test/SafeSignal.cpp b/test/utils/SafeSignal.cpp similarity index 98% rename from test/SafeSignal.cpp rename to test/utils/SafeSignal.cpp index f496d7ab2..341e8e2ea 100644 --- a/test/SafeSignal.cpp +++ b/test/utils/SafeSignal.cpp @@ -10,7 +10,7 @@ #include #include -#include "GlibTestsFixture.hpp" +#include "fixtures/GlibTestsFixture.hpp" using namespace waybar; diff --git a/test/css_reload_helper.cpp b/test/utils/css_reload_helper.cpp similarity index 100% rename from test/css_reload_helper.cpp rename to test/utils/css_reload_helper.cpp diff --git a/test/date.cpp b/test/utils/date.cpp similarity index 100% rename from test/date.cpp rename to test/utils/date.cpp diff --git a/test/GlibTestsFixture.hpp b/test/utils/fixtures/GlibTestsFixture.hpp similarity index 100% rename from test/GlibTestsFixture.hpp rename to test/utils/fixtures/GlibTestsFixture.hpp diff --git a/test/utils/meson.build b/test/utils/meson.build new file mode 100644 index 000000000..b7b3665a8 --- /dev/null +++ b/test/utils/meson.build @@ -0,0 +1,36 @@ +test_inc = include_directories('../../include') + +test_dep = [ + catch2, + fmt, + gtkmm, + jsoncpp, + spdlog, +] +test_src = files( + '../main.cpp', + '../config.cpp', + '../../src/config.cpp', + 'JsonParser.cpp', + 'SafeSignal.cpp', + 'css_reload_helper.cpp', + '../../src/util/css_reload_helper.cpp', +) + +if tz_dep.found() + test_dep += tz_dep + test_src += files('date.cpp') +endif + +utils_test = executable( + 'utils_test', + test_src, + dependencies: test_dep, + include_directories: test_inc, +) + +test( + 'utils', + utils_test, + workdir: meson.project_source_root(), +)