From b6688f4eaeb8a5c4ac60c051d0cfa2fbdd30f46e Mon Sep 17 00:00:00 2001 From: clotodex Date: Tue, 3 Jan 2017 22:32:17 +0100 Subject: [PATCH 1/3] reorganized in files, handled event, print in dzen format --- src/defines.h | 3 + src/global.h | 36 +++++++ src/log.cpp | 63 ++++++++++++ src/log.h | 5 + src/main.cpp | 273 ++++++++++++++++++++++++++++++++------------------ src/util.cpp | 32 ++++++ src/util.h | 9 ++ 7 files changed, 325 insertions(+), 96 deletions(-) create mode 100644 src/defines.h create mode 100644 src/global.h create mode 100644 src/log.cpp create mode 100644 src/log.h create mode 100644 src/util.cpp create mode 100644 src/util.h diff --git a/src/defines.h b/src/defines.h new file mode 100644 index 0000000..ead6b87 --- /dev/null +++ b/src/defines.h @@ -0,0 +1,3 @@ +#pragma once + +#define LOG_FILE_NAME "winterpanel.log" diff --git a/src/global.h b/src/global.h new file mode 100644 index 0000000..b2b96e3 --- /dev/null +++ b/src/global.h @@ -0,0 +1,36 @@ +#pragma once + +#include "defines.h" + +/* for printf */ +#include +#include +/* for the return value of main */ +#include + +/* to open/close filedescriptors */ +#include +#ifdef LOG_FILE_NAME + #include +#endif + +/* reading command input */ +#include +#include +#include + +/* signal handling */ +#include + +/* for str2int*/ +#include + +/* data management */ +#include + +/* regex */ +#include + +/* INTERNAL */ +#include "log.h" +#include "util.h" diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 0000000..857472e --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,63 @@ +#include "global.h" + +void log(const char* message, ...) +{ +#ifdef LOG_FILE_NAME + int fd = open(LOG_FILE_NAME, (O_WRONLY | O_CREAT | O_APPEND), 0655); +#else + int fd = STDOUT_FILENO; +#endif + + dprintf(fd, "[LOG] "); + + va_list argptr; + va_start(argptr, message); + + vdprintf(fd, message, argptr); + + va_end(argptr); + +#ifdef LOG_FILE_NAME + close(fd); +#endif +} +void debug(const char* message, ...) +{ +#ifdef LOG_FILE_NAME + int fd = open(LOG_FILE_NAME, (O_WRONLY | O_CREAT | O_APPEND), 0655); +#else + int fd = STDOUT_FILENO; +#endif + + dprintf(fd, "[DEBUG] "); + + va_list argptr; + va_start(argptr, message); + + vdprintf(fd, message, argptr); + + va_end(argptr); +#ifdef LOG_FILE_NAME + close(fd); +#endif +} +void error(const char* message, ...) +{ +#ifdef LOG_FILE_NAME + int fd = open(LOG_FILE_NAME, (O_WRONLY | O_CREAT | O_APPEND), 0655); +#else + int fd = STDERR_FILENO; +#endif + + dprintf(fd, "[ERROR] "); + + va_list argptr; + va_start(argptr, message); + + vdprintf(fd, message, argptr); + + va_end(argptr); +#ifdef LOG_FILE_NAME + close(fd); +#endif +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..ca40692 --- /dev/null +++ b/src/log.h @@ -0,0 +1,5 @@ +#pragma once + +void log(const char* message, ...); +void debug(const char* message, ...); +void error(const char* message, ...); diff --git a/src/main.cpp b/src/main.cpp index 74546ed..63eb179 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,94 +1,11 @@ -#define LOG_FILE_NAME "winterpanel.log" - -/* for printf */ -#include -#include -/* for the return value of main */ -#include - -/* to open/close filedescriptors */ -#include -#ifdef LOG_FILE_NAME - #include -#endif - -/* reading command input */ -#include -#include -#include - -/* signal handling */ -#include +#include "global.h" bool quit_signal = false; -void log(const char* message, ...) -{ -#ifdef LOG_FILE_NAME - int fd = open(LOG_FILE_NAME, (O_WRONLY | O_CREAT | O_APPEND), 0655); -#else - int fd = STDOUT_FILENO; -#endif - - dprintf(fd, "[LOG] "); - - va_list argptr; - va_start(argptr, message); - - vdprintf(fd, message, argptr); - - va_end(argptr); - -#ifdef LOG_FILE_NAME - close(fd); -#endif -} -void debug(const char* message, ...) -{ -#ifdef LOG_FILE_NAME - int fd = open(LOG_FILE_NAME, (O_WRONLY | O_CREAT | O_APPEND), 0655); -#else - int fd = STDOUT_FILENO; -#endif - - dprintf(fd, "[DEBUG] "); - - va_list argptr; - va_start(argptr, message); - - vdprintf(fd, message, argptr); - - va_end(argptr); -#ifdef LOG_FILE_NAME - close(fd); -#endif -} -void error(const char* message, ...) -{ -#ifdef LOG_FILE_NAME - int fd = open(LOG_FILE_NAME, (O_WRONLY | O_CREAT | O_APPEND), 0655); -#else - int fd = STDERR_FILENO; -#endif - - dprintf(fd, "[ERROR] "); - - va_list argptr; - va_start(argptr, message); - - vdprintf(fd, message, argptr); - - va_end(argptr); -#ifdef LOG_FILE_NAME - close(fd); -#endif -} - - void cleanup() { //TODO is destructor of pstream enaugh? - should it be called here? - //cleanup open streams, filedescriptors + /* cleanup open streams, filedescriptors */ } void shutdown(const char* message, int code) @@ -104,6 +21,87 @@ void shutdown_signal(int s) quit_signal = true; } +struct panel_config +{ + std::string left; + std::string center; + std::string right; + + int panel_height; + int panel_width; //TODO should be set by arguments to this program or even better retreived via xlib + + const char* font; +}; + +std::map messages; +std::map lengths; +std::vector events_in_use; +int static_width_left = 0; +int static_width_center = 0; +int static_width_right = 0; +panel_config cfg = +{ + //TODO include const char* separator = "^bg()^fg(#00ffff)|^bg()^fg()" + "{tags} | {date}", + "{title}", + "{battery} | {time}, {date}", + 20, + 1920, + "-*-fixed-medium-*-*-*-12-*-*-*-*-*-*-*" +}; + +void print_dzen_update() +{ + //TODO if length does not exist -> calculate it in future + + std::string left = cfg.left; + std::string center = cfg.center; + std::string right = cfg.right; + + int totalwidth_left = static_width_left; + int totalwidth_center = static_width_center; + int totalwidth_right = static_width_right; + + int count = 0; + for (const auto& event : events_in_use) + { + /* prepare string to be replaced to form '{eventname}' */ + std::string from = event; + from.append("}"); + from.insert(0, 1, '{'); + + /* replace in left center and right (array access always correct due to default values)*/ + replaceAll(count, left, from, messages[event]); + totalwidth_left += (count * lengths[event]); + count = 0; + replaceAll(count, center, from, messages[event]); + totalwidth_center += (count * lengths[event]); + count = 0; + replaceAll(count, right, from, messages[event]); + totalwidth_right += (count * lengths[event]); + count = 0; + } + + int padding_left = cfg.panel_width - (cfg.panel_width / 2) - (totalwidth_center / 2) - totalwidth_left; + int padding_right = cfg.panel_width - totalwidth_left - padding_left - totalwidth_center - totalwidth_right; + + /* commit to dzen */ + printf("%s^pa(%d)%s^pa(%d)%s\n", left.data(), padding_left, center.data(), padding_right, right.data()); +} + +void handle_event(std::string& component, std::string& message, int& length) +{ + printf("Got event: {%s, %s, %d}\n", component.data(), message.data(), length); + + //TODO update message and lenght of component + + bool update = (messages[component] != message) || (lengths[component] != length); + messages[component] = message; + lengths[component] = length; + + if (update) print_dzen_update(); +} + void handle_line(const std::string& line) { if (quit_signal) @@ -115,7 +113,7 @@ void handle_line(const std::string& line) if (!std::getline(ss, item, '\t')) { - error("command has no first element"); + error("command has no first element\n"); return; } @@ -131,6 +129,34 @@ void handle_line(const std::string& line) if (item == "event") { //TODO + /* [wp] event component message length (confirm) */ + item.clear(); + std::string component; + if (!std::getline(ss, component, '\t')) + { + error("protocol error: '[wp] event' expected a components name as next argument\n"); + return; + } + std::string message; + if (!std::getline(ss, message, '\t')) + { + error("protocol error: '[wp] event component' expected a message as next argument\n"); + return; + } + std::string length_str; + if (!std::getline(ss, length_str, '\t')) + { + error("protocol error: '[wp] event component message' expected message_length as next argument\n"); + return; + } + int length; + if ((str2int(length, length_str.data())) != SUCCESS) + { + error("protocol error: 'message_length' should be an int value, but was %s\n", length_str.data()); + return; + } + handle_event(component, message, length); + } else if (item == "reload") { @@ -140,7 +166,7 @@ void handle_line(const std::string& line) { //TODO handle request (types: sheduling, ...?) //TODO fill timing array & stop and start sheduler - //sheduler is background task? stop and start for new timings + //TODO sheduler is background task? stop and start for new timings } else if (item == "ping") { @@ -148,7 +174,7 @@ void handle_line(const std::string& line) } else if (item == "pong" || item == "confirm" || item == "action" || item == "quit") { - //sent by the panel so just ignore them - maybe redefine protocol to have component channel and panel channel and filter it out by regex + /* sent by the panel so just ignore them - maybe redefine protocol to have component channel and panel channel and filter it out by regex */ } else error("Unknown protocol action: %s\n", item.data()); @@ -156,28 +182,65 @@ void handle_line(const std::string& line) else if (item == "reload" || item == "quit_panel") shutdown("quit requested", EXIT_SUCCESS); else - //ignore - command not found or not intended for handling - should not happen if regex is correct + /* command not found or not intended for handling - should not happen if regex is correct */ error("'%s' should have been ignored by the regex\n", item.data()); } void herbstclient_listen() { - // run a process and create a streambuf that reads its stdout and stderr - // listen for herbstluft events with a regex filtering out unnecessary stuff + /* run a process and create a streambuf that reads its stdout and stderr */ + /* listen for herbstluft events with a regex filtering out unnecessary stuff */ redi::ipstream proc("herbstclient --idle \"(\\[wp\\].*|reload|quit_panel)\"", redi::pstreams::pstdout | redi::pstreams::pstderr); std::string line; - // read child's stdout + /* read child's stdout */ while (std::getline(proc.out(), line)) handle_line(line); - // read child's stderr + /* read child's stderr */ while (std::getline(proc.err(), line)) error("[HERBSTCLIENT] %s\n", line.data()); } +void find_events_in_use() +{ + std::string all = cfg.left + cfg.center + cfg.right; + debug("all %s\n", all.data()); + std::regex r("([^{}]*)}"); + std::smatch m; + while (std::regex_search(all, m, r)) + { + /* group 1 is the correct match */ + events_in_use.push_back(m[1].str()); + /* remove match from string */ + all = m.suffix().str(); + } +} + +int calculate_static_width(std::string s) +{ + /* remove dzen instructions from panel_string */ + std::regex r("\^[^(]*([^)]*)"); + std::regex_replace(s, r, ""); + /* calculate width */ + //TODO calc correct width + return 10; +} + +void setup_default_entries() +{ + std::string message("loading..."); + int length = 60; //TODO calculate freshly + for (const std::string& event : events_in_use) + { + messages[event] = message; + lengths[event] = length; + } +} + int main() { + /* STARTUP */ log("Starting up Winterpanel...\n"); - log("Setting signal-handler... "); + log("Setting signal-handler...\n"); struct sigaction sa; sa.sa_handler = shutdown_signal; sigemptyset(&sa.sa_mask); @@ -186,9 +249,27 @@ int main() shutdown("Could not install signal-handler", EXIT_FAILURE); log("DONE\n"); - log("Startup successful!"); + //TODO maybe have a config generator, that generates a config with given variables (e.g. generate a json with bash) + log("Loading config...\n"); + log("DONE\n"); + + log("Preparing default values...\n"); + find_events_in_use(); + setup_default_entries(); + /* calculate static width */ + static_width_left = calculate_static_width(cfg.left); + static_width_center = calculate_static_width(cfg.center); + static_width_right = calculate_static_width(cfg.right); + log("DONE\n"); + + log("Printing default dzen...\n"); + print_dzen_update(); + log("DONE\n"); + + log("Startup successful!\n"); - log("Listening for herbstluft events"); + /* MAIN LOOP */ + log("Listening for herbstluft events\n"); herbstclient_listen(); return EXIT_SUCCESS; } diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..7524e8c --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,32 @@ +#include "global.h" + +/* from http://stackoverflow.com/questions/194465/how-to-parse-a-string-to-an-int-in-c#6154614 */ +STR2INT_ERROR str2int(int& i, char const* s, int base) +{ + char* end; + long l; + errno = 0; + l = strtol(s, &end, base); + if ((errno == ERANGE && l == LONG_MAX) || l > INT_MAX) + return OVERFLOW; + if ((errno == ERANGE && l == LONG_MIN) || l < INT_MIN) + return UNDERFLOW; + if (*s == '\0' || *end != '\0') + return INCONVERTIBLE; + i = l; + return SUCCESS; +} + +/* altered from http://stackoverflow.com/a/3418285/5864378 */ +void replaceAll(int& count, std::string& str, const std::string& from, const std::string& to) +{ + if (from.empty()) + return; + size_t start_pos = 0; + while ((start_pos = str.find(from, start_pos)) != std::string::npos) + { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); /* In case 'to' contains 'from', like replacing 'x' with 'yx' */ + ++count; + } +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..0621429 --- /dev/null +++ b/src/util.h @@ -0,0 +1,9 @@ +#pragma once + +/* from http://stackoverflow.com/questions/194465/how-to-parse-a-string-to-an-int-in-c#6154614 */ +enum STR2INT_ERROR { SUCCESS, OVERFLOW, UNDERFLOW, INCONVERTIBLE }; + +STR2INT_ERROR str2int(int& i, char const* s, int base = 0); + +/* altered from http://stackoverflow.com/a/3418285/5864378 */ +void replaceAll(int& count, std::string& str, const std::string& from, const std::string& to); -- GitLab From 2455e148f2324a16a878f9c2adcce584ee21c64b Mon Sep 17 00:00:00 2001 From: clotodex Date: Thu, 5 Jan 2017 12:06:25 +0100 Subject: [PATCH 2/3] added changelog entry - event handling --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9873ff4..0dd2305 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Changelog - Logger with log/debug/error - Reading special events from herbstclient -- handling each event in a protocol outline +- handling each event by reproducing dzen output +- supoorting left, center, right with calculated paddings - mini signal handler calling cleanup method [Unreleased]: https://gitlab.com/Clotodex/WinterPanel/compare/v.0.0.0...HEAD -- GitLab From 65ecee353b8756b1cd6f7124f86f86c75cfc5884 Mon Sep 17 00:00:00 2001 From: clotodex Date: Mon, 9 Jan 2017 22:07:05 +0100 Subject: [PATCH 3/3] fixed regex, fixed printf not flushing --- src/main.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 63eb179..cdcf822 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -86,12 +86,13 @@ void print_dzen_update() int padding_right = cfg.panel_width - totalwidth_left - padding_left - totalwidth_center - totalwidth_right; /* commit to dzen */ - printf("%s^pa(%d)%s^pa(%d)%s\n", left.data(), padding_left, center.data(), padding_right, right.data()); + /* printf doesnt flush in piping mode */ + dprintf(STDOUT_FILENO, "%s^pa(%d)%s^pa(%d)%s\n", left.data(), padding_left, center.data(), padding_right, right.data()); } void handle_event(std::string& component, std::string& message, int& length) { - printf("Got event: {%s, %s, %d}\n", component.data(), message.data(), length); + debug("Got event: {%s, %s, %d}\n", component.data(), message.data(), length); //TODO update message and lenght of component @@ -204,7 +205,7 @@ void find_events_in_use() { std::string all = cfg.left + cfg.center + cfg.right; debug("all %s\n", all.data()); - std::regex r("([^{}]*)}"); + std::regex r("([^\\{\\}]*)\\}"); std::smatch m; while (std::regex_search(all, m, r)) { @@ -218,7 +219,7 @@ void find_events_in_use() int calculate_static_width(std::string s) { /* remove dzen instructions from panel_string */ - std::regex r("\^[^(]*([^)]*)"); + std::regex r("\\^[^(]*([^)]*)"); std::regex_replace(s, r, ""); /* calculate width */ //TODO calc correct width -- GitLab