mirror of
https://github.com/rad4day/Waybar.git
synced 2025-07-13 22:52:30 +02:00
Merge pull request #2852 from dpayne/add_css_reload
Adding css reloader
This commit is contained in:
@ -262,15 +262,21 @@ int waybar::Client::main(int argc, char *argv[]) {
|
||||
if (!portal) {
|
||||
portal = std::make_unique<waybar::Portal>();
|
||||
}
|
||||
auto css_file = getStyle(style_opt);
|
||||
setupCss(css_file);
|
||||
m_cssFile = getStyle(style_opt);
|
||||
setupCss(m_cssFile);
|
||||
m_cssReloadHelper = std::make_unique<CssReloadHelper>(m_cssFile, [&]() { setupCss(m_cssFile); });
|
||||
portal->signal_appearance_changed().connect([&](waybar::Appearance appearance) {
|
||||
auto css_file = getStyle(style_opt, appearance);
|
||||
setupCss(css_file);
|
||||
});
|
||||
|
||||
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
|
||||
bars.clear();
|
||||
return 0;
|
||||
}
|
||||
|
144
src/util/css_reload_helper.cpp
Normal file
144
src/util/css_reload_helper.cpp
Normal file
@ -0,0 +1,144 @@
|
||||
#include "util/css_reload_helper.hpp"
|
||||
|
||||
#include <poll.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sys/inotify.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "giomm/file.h"
|
||||
#include "glibmm/refptr.h"
|
||||
|
||||
namespace {
|
||||
const std::regex IMPORT_REGEX(R"(@import\s+(?:url\()?(?:"|')([^"')]+)(?:"|')\)?;)");
|
||||
}
|
||||
|
||||
waybar::CssReloadHelper::CssReloadHelper(std::string cssFile, std::function<void()> callback)
|
||||
: m_cssFile(std::move(cssFile)), m_callback(std::move(callback)) {}
|
||||
|
||||
std::string waybar::CssReloadHelper::getFileContents(const std::string& filename) {
|
||||
if (filename.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::ifstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {(std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()};
|
||||
}
|
||||
|
||||
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)) {
|
||||
result = filename;
|
||||
} else {
|
||||
result = 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() {
|
||||
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<Gio::File> const& file,
|
||||
Glib::RefPtr<Gio::File> 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<std::string> waybar::CssReloadHelper::parseImports(const std::string& cssFile) {
|
||||
std::unordered_map<std::string, bool> 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<std::string> 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<std::string, bool>& 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;
|
||||
}
|
Reference in New Issue
Block a user