Skip to content

Commit

Permalink
0.2.0: added config + customization options
Browse files Browse the repository at this point in the history
  • Loading branch information
Pato05 committed Dec 21, 2022
1 parent 974b91d commit a5ae61a
Show file tree
Hide file tree
Showing 7 changed files with 361 additions and 117 deletions.
22 changes: 20 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
cmake_minimum_required(VERSION 3.24)
project(cppbtbl)

set(SOURCE_DIR ${PROJECT_SOURCE_DIR}/src)

set(CMAKE_COLOR_DIAGNOSTICS ON)

find_package(sdbus-c++ REQUIRED)

add_executable(cppbtbl cppbtbl.cpp)
include_directories(${SOURCE_DIR})

file(GLOB COMPILE_FILES ${SOURCE_DIR}/*.cpp)
add_executable(${PROJECT_NAME} ${COMPILE_FILES})

target_link_libraries(cppbtbl PRIVATE SDBusCpp::sdbus-c++)

set_target_properties(${PROJECT_NAME}
PROPERTIES
LINKER_LANGUAGE CXX
CXX_STANDARD 20
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS OFF
)

add_compile_options(-Wall -Wextra -Wshadow -Wpedantic -Wno-c++98-compat -Wfloat-conversion -Wno-unused-parameter -Wimplicit-fallthrough -Wconversion)

install(
TARGETS cppbtbl
TARGETS ${PROJECT_NAME}
DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
)
75 changes: 42 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,30 @@ cppbtbl depends on sdbus-c++ which itself depends on libsystemd. Regardless of t

Note that if you are using systemd, you won't have any issues using cppbtbl.

## Configuration

`cppbtbl` supports a variety of flags, which can be seen by doing `cppbtbl --help`.

Each of these flags (in its long form) can be written inside a configuration file (with the format `name=value`) that is found inside either
`$XDG_CONFIG_HOME/cppbtbl/config` (if defined) or `$HOME/.config/cppbtbl/config`.

An example of this config file can be as follows:

```
format=custom
output={icon} ({percentage}%)
icons=1,2,3,4,5
```

You can also specify the `dont-follow` option here (if needed) by just typing it with no following characters.

```
format=custom
output={icon} ({percentage}%)
icons=1,2,3,4,5
dont-follow
```

## Waybar

```json
Expand All @@ -49,49 +73,34 @@ Note that if you are using systemd, you won't have any issues using cppbtbl.

Change format and icons accordingly, and the module should work!

## Other status bars
## Polybar

Two extra formats are offered to make cppbtbl usable in this case too. Those formats are `icononly` and `icon+devicename`. Please note that the default icons provided are part of the FontAwesome font, and (as of right now) the only way of changing them is by editing the source code.
```
[module/bt-battery]
type = custom/script
A better way to handle this, would be to make a script that reads `cppbtbl`'s output and re-writes it in whatever way is best for you. For example:
exec = cppbtbl
```bash
#!/usr/bin/bash
DEVICES=()
PERCENTAGES=()
timeout=
while true; do
eval "read $timeout line"
if [ "$?" -ne "0" ]; then
[ -z "$timeout" ] && break
timeout=
# timeout, flush devices info
...
continue
fi
if [ -z "$line" ]; then
timeout=
# clear ARRAYs, all devices have been disconnected
DEVICES=(); PERCENTAGES=()
echo ''
continue
fi

IFS=":"; read -a split <<< "${line//: /:}"; unset IFS
DEVICES[${#DEVICES}]="${split[0]}"
PERCENTAGES[${#PERCENTAGES}]="${split[1]}"
# time out read comamnd after 1 second (we supposed that if no other data is available within one second, we need to flush)
timeout="-t 1"
done < <(cppbtbl -f raw)
; This is necessary, because cppbtbl will continue running
; so let's make sure polybar knows about it
tail = true
```

Please note that this is just an example, and it could likely be executed better.
Now, you need to define your custom options, see [#configuration](#configuration).
Please **make sure** to use `--format=custom`.

## Other status bars

In other status bars, just like we saw in Polybar, you can use `--format custom` and defining your own `--output`-format and your `--icons`-set

Or a better way would be to just modify the source code to your likings.
For example `cppbtbl --format custom --output "{icon} ({percentage}%)" --icons 'icon1,icon2,icon3'`.

Keep in mind that by default the `cppbtbl` process will always be running and listening for connection/disconnection events, therefore your bar must be
able to handle that. In case you want to use a "polling" approach, you can use the `--dont-follow` option, which will close the program once it
returned the desired output.

Please note that the "polling" approach won't be closely as efficient as the "listening" one, because too high of an interval will cause massive CPU usage,
too low and you'll get a delay between your device being connected and it being shown in your bar.


### A thank-you goes to [@Justasic](https://github.com/Justasic) for helping me a lot in making this.
162 changes: 162 additions & 0 deletions src/config.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#include <iostream>
#include <getopt.h>
#include <vector>
#include <string>
#include <optional>
#include <filesystem>
#include <fstream>
#include <string.h>
#include <cstdlib>
#include <algorithm>

#include "config.h"
#include "utils.h"

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'}
};


std::optional<std::filesystem::path> get_config_home() {
if (char *home = std::getenv("XDG_CONFIG_HOME"); home != nullptr)
return home;

if (char *home = std::getenv("HOME"); home != nullptr)
return std::filesystem::path(home) / ".config";


std::cerr << "Unable to get config home. Ignoring config." << std::endl;
return std::nullopt;
}

int _parse_format(ProgramOptions *opts) {
auto output_format = optarg_to_format();
if (!output_format) {
std::cerr << "Invalid format!\n"
<< "Valid values are: waybar, custom, raw." << std::endl;

return 1;
}

opts->output_format = *output_format;
return -1;
}

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::cerr << "[DEBUG] config_file_path = " << config_file << std::endl;

if (!std::filesystem::exists(config_file))
return -1;

std::ifstream file(config_file);
std::string line;

// first arg is program name, we don't need it
std::vector<const char*> _argv = { "cppbtbl" };
while (std::getline(file, line)) {
line.insert(0, "--");

const char *copy = strdup(line.c_str());
// std::cerr << static_cast<const void *>(line.c_str()) << ": " << line << std::endl;
_argv.push_back(std::move(copy));
}

// std::cerr << "[DEBUG] _argv.size() = " << _argv.size() << std::endl;

// reset getopt
optind = 1;
int opt;
while ((opt = getopt_long_only(
_argv.size(),
const_cast<char *const *>(_argv.data()),
"ef:o:i:",
long_options,
nullptr
)) != -1) {
//std::cerr << "[DEBUG] getopt_long_only opt=" << (char)opt << ", optarg=" << optarg << std::endl;
switch(opt) {
case 'f':
if (opts->output_format != format_raw_default) break;
if (int e = _parse_format(opts); e != -1)
return e;
break;
case 'o':
if (opts->custom_format != nullptr) break;
// i need a copy here, because the pointer gets free'd later on
opts->custom_format = strdup(optarg);
break;
case 'e':
opts->dont_follow = true;
break;
case 'i':
if (!opts->icons.empty()) break;
opts->icons = split(optarg, ',');
break;
default:
return 1;
}
}

// clear all duplicated strings
std::for_each(
++_argv.begin(),
_argv.end(),
[opts](const char* ptr) {
// std::cerr << (void *)ptr << ": " << ptr << std::endl;
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;
while ((opt = getopt_long(argc, argv, "hef:o:i:", long_options, nullptr)) != -1) {
switch (opt) {
case 'h':
help(argv[0]);
return 0;
case 'f':
if (int e = _parse_format(opts); e != -1)
return e;
break;
case 'o':
opts->custom_format = std::move(optarg);
break;
case 'e':
opts->dont_follow = true;
break;
case 'i':
opts->icons = split(optarg, ',');
break;
default:
return 1;
}
}
if (int e = parse_config(opts); e != -1) {
std::cerr << "Error occurred while parsing config." << std::endl;
return 1;
}

if (opts->output_format == format_custom and
(!opts->custom_format or opts->icons.empty())) {
incorrect_format_usage(argv[0]);
return 1;
}

return -1;
}
19 changes: 19 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once
#include <vector>
#include <string>

enum OutputFormat {
format_raw_default, // when its the default value
format_raw,
format_waybar,
format_custom
};

struct ProgramOptions {
std::vector<std::string> icons;
OutputFormat output_format = format_raw_default;
char *custom_format;
bool dont_follow;
};

extern int parse_opts(int argc, char *const argv[], ProgramOptions *opts);
Loading

0 comments on commit a5ae61a

Please sign in to comment.