From d7ed4f1fa8f749ae257d769013269d262ffb90c4 Mon Sep 17 00:00:00 2001 From: dpayne Date: Sun, 21 Jan 2024 18:23:46 -0800 Subject: [PATCH 1/5] Adding css reloader --- include/client.hpp | 3 + include/util/css_reload_helper.hpp | 49 +++++++ meson.build | 3 +- src/client.cpp | 7 +- src/util/css_reload_helper.cpp | 214 +++++++++++++++++++++++++++++ test/css_reload_helper.cpp | 188 +++++++++++++++++++++++++ test/meson.build | 2 + 7 files changed, 463 insertions(+), 3 deletions(-) create mode 100644 include/util/css_reload_helper.hpp create mode 100644 src/util/css_reload_helper.cpp create mode 100644 test/css_reload_helper.cpp diff --git a/include/client.hpp b/include/client.hpp index 9ec29ef3..641ee6a7 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -7,6 +7,7 @@ #include "bar.hpp" #include "config.hpp" +#include "util/css_reload_helper.hpp" #include "util/portal.hpp" struct zwlr_layer_shell_v1; @@ -55,6 +56,8 @@ class Client { Glib::RefPtr css_provider_; std::unique_ptr portal; std::list outputs_; + std::unique_ptr m_cssReloadHelper; + std::string m_cssFile; }; } // namespace waybar diff --git a/include/util/css_reload_helper.hpp b/include/util/css_reload_helper.hpp new file mode 100644 index 00000000..62507e42 --- /dev/null +++ b/include/util/css_reload_helper.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +struct pollfd; + +namespace waybar { +class CssReloadHelper { + public: + CssReloadHelper(std::string cssFile, std::function callback); + + ~CssReloadHelper(); + + virtual void monitorChanges(); + + void stop(); + + protected: + std::vector parseImports(const std::string& cssFile); + + void parseImports(const std::string& cssFile, + std::unordered_map& imports); + + + void watchFiles(const std::vector& files); + + bool handleInotifyEvents(int fd); + + bool watch(int inotifyFd, pollfd * pollFd); + + virtual std::string getFileContents(const std::string& filename); + + virtual std::string findPath(const std::string& filename); + + private: + std::string m_cssFile; + std::function m_callback; + std::atomic m_running = false; + std::thread m_thread; + std::mutex m_mutex; + std::condition_variable m_cv; +}; +} // namespace waybar diff --git a/meson.build b/meson.build index c1ae48b5..d7549731 100644 --- a/meson.build +++ b/meson.build @@ -196,7 +196,8 @@ src_files = files( 'src/util/sanitize_str.cpp', 'src/util/rewrite_string.cpp', 'src/util/gtk_icon.cpp', - 'src/util/regex_collection.cpp' + 'src/util/regex_collection.cpp', + 'src/util/css_reload_helper.cpp' ) inc_dirs = ['include'] diff --git a/src/client.cpp b/src/client.cpp index 066247e7..ff1be5d8 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -262,15 +262,18 @@ int waybar::Client::main(int argc, char *argv[]) { if (!portal) { portal = std::make_unique(); } - auto css_file = getStyle(style_opt); - setupCss(css_file); + m_cssFile = getStyle(style_opt); + setupCss(m_cssFile); + m_cssReloadHelper = std::make_unique(m_cssFile, [&]() { setupCss(m_cssFile); }); portal->signal_appearance_changed().connect([&](waybar::Appearance appearance) { auto css_file = getStyle(style_opt, appearance); setupCss(css_file); }); + m_cssReloadHelper->monitorChanges(); bindInterfaces(); gtk_app->hold(); gtk_app->run(); + m_cssReloadHelper.reset(); // stop watching css file bars.clear(); return 0; } diff --git a/src/util/css_reload_helper.cpp b/src/util/css_reload_helper.cpp new file mode 100644 index 00000000..4038f1f3 --- /dev/null +++ b/src/util/css_reload_helper.cpp @@ -0,0 +1,214 @@ +#include "util/css_reload_helper.hpp" + +#include +#include +#include + +#include +#include +#include +#include + +#include "config.hpp" +namespace { +const std::regex IMPORT_REGEX(R"(@import\s+(?:url\()?(?:"|')([^"')]+)(?:"|')\)?;)"); +} + +waybar::CssReloadHelper::CssReloadHelper(std::string cssFile, std::function callback) + : m_cssFile(std::move(cssFile)), m_callback(std::move(callback)) {} + +waybar::CssReloadHelper::~CssReloadHelper() { stop(); } + +std::string waybar::CssReloadHelper::getFileContents(const std::string& filename) { + if (filename.empty()) { + return {}; + } + + std::ifstream file(filename); + if (!file.is_open()) { + return {}; + } + + return std::string((std::istreambuf_iterator(file)), std::istreambuf_iterator()); +} + +std::string waybar::CssReloadHelper::findPath(const std::string& filename) { + // try path and fallback to looking relative to the config + if (std::filesystem::exists(filename)) { + return filename; + } + + return Config::findConfigPath({filename}).value_or(""); +} + +void waybar::CssReloadHelper::monitorChanges() { + m_thread = std::thread([this] { + m_running = true; + while (m_running) { + auto files = parseImports(m_cssFile); + watchFiles(files); + } + }); +} + +std::vector waybar::CssReloadHelper::parseImports(const std::string& cssFile) { + std::unordered_map imports; + + auto cssFullPath = findPath(cssFile); + if (cssFullPath.empty()) { + spdlog::error("Failed to find css file: {}", cssFile); + return {}; + } + + spdlog::debug("Parsing imports for file: {}", cssFullPath); + imports[cssFullPath] = false; + + auto previousSize = 0UL; + auto maxIterations = 100U; + do { + previousSize = imports.size(); + for (const auto& [file, parsed] : imports) { + if (!parsed) { + parseImports(file, imports); + } + } + + } while (imports.size() > previousSize && maxIterations-- > 0); + + std::vector result; + for (const auto& [file, parsed] : imports) { + if (parsed) { + spdlog::debug("Adding file to watch list: {}", file); + result.push_back(file); + } + } + + return result; +} + +void waybar::CssReloadHelper::parseImports(const std::string& cssFile, + std::unordered_map& imports) { + // if the file has already been parsed, skip + if (imports.find(cssFile) != imports.end() && imports[cssFile]) { + return; + } + + auto contents = getFileContents(cssFile); + std::smatch matches; + while (std::regex_search(contents, matches, IMPORT_REGEX)) { + auto importFile = findPath({matches[1].str()}); + if (!importFile.empty() && imports.find(importFile) == imports.end()) { + imports[importFile] = false; + } + + contents = matches.suffix().str(); + } + + imports[cssFile] = true; +} + +void waybar::CssReloadHelper::stop() { + if (!m_running) { + return; + } + + m_running = false; + m_cv.notify_all(); + if (m_thread.joinable()) { + m_thread.join(); + } +} + +void waybar::CssReloadHelper::watchFiles(const std::vector& files) { + auto inotifyFd = inotify_init1(IN_NONBLOCK); + if (inotifyFd < 0) { + spdlog::error("Failed to initialize inotify: {}", strerror(errno)); + return; + } + + std::vector watchFds; + for (const auto& file : files) { + auto watchFd = inotify_add_watch(inotifyFd, file.c_str(), IN_MODIFY | IN_MOVED_TO); + if (watchFd < 0) { + spdlog::error("Failed to add watch for file: {}", file); + } else { + spdlog::debug("Added watch for file: {}", file); + } + watchFds.push_back(watchFd); + } + + auto pollFd = pollfd{inotifyFd, POLLIN, 0}; + + while (true) { + if (watch(inotifyFd, &pollFd)) { + break; + } + } + + for (const auto& watchFd : watchFds) { + inotify_rm_watch(inotifyFd, watchFd); + } + + close(inotifyFd); +} + +bool waybar::CssReloadHelper::watch(int inotifyFd, pollfd* pollFd) { + auto pollResult = poll(pollFd, 1, 10); + if (pollResult < 0) { + spdlog::error("Failed to poll inotify: {}", strerror(errno)); + return true; + } + + if (pollResult == 0) { + // check if we should stop + if (!m_running) { + return true; + } + + std::unique_lock lock(m_mutex); + // a condition variable is used to allow the thread to be stopped immediately while still not + // spamming poll + m_cv.wait_for(lock, std::chrono::milliseconds(250), [this] { return !m_running; }); + + // timeout + return false; + } + + if (static_cast(pollFd->revents & POLLIN)) { + if (handleInotifyEvents(inotifyFd)) { + // after the callback is fired we need to re-parse the imports and setup the watches + // again in case the import list has changed + return true; + } + } + + return false; +} + +bool waybar::CssReloadHelper::handleInotifyEvents(int inotify_fd) { + // inotify event + auto buffer = std::array{}; + auto readResult = read(inotify_fd, buffer.data(), buffer.size()); + if (readResult < 0) { + spdlog::error("Failed to read inotify event: {}", strerror(errno)); + return false; + } + + auto offset = 0; + auto shouldFireCallback = false; + + // read all events on the fd + while (offset < readResult) { + auto* event = reinterpret_cast(buffer.data() + offset); + offset += sizeof(inotify_event) + event->len; + shouldFireCallback = true; + } + + // we only need to fire the callback once + if (shouldFireCallback) { + m_callback(); + return true; + } + + return false; +} diff --git a/test/css_reload_helper.cpp b/test/css_reload_helper.cpp new file mode 100644 index 00000000..3ee1fb6e --- /dev/null +++ b/test/css_reload_helper.cpp @@ -0,0 +1,188 @@ +#include "util/css_reload_helper.hpp" +#include +#include + +#if __has_include() +#include +#include +#else +#include +#endif + + +class CssReloadHelperTest : public waybar::CssReloadHelper +{ +public: + CssReloadHelperTest() + : CssReloadHelper("/tmp/waybar_test.css", [this]() {callback();}) + { + } + + void callback() + { + m_callbackCounter++; + } + +protected: + + std::string getFileContents(const std::string& filename) override + { + return m_fileContents[filename]; + } + + std::string findPath(const std::string& filename) override + { + return filename; + } + + void setFileContents(const std::string& filename, const std::string& contents) + { + m_fileContents[filename] = contents; + } + + int getCallbackCounter() const + { + return m_callbackCounter; + } + +private: + int m_callbackCounter{}; + std::map m_fileContents; +}; + +TEST_CASE_METHOD(CssReloadHelperTest, "parse_imports", "[util][css_reload_helper]") +{ + SECTION("no imports") + { + setFileContents("/tmp/waybar_test.css", "body { color: red; }"); + auto files = parseImports("/tmp/waybar_test.css"); + REQUIRE(files.size() == 1); + CHECK(files[0] == "/tmp/waybar_test.css"); + } + + SECTION("single import") + { + setFileContents("/tmp/waybar_test.css", "@import 'test.css';"); + setFileContents("test.css", "body { color: red; }"); + auto files = parseImports("/tmp/waybar_test.css"); + std::sort(files.begin(), files.end()); + REQUIRE(files.size() == 2); + CHECK(files[0] == "/tmp/waybar_test.css"); + CHECK(files[1] == "test.css"); + } + + SECTION("multiple imports") + { + setFileContents("/tmp/waybar_test.css", "@import 'test.css'; @import 'test2.css';"); + setFileContents("test.css", "body { color: red; }"); + setFileContents("test2.css", "body { color: blue; }"); + auto files = parseImports("/tmp/waybar_test.css"); + std::sort(files.begin(), files.end()); + REQUIRE(files.size() == 3); + CHECK(files[0] == "/tmp/waybar_test.css"); + CHECK(files[1] == "test.css"); + CHECK(files[2] == "test2.css"); + } + + SECTION("nested imports") + { + setFileContents("/tmp/waybar_test.css", "@import 'test.css';"); + setFileContents("test.css", "@import 'test2.css';"); + setFileContents("test2.css", "body { color: red; }"); + auto files = parseImports("/tmp/waybar_test.css"); + std::sort(files.begin(), files.end()); + REQUIRE(files.size() == 3); + CHECK(files[0] == "/tmp/waybar_test.css"); + CHECK(files[1] == "test.css"); + CHECK(files[2] == "test2.css"); + } + + SECTION("circular imports") + { + setFileContents("/tmp/waybar_test.css", "@import 'test.css';"); + setFileContents("test.css", "@import 'test2.css';"); + setFileContents("test2.css", "@import 'test.css';"); + auto files = parseImports("/tmp/waybar_test.css"); + std::sort(files.begin(), files.end()); + REQUIRE(files.size() == 3); + CHECK(files[0] == "/tmp/waybar_test.css"); + CHECK(files[1] == "test.css"); + CHECK(files[2] == "test2.css"); + } + + SECTION("empty") + { + setFileContents("/tmp/waybar_test.css", ""); + auto files = parseImports("/tmp/waybar_test.css"); + REQUIRE(files.size() == 1); + CHECK(files[0] == "/tmp/waybar_test.css"); + } + + SECTION("empty name") + { + auto files = parseImports(""); + REQUIRE(files.empty()); + } +} + +TEST_CASE("file_watcher", "[util][css_reload_helper]") +{ + SECTION("file does not exist") + { + std::atomic count; + std::string f1 = std::tmpnam(nullptr); + waybar::CssReloadHelper helper(f1, [&count](){++count;}); + helper.monitorChanges(); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + CHECK(count == 0); + helper.stop(); + std::remove(f1.c_str()); + } + + SECTION("file exists") + { + std::atomic count; + std::string f1 = std::tmpnam(nullptr); + std::ofstream(f1) << "body { color: red; }"; + waybar::CssReloadHelper helper(f1, [&count](){++count;}); + helper.monitorChanges(); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + CHECK(count == 0); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + std::ofstream(f1) << "body { color: blue; }"; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + CHECK(count == 1); + helper.stop(); + std::remove(f1.c_str()); + } + + SECTION("multiple files") + { + std::atomic count; + std::string f1 = std::tmpnam(nullptr); + std::string f2 = std::tmpnam(nullptr); + std::ofstream(f1) << ("@import '" + f2 + " ';"); + std::ofstream(f2) << "body { color: red; }"; + waybar::CssReloadHelper helper(f1, [&count](){++count;}); + helper.monitorChanges(); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + CHECK(count == 0); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + std::ofstream(f2) << "body { color: blue; }"; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + CHECK(count == 1); + helper.stop(); + std::remove(f1.c_str()); + std::remove(f2.c_str()); + } +} diff --git a/test/meson.build b/test/meson.build index 02cbb2a4..358d6c96 100644 --- a/test/meson.build +++ b/test/meson.build @@ -10,7 +10,9 @@ test_src = files( 'main.cpp', 'SafeSignal.cpp', 'config.cpp', + 'css_reload_helper.cpp', '../src/config.cpp', + '../src/util/css_reload_helper.cpp', ) if tz_dep.found() From f7eca99496e4ad8c635db5aa2c38967994eabad0 Mon Sep 17 00:00:00 2001 From: dpayne Date: Sun, 21 Jan 2024 20:49:13 -0800 Subject: [PATCH 2/5] Using Gio::FileMonitor for watching style changes --- include/util/css_reload_helper.hpp | 24 ++--- src/util/css_reload_helper.cpp | 162 ++++++++--------------------- test/css_reload_helper.cpp | 62 ----------- 3 files changed, 58 insertions(+), 190 deletions(-) diff --git a/include/util/css_reload_helper.hpp b/include/util/css_reload_helper.hpp index 62507e42..4da64ec6 100644 --- a/include/util/css_reload_helper.hpp +++ b/include/util/css_reload_helper.hpp @@ -1,13 +1,13 @@ #pragma once -#include -#include #include -#include #include -#include #include +#include "glibmm/refptr.h" +#include "giomm/file.h" +#include "giomm/filemonitor.h" + struct pollfd; namespace waybar { @@ -15,12 +15,8 @@ class CssReloadHelper { public: CssReloadHelper(std::string cssFile, std::function callback); - ~CssReloadHelper(); - virtual void monitorChanges(); - void stop(); - protected: std::vector parseImports(const std::string& cssFile); @@ -38,12 +34,16 @@ class CssReloadHelper { virtual std::string findPath(const std::string& filename); + void handleFileChange( + Glib::RefPtr const& file, + Glib::RefPtr const& other_type, + Gio::FileMonitorEvent event_type); + private: std::string m_cssFile; + std::function m_callback; - std::atomic m_running = false; - std::thread m_thread; - std::mutex m_mutex; - std::condition_variable m_cv; + + std::vector>> m_fileMonitors; }; } // namespace waybar diff --git a/src/util/css_reload_helper.cpp b/src/util/css_reload_helper.cpp index 4038f1f3..9bbd0f93 100644 --- a/src/util/css_reload_helper.cpp +++ b/src/util/css_reload_helper.cpp @@ -8,8 +8,11 @@ #include #include #include +#include "glibmm/refptr.h" +#include "giomm/file.h" #include "config.hpp" + namespace { const std::regex IMPORT_REGEX(R"(@import\s+(?:url\()?(?:"|')([^"')]+)(?:"|')\)?;)"); } @@ -17,8 +20,6 @@ const std::regex IMPORT_REGEX(R"(@import\s+(?:url\()?(?:"|')([^"')]+)(?:"|')\)?; waybar::CssReloadHelper::CssReloadHelper(std::string cssFile, std::function callback) : m_cssFile(std::move(cssFile)), m_callback(std::move(callback)) {} -waybar::CssReloadHelper::~CssReloadHelper() { stop(); } - std::string waybar::CssReloadHelper::getFileContents(const std::string& filename) { if (filename.empty()) { return {}; @@ -34,21 +35,56 @@ std::string waybar::CssReloadHelper::getFileContents(const std::string& filename std::string waybar::CssReloadHelper::findPath(const std::string& filename) { // try path and fallback to looking relative to the config + std::string result; if (std::filesystem::exists(filename)) { - return filename; + result = filename; + } else { + result = Config::findConfigPath({filename}).value_or(""); } - return Config::findConfigPath({filename}).value_or(""); + // File monitor does not work with symlinks, so resolve them + if (std::filesystem::is_symlink(result)) { + result = std::filesystem::read_symlink(result); + } + + return result; } void waybar::CssReloadHelper::monitorChanges() { - m_thread = std::thread([this] { - m_running = true; - while (m_running) { - auto files = parseImports(m_cssFile); - watchFiles(files); + auto files = parseImports(m_cssFile); + for (const auto& file : files) { + auto gioFile = Gio::File::create_for_path(file); + if (!gioFile) { + spdlog::error("Failed to create file for path: {}", file); + continue; } - }); + + auto fileMonitor = gioFile->monitor_file(); + if (!fileMonitor) { + spdlog::error("Failed to create file monitor for path: {}", file); + continue; + } + + auto connection = fileMonitor->signal_changed().connect( + sigc::mem_fun(*this, &CssReloadHelper::handleFileChange)); + + if (!connection.connected()) { + spdlog::error("Failed to connect to file monitor for path: {}", file); + continue; + } + m_fileMonitors.emplace_back(std::move(fileMonitor)); + } +} + +void waybar::CssReloadHelper::handleFileChange(Glib::RefPtr const& file, + Glib::RefPtr const& other_type, + Gio::FileMonitorEvent event_type) { + // Multiple events are fired on file changed (attributes, write, changes done hint, etc.), only + // fire for one + if (event_type == Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { + spdlog::debug("Reloading style, file changed: {}", file->get_path()); + m_callback(); + } } std::vector waybar::CssReloadHelper::parseImports(const std::string& cssFile) { @@ -106,109 +142,3 @@ void waybar::CssReloadHelper::parseImports(const std::string& cssFile, imports[cssFile] = true; } - -void waybar::CssReloadHelper::stop() { - if (!m_running) { - return; - } - - m_running = false; - m_cv.notify_all(); - if (m_thread.joinable()) { - m_thread.join(); - } -} - -void waybar::CssReloadHelper::watchFiles(const std::vector& files) { - auto inotifyFd = inotify_init1(IN_NONBLOCK); - if (inotifyFd < 0) { - spdlog::error("Failed to initialize inotify: {}", strerror(errno)); - return; - } - - std::vector watchFds; - for (const auto& file : files) { - auto watchFd = inotify_add_watch(inotifyFd, file.c_str(), IN_MODIFY | IN_MOVED_TO); - if (watchFd < 0) { - spdlog::error("Failed to add watch for file: {}", file); - } else { - spdlog::debug("Added watch for file: {}", file); - } - watchFds.push_back(watchFd); - } - - auto pollFd = pollfd{inotifyFd, POLLIN, 0}; - - while (true) { - if (watch(inotifyFd, &pollFd)) { - break; - } - } - - for (const auto& watchFd : watchFds) { - inotify_rm_watch(inotifyFd, watchFd); - } - - close(inotifyFd); -} - -bool waybar::CssReloadHelper::watch(int inotifyFd, pollfd* pollFd) { - auto pollResult = poll(pollFd, 1, 10); - if (pollResult < 0) { - spdlog::error("Failed to poll inotify: {}", strerror(errno)); - return true; - } - - if (pollResult == 0) { - // check if we should stop - if (!m_running) { - return true; - } - - std::unique_lock lock(m_mutex); - // a condition variable is used to allow the thread to be stopped immediately while still not - // spamming poll - m_cv.wait_for(lock, std::chrono::milliseconds(250), [this] { return !m_running; }); - - // timeout - return false; - } - - if (static_cast(pollFd->revents & POLLIN)) { - if (handleInotifyEvents(inotifyFd)) { - // after the callback is fired we need to re-parse the imports and setup the watches - // again in case the import list has changed - return true; - } - } - - return false; -} - -bool waybar::CssReloadHelper::handleInotifyEvents(int inotify_fd) { - // inotify event - auto buffer = std::array{}; - auto readResult = read(inotify_fd, buffer.data(), buffer.size()); - if (readResult < 0) { - spdlog::error("Failed to read inotify event: {}", strerror(errno)); - return false; - } - - auto offset = 0; - auto shouldFireCallback = false; - - // read all events on the fd - while (offset < readResult) { - auto* event = reinterpret_cast(buffer.data() + offset); - offset += sizeof(inotify_event) + event->len; - shouldFireCallback = true; - } - - // we only need to fire the callback once - if (shouldFireCallback) { - m_callback(); - return true; - } - - return false; -} diff --git a/test/css_reload_helper.cpp b/test/css_reload_helper.cpp index 3ee1fb6e..ac7b3eb7 100644 --- a/test/css_reload_helper.cpp +++ b/test/css_reload_helper.cpp @@ -124,65 +124,3 @@ TEST_CASE_METHOD(CssReloadHelperTest, "parse_imports", "[util][css_reload_helper REQUIRE(files.empty()); } } - -TEST_CASE("file_watcher", "[util][css_reload_helper]") -{ - SECTION("file does not exist") - { - std::atomic count; - std::string f1 = std::tmpnam(nullptr); - waybar::CssReloadHelper helper(f1, [&count](){++count;}); - helper.monitorChanges(); - - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - CHECK(count == 0); - helper.stop(); - std::remove(f1.c_str()); - } - - SECTION("file exists") - { - std::atomic count; - std::string f1 = std::tmpnam(nullptr); - std::ofstream(f1) << "body { color: red; }"; - waybar::CssReloadHelper helper(f1, [&count](){++count;}); - helper.monitorChanges(); - - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - CHECK(count == 0); - - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - std::ofstream(f1) << "body { color: blue; }"; - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - CHECK(count == 1); - helper.stop(); - std::remove(f1.c_str()); - } - - SECTION("multiple files") - { - std::atomic count; - std::string f1 = std::tmpnam(nullptr); - std::string f2 = std::tmpnam(nullptr); - std::ofstream(f1) << ("@import '" + f2 + " ';"); - std::ofstream(f2) << "body { color: red; }"; - waybar::CssReloadHelper helper(f1, [&count](){++count;}); - helper.monitorChanges(); - - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - CHECK(count == 0); - - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - std::ofstream(f2) << "body { color: blue; }"; - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - CHECK(count == 1); - helper.stop(); - std::remove(f1.c_str()); - std::remove(f2.c_str()); - } -} From 20fa578b12212591299d2010f1b6f8d5c117dcde Mon Sep 17 00:00:00 2001 From: dpayne Date: Sun, 21 Jan 2024 21:02:01 -0800 Subject: [PATCH 3/5] Adding config option to enable reloading style on file change --- src/client.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index ff1be5d8..bd0ee41a 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -269,11 +269,14 @@ int waybar::Client::main(int argc, char *argv[]) { auto css_file = getStyle(style_opt, appearance); setupCss(css_file); }); - m_cssReloadHelper->monitorChanges(); + + if (config.getConfig()["reload_style_on_change"].asBool()) { + m_cssReloadHelper->monitorChanges(); + } bindInterfaces(); gtk_app->hold(); gtk_app->run(); - m_cssReloadHelper.reset(); // stop watching css file + m_cssReloadHelper.reset(); // stop watching css file bars.clear(); return 0; } From 9556b0fe893763d15dc675f017c895742e6cddbb Mon Sep 17 00:00:00 2001 From: dpayne Date: Sun, 28 Jan 2024 14:25:00 -0800 Subject: [PATCH 4/5] Adding a man page entry for the realod_style_on_change option --- man/waybar.5.scd.in | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index e076b000..17324d69 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -130,6 +130,11 @@ Also, a minimal example configuration can be found at the bottom of this man pag Each file can contain a single object with any of the bar configuration options. In case of duplicate options, the first defined value takes precedence, i.e. including file -> first included file -> etc. Nested includes are permitted, but make sure to avoid circular imports. For a multi-bar config, the include directive affects only current bar configuration object. +*reload_style_on_change* ++ + typeof: bool ++ + default: *false* ++ + Option to enable reloading the css style if a modification is detected on the style sheet file or any imported css files. + # MODULE FORMAT You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat). From 10cb4180f67a1a3d43cf00574ebcade25205047c Mon Sep 17 00:00:00 2001 From: dpayne Date: Sun, 28 Jan 2024 14:44:25 -0800 Subject: [PATCH 5/5] * Fixing clang tidy comments * Fixing missing includes * Fixing formatting --- include/util/css_reload_helper.hpp | 16 ++++---- src/util/css_reload_helper.cpp | 6 +-- test/css_reload_helper.cpp | 63 +++++++++--------------------- 3 files changed, 29 insertions(+), 56 deletions(-) diff --git a/include/util/css_reload_helper.hpp b/include/util/css_reload_helper.hpp index 4da64ec6..4826fc31 100644 --- a/include/util/css_reload_helper.hpp +++ b/include/util/css_reload_helper.hpp @@ -2,11 +2,12 @@ #include #include +#include #include -#include "glibmm/refptr.h" #include "giomm/file.h" #include "giomm/filemonitor.h" +#include "glibmm/refptr.h" struct pollfd; @@ -20,24 +21,21 @@ class CssReloadHelper { protected: std::vector parseImports(const std::string& cssFile); - void parseImports(const std::string& cssFile, - std::unordered_map& imports); - + void parseImports(const std::string& cssFile, std::unordered_map& imports); void watchFiles(const std::vector& files); bool handleInotifyEvents(int fd); - bool watch(int inotifyFd, pollfd * pollFd); + bool watch(int inotifyFd, pollfd* pollFd); virtual std::string getFileContents(const std::string& filename); virtual std::string findPath(const std::string& filename); - void handleFileChange( - Glib::RefPtr const& file, - Glib::RefPtr const& other_type, - Gio::FileMonitorEvent event_type); + void handleFileChange(Glib::RefPtr const& file, + Glib::RefPtr const& other_type, + Gio::FileMonitorEvent event_type); private: std::string m_cssFile; diff --git a/src/util/css_reload_helper.cpp b/src/util/css_reload_helper.cpp index 9bbd0f93..45fd801a 100644 --- a/src/util/css_reload_helper.cpp +++ b/src/util/css_reload_helper.cpp @@ -8,10 +8,10 @@ #include #include #include -#include "glibmm/refptr.h" -#include "giomm/file.h" #include "config.hpp" +#include "giomm/file.h" +#include "glibmm/refptr.h" namespace { const std::regex IMPORT_REGEX(R"(@import\s+(?:url\()?(?:"|')([^"')]+)(?:"|')\)?;)"); @@ -30,7 +30,7 @@ std::string waybar::CssReloadHelper::getFileContents(const std::string& filename return {}; } - return std::string((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + return {(std::istreambuf_iterator(file)), std::istreambuf_iterator()}; } std::string waybar::CssReloadHelper::findPath(const std::string& filename) { diff --git a/test/css_reload_helper.cpp b/test/css_reload_helper.cpp index ac7b3eb7..01850bc3 100644 --- a/test/css_reload_helper.cpp +++ b/test/css_reload_helper.cpp @@ -1,6 +1,6 @@ #include "util/css_reload_helper.hpp" + #include -#include #if __has_include() #include @@ -9,59 +9,39 @@ #include #endif +class CssReloadHelperTest : public waybar::CssReloadHelper { + public: + CssReloadHelperTest() : CssReloadHelper("/tmp/waybar_test.css", [this]() { callback(); }) {} -class CssReloadHelperTest : public waybar::CssReloadHelper -{ -public: - CssReloadHelperTest() - : CssReloadHelper("/tmp/waybar_test.css", [this]() {callback();}) - { - } + void callback() { m_callbackCounter++; } - void callback() - { - m_callbackCounter++; - } - -protected: - - std::string getFileContents(const std::string& filename) override - { + protected: + std::string getFileContents(const std::string& filename) override { return m_fileContents[filename]; } - std::string findPath(const std::string& filename) override - { - return filename; - } + std::string findPath(const std::string& filename) override { return filename; } - void setFileContents(const std::string& filename, const std::string& contents) - { + void setFileContents(const std::string& filename, const std::string& contents) { m_fileContents[filename] = contents; } - int getCallbackCounter() const - { - return m_callbackCounter; - } + int getCallbackCounter() const { return m_callbackCounter; } -private: + private: int m_callbackCounter{}; std::map m_fileContents; }; -TEST_CASE_METHOD(CssReloadHelperTest, "parse_imports", "[util][css_reload_helper]") -{ - SECTION("no imports") - { +TEST_CASE_METHOD(CssReloadHelperTest, "parse_imports", "[util][css_reload_helper]") { + SECTION("no imports") { setFileContents("/tmp/waybar_test.css", "body { color: red; }"); auto files = parseImports("/tmp/waybar_test.css"); REQUIRE(files.size() == 1); CHECK(files[0] == "/tmp/waybar_test.css"); } - SECTION("single import") - { + SECTION("single import") { setFileContents("/tmp/waybar_test.css", "@import 'test.css';"); setFileContents("test.css", "body { color: red; }"); auto files = parseImports("/tmp/waybar_test.css"); @@ -71,8 +51,7 @@ TEST_CASE_METHOD(CssReloadHelperTest, "parse_imports", "[util][css_reload_helper CHECK(files[1] == "test.css"); } - SECTION("multiple imports") - { + SECTION("multiple imports") { setFileContents("/tmp/waybar_test.css", "@import 'test.css'; @import 'test2.css';"); setFileContents("test.css", "body { color: red; }"); setFileContents("test2.css", "body { color: blue; }"); @@ -84,8 +63,7 @@ TEST_CASE_METHOD(CssReloadHelperTest, "parse_imports", "[util][css_reload_helper CHECK(files[2] == "test2.css"); } - SECTION("nested imports") - { + SECTION("nested imports") { setFileContents("/tmp/waybar_test.css", "@import 'test.css';"); setFileContents("test.css", "@import 'test2.css';"); setFileContents("test2.css", "body { color: red; }"); @@ -97,8 +75,7 @@ TEST_CASE_METHOD(CssReloadHelperTest, "parse_imports", "[util][css_reload_helper CHECK(files[2] == "test2.css"); } - SECTION("circular imports") - { + SECTION("circular imports") { setFileContents("/tmp/waybar_test.css", "@import 'test.css';"); setFileContents("test.css", "@import 'test2.css';"); setFileContents("test2.css", "@import 'test.css';"); @@ -110,16 +87,14 @@ TEST_CASE_METHOD(CssReloadHelperTest, "parse_imports", "[util][css_reload_helper CHECK(files[2] == "test2.css"); } - SECTION("empty") - { + SECTION("empty") { setFileContents("/tmp/waybar_test.css", ""); auto files = parseImports("/tmp/waybar_test.css"); REQUIRE(files.size() == 1); CHECK(files[0] == "/tmp/waybar_test.css"); } - SECTION("empty name") - { + SECTION("empty name") { auto files = parseImports(""); REQUIRE(files.empty()); }