diff --git a/CMakeLists.txt b/CMakeLists.txt index d51f873..f351d75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,21 @@ cmake_minimum_required(VERSION 3.24) + +option(NO_CLANG "Don't prefer clang for compilation" OFF) + +if (NOT NO_CLANG) + # Prefer clang++ for compilation if available + find_program(CLANG clang) + find_program(CLANGXX clang++) + + if (CLANG) + set(CMAKE_C_COMPILER ${CLANG}) + endif() + + if (CLANGXX) + set(CMAKE_CXX_COMPILER ${CLANGXX}) + endif() +endif() + project(cppbtbl) set(SOURCE_DIR ${PROJECT_SOURCE_DIR}/src) @@ -24,6 +41,11 @@ set_target_properties(${PROJECT_NAME} add_compile_options(-Wall -Wextra -Wshadow -Wpedantic -Wno-c++98-compat -Wfloat-conversion -Wno-unused-parameter -Wimplicit-fallthrough -Wconversion) +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + add_compile_options(-fsanitize=address,undefined) + target_link_options(${PROJECT_NAME} PRIVATE -fsanitize=address,undefined) +endif() + install( TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin diff --git a/src/config.cpp b/src/config.cpp index 3aea963..c09636a 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -12,12 +12,15 @@ #include "config.h" #include "utils.h" +#define get_config_file(config_home) config_home / "cppbtbl" / "config" + static struct option long_options[] = { - {"help", no_argument, NULL, 'h'}, - {"format", required_argument, NULL, 'f'}, - {"output", required_argument, NULL, 'o'}, - {"dont-follow", no_argument, NULL, 'e'}, - {"icons", required_argument, NULL, 'i'} + {"help", no_argument, NULL, 'h'}, + {"format", required_argument, NULL, 'f'}, + {"output", required_argument, NULL, 'o'}, + {"icons", required_argument, NULL, 'i'}, + {"devices-separator", required_argument, NULL, 's'}, + {"dont-follow", no_argument, NULL, 'e'}, }; @@ -50,7 +53,7 @@ int parse_config(ProgramOptions *opts) { auto config_home = get_config_home(); if (!config_home) return -1; - std::filesystem::path config_file = *config_home / "cppbtbl" / "config"; + std::filesystem::path config_file = get_config_file(*config_home); // std::cerr << "[DEBUG] config_file_path = " << config_file << std::endl; @@ -101,6 +104,9 @@ int parse_config(ProgramOptions *opts) { if (!opts->icons.empty()) break; opts->icons = split(optarg, ','); break; + case 's': + if (opts->devices_separator != nullptr) break; + opts->devices_separator = strdup(optarg); default: return 1; } @@ -110,18 +116,15 @@ int parse_config(ProgramOptions *opts) { std::for_each( ++_argv.begin(), _argv.end(), - [opts](const char* ptr) { - // std::cerr << (void *)ptr << ": " << ptr << std::endl; + [](const char *ptr) { std::free((void *)ptr); - //std::cerr << "opts->custom_format: " << opts->custom_format << std::endl; - } + } ); return -1; } int parse_opts(int argc, char *const argv[], ProgramOptions *opts) { - // reset getopt optind = 1; int opt; @@ -143,13 +146,15 @@ int parse_opts(int argc, char *const argv[], ProgramOptions *opts) { case 'i': opts->icons = split(optarg, ','); break; + case 's': + opts->devices_separator = std::move(optarg); default: return 1; } } if (int e = parse_config(opts); e != -1) { std::cerr << "Error occurred while parsing config." << std::endl; - return 1; + return e; } if (opts->output_format == format_custom and diff --git a/src/config.h b/src/config.h index 5740b1f..fa734f6 100644 --- a/src/config.h +++ b/src/config.h @@ -10,9 +10,12 @@ enum OutputFormat { }; struct ProgramOptions { - std::vector icons; OutputFormat output_format = format_raw_default; - char *custom_format; + // for custom format + std::vector icons; + const char *custom_format; + const char *devices_separator; + bool dont_follow; }; diff --git a/src/main.cpp b/src/main.cpp index a306433..bf2a708 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,11 @@ #include -#include -#include #include #include -#include -#include -#include +#include +#include #include +#include +#include #include #include "config.h" @@ -27,14 +26,56 @@ std::set watch_list; // runtime options struct ProgramOptions opts; -std::string get_icon(int percentage) { - int icon_idx = (percentage * opts.icons.size() / 100) + 0.5; +const std::string get_icon(int percentage) { + int icon_idx = (percentage * (opts.icons.size() - 1) / 100) + 0.5; return opts.icons[icon_idx]; } +void _output(std::optional tooltip_str, int percentage, std::string device_name, OutputFormat output_format, const char* end = nullptr) { + switch (output_format) { + case format_waybar: + if (!tooltip_str) { + throw new std::runtime_error("illegal state! tooltip_str was not passed."); + } + replace_all(*tooltip_str, "\n", "\\n"); + replace_all(*tooltip_str, "\"", "\\\""); + // lazy af solution, but should work + std::cout << "{\"percentage\":" << percentage << ",\"tooltip\":\"" << *tooltip_str << "\"}"; + break; + case format_custom: + { + // make an std::string copy of the char* + std::string output(opts.custom_format); + const std::string icon = get_icon(percentage); + // replace all variables + replace(output, "{icon}", icon); + replace(output, "{percentage}", std::to_string(percentage)); + replace(output, "{name}", device_name); + + std::cout << output; + } + break; + case format_raw_default: + case format_raw: + if (!tooltip_str) { + throw new std::runtime_error("illegal state! tooltip_str was not passed."); + } + std::cout << *tooltip_str; + break; + } + + if (end != nullptr) { + std::cout << end; + } else { + std::cout << std::endl; + } +} + void _get_battery_infos() { if (watch_list.empty()) return; + bool should_use_separator = opts.output_format == format_custom and opts.devices_separator != nullptr; + int least_percentage = 100; std::string least_device_name; std::stringstream tooltip; @@ -54,6 +95,11 @@ void _get_battery_infos() { double percentage = device_obj->getProperty("Percentage") .onInterface(UPOWER_DEVICE_IFACE) .get(); + + if (should_use_separator) { + _output(std::nullopt, percentage, device_name, opts.output_format, opts.devices_separator); + continue; + } tooltip << device_name << ": " << percentage << "%\n"; if (percentage < least_percentage) { @@ -62,34 +108,13 @@ void _get_battery_infos() { } } + if (should_use_separator) { + std::cout << std::endl; + } + std::string tooltip_str = tooltip.str(); tooltip_str.erase(tooltip_str.length() - 1); - - switch (opts.output_format) { - case format_waybar: - replace_all(tooltip_str, "\n", "\\n"); - replace_all(tooltip_str, "\"", "\\\""); - // lazy af solution, but should work - std::cout << "{\"percentage\":" << least_percentage << ",\"tooltip\":\"" << tooltip_str << "\"}" << std::endl; - break; - case format_custom: - { - // make an std::string copy of the char* - std::string output(opts.custom_format); - std::string icon = get_icon(least_percentage); - // replace all variables - replace(output, "{icon}", icon); - replace(output, "{percentage}", std::to_string(least_percentage)); - replace(output, "{name}", least_device_name); - - std::cout << output << std::endl; - } - break; - case format_raw_default: - case format_raw: - std::cout << tooltip_str << std::endl; - break; - } + _output(tooltip_str, least_percentage, least_device_name, opts.output_format); } void _device_added(sdbus::ObjectPath &path) { @@ -111,8 +136,8 @@ void _device_added(sdbus::ObjectPath &path) { void _device_removed(sdbus::ObjectPath &path) { watch_list.erase(path); - - if (watch_list.empty()) { + + if (watch_list.empty()) { std::cout << std::endl; } } @@ -165,8 +190,7 @@ int main(int argc, char *const *argv) { auto connection = &proxy->getConnection(); // `poll` event loop + timer for polling connected devices' battery - while (true) - { + while (true) { bool processed = connection->processPendingRequest(); if (processed) continue; // Process next one diff --git a/src/utils.cpp b/src/utils.cpp index adb9e8c..e53de09 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -41,18 +41,21 @@ void replace_all(std::string &s, const std::string &search, const std::string &r void help(char *name) { std::cout << "Usage: " << name << " -f [format] [-o [format]] [-e]\n" - << "-f/--format [format] valid options: waybar, custom, raw (default: raw)\n" - << "-o/--output [format] the custom format to be used if -f is 'custom',\n" - << " this must be a string, and you can include use the following variables:\n" - << " {icon}, {percentage}, {name}.\n" - << " please note that the 'icon' variable will be taken from the predefined\n" - << " list of fontawesome icons.\n" - << "-i/--icons [list] comma-separated list of icons, one of which will be used\n" - << " in proportion to the device's percentage as the {icon}\n" - << " parameter in '--output'\n" - << " example: ',,,,'\n" - << "-h/--help show this help screen\n" - << "-e/--dont-follow output info and exit" << std::endl; + << "-f/--format [format] valid options: waybar, custom, raw (default: raw)\n" + << "-o/--output [format] the custom format to be used if -f is 'custom',\n" + << " this must be a string, and you can include use these\n" + << " variables: {icon}, {percentage}, {name}.\n" + << "-i/--icons [list] comma-separated list of icons, one of which will be used\n" + << " in proportion to the device's percentage as the {icon}\n" + << " parameter in '--output'\n" + << " example: --icons='1,2,3,4,5'\n" + << " where 1,2,3,4,5 are the icons that indicate the battery\n" + << " level, in ascending order.\n" + << "-s/--device-separator [sep] by setting this, the scriptwill separate devices" + << " with the given string; the --output parameter is repeated" + << " for each connected device and devices are ordered by charge." + << "-h/--help show this help screen\n" + << "-e/--dont-follow output info and exit" << std::endl; } void incorrect_format_usage(char *name) {