-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 981122a
Showing
5 changed files
with
353 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
build | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
cmake_minimum_required(VERSION 3.24) | ||
project(cppbtbl) | ||
|
||
find_package(sdbus-c++ REQUIRED) | ||
|
||
add_executable(cppbtbl cppbtbl.cpp) | ||
target_link_libraries(cppbtbl PRIVATE SDBusCpp::sdbus-c++) | ||
|
||
install( | ||
TARGETS cppbtbl | ||
DESTINATION ${CMAKE_INSTALL_PREFIX}/bin | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Copyright 2022 Pato05 | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
# cppbtbl (**C++** **b**lue**t**ooth **b**attery **l**evel) | ||
|
||
A wrapper around UPower's DBus API that returns connected bluetooth devices' battery (and that can also be used as a waybar module!) | ||
|
||
## Installing | ||
|
||
First of all, you're gonna need to install sdbus-c++, see [sdbus-cpp: Building and installing the library](https://github.com/Kistler-Group/sdbus-cpp#building-and-installing-the-library) | ||
|
||
Clone the repo | ||
|
||
```bash | ||
git clone https://github.com/pato05/cppbtbl | ||
cd cppbtbl | ||
``` | ||
|
||
Build using CMake | ||
|
||
```bash | ||
cmake . | ||
make | ||
sudo make install | ||
``` | ||
|
||
### Note | ||
|
||
cppbtbl depends on sdbus-c++ which itself depends on libsystemd. Regardless of this dependency, though, you don't necessarily need systemd, as you can get libsystemd without having it: see [sdbus-cpp: Solving libsystemd dependency](https://github.com/Kistler-Group/sdbus-cpp/blob/master/docs/using-sdbus-c++.md#solving-libsystemd-dependency). | ||
|
||
Note that if you are using systemd, you won't have any issues using cppbtbl. | ||
|
||
## Waybar | ||
|
||
```json | ||
... | ||
"custom/bt-battery": { | ||
"exec": "cppbtbl -f waybar", | ||
"restart-interval": 5, | ||
"return-type": "json", | ||
"format": "{icon}", | ||
"format-icons": ["", "", "", "", "", "", "", "", "", ""] | ||
}, | ||
... | ||
``` | ||
|
||
Change format and icons accordingly, and the module should work! | ||
|
||
## Other status bars | ||
|
||
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. | ||
|
||
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: | ||
|
||
```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) | ||
``` | ||
|
||
Please note that this is just an example, and it could likely be executed better. | ||
|
||
|
||
|
||
Or a better way would be to just modify the source code to your likings. | ||
|
||
|
||
|
||
|
||
### A thank-you goes to [@Justasic](https://github.com/Justasic) for helping me a lot in making this. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
#include <iostream> | ||
#include <string> | ||
#include <sstream> | ||
#include <set> | ||
#include <vector> | ||
#include <sys/eventfd.h> | ||
#include <bits/stdc++.h> | ||
#include <getopt.h> | ||
#include <poll.h> | ||
#include <sdbus-c++/sdbus-c++.h> | ||
|
||
// battery check interval (if a device is connected) | ||
#define CHECK_INTERVAL 15 * 1000 | ||
|
||
#define UPOWER_IFACE "org.freedesktop.UPower" | ||
#define UPOWER_PATH "/org/freedesktop/UPower" | ||
#define UPOWER_DEVICE_IFACE "org.freedesktop.UPower.Device" | ||
|
||
#define PROPERTIES_IFACE "org.freedesktop.DBus.Properties" | ||
|
||
const int icon_length = 5; | ||
const char *icons[5] = { "", "", "", "", "" }; | ||
|
||
std::set<sdbus::ObjectPath> watch_list; | ||
|
||
enum OutputFormat { | ||
format_waybar, | ||
format_icon_only, | ||
format_icon_device_name, | ||
format_raw, | ||
|
||
format_invalid | ||
}; | ||
OutputFormat out_format = format_raw; | ||
|
||
const char *get_icon(int percentage) { | ||
int icon_idx = (percentage * icon_length / 100) + 0.5; | ||
return icons[icon_idx]; | ||
} | ||
|
||
void replace_all(std::string &s, const std::string &search, const std::string &replace) { | ||
for( size_t pos = 0; ; pos += replace.length() ) { | ||
// Locate the substring to replace | ||
pos = s.find( search, pos ); | ||
if( pos == std::string::npos ) break; | ||
// Replace by erasing and inserting | ||
s.erase( pos, search.length() ); | ||
s.insert( pos, replace ); | ||
} | ||
} | ||
|
||
void _get_battery_infos() { | ||
if (watch_list.empty()) return; | ||
|
||
int least_percentage = 100; | ||
std::string least_device_name; | ||
std::stringstream tooltip; | ||
for (auto &device : watch_list) { | ||
auto device_obj = sdbus::createProxy(UPOWER_IFACE, device); | ||
std::string device_name; | ||
try { | ||
device_name = device_obj->getProperty("Model") | ||
.onInterface(UPOWER_DEVICE_IFACE) | ||
.get<std::string>(); | ||
} catch (const sdbus::Error& e) { | ||
device_name = device_obj->getProperty("Serial") | ||
.onInterface(UPOWER_DEVICE_IFACE) | ||
.get<std::string>(); | ||
} | ||
|
||
double percentage = device_obj->getProperty("Percentage") | ||
.onInterface(UPOWER_DEVICE_IFACE) | ||
.get<double>(); | ||
tooltip << device_name << ": " << percentage << "%\n"; | ||
|
||
if (percentage < least_percentage) { | ||
least_percentage = percentage; | ||
least_device_name = std::move(device_name); | ||
} | ||
} | ||
|
||
std::string tooltip_str = tooltip.str(); | ||
tooltip_str.erase(tooltip_str.length() - 1); | ||
|
||
switch (out_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_icon_device_name: | ||
std::cout << get_icon(least_percentage) << ": " << least_device_name << std::endl; | ||
break; | ||
case format_icon_only: | ||
std::cout << get_icon(least_percentage) << std::endl; | ||
break; | ||
case format_raw: | ||
std::cout << tooltip_str << std::endl; | ||
break; | ||
} | ||
} | ||
|
||
void _device_added(sdbus::ObjectPath &path) { | ||
auto device_object = sdbus::createProxy(UPOWER_IFACE, path); | ||
|
||
std::string native_path = device_object | ||
->getProperty("NativePath") | ||
.onInterface(UPOWER_DEVICE_IFACE) | ||
.get<std::string>(); | ||
if (native_path.rfind("/org/bluez") != 0) { | ||
// non-bluetooth device, we can ignore | ||
return; | ||
} | ||
|
||
// std::cerr << "[DEBUG] Added to watchlist: " << path << std::endl; | ||
|
||
watch_list.insert(std::move(path)); | ||
} | ||
|
||
void _device_removed(sdbus::ObjectPath &path) { | ||
watch_list.erase(path); | ||
|
||
if (watch_list.empty()) { | ||
std::cout << std::endl; | ||
} | ||
} | ||
|
||
void _device_added_signal(sdbus::Signal &signal) { | ||
sdbus::ObjectPath device_path; | ||
signal >> device_path; | ||
|
||
// std::cerr << "[DEBUG] Received device_added signal!" << std::endl; | ||
_device_added(device_path); | ||
} | ||
|
||
void _device_removed_signal(sdbus::Signal &signal) { | ||
sdbus::ObjectPath device_path; | ||
signal >> device_path; | ||
|
||
// std::cerr << "[DEBUG] Received device_removed signal!" << std::endl; | ||
_device_removed(device_path); | ||
} | ||
|
||
void help(char *name) { | ||
std::cout | ||
<< "Usage: " << name << " -f [format] [-e]\n" | ||
<< "-f/--format [format] valid options: waybar, icononly, icon+devicename, raw (default: raw)\n" | ||
<< "-h/--help show this help screen\n" | ||
<< "-e/--dont-follow output info and exit" << std::endl; | ||
} | ||
|
||
OutputFormat optarg_to_format() { | ||
if (strcmp(optarg, "waybar") == 0) return format_waybar; | ||
if (strcmp(optarg, "icononly") == 0) return format_icon_only; | ||
if (strcmp(optarg, "icon+devicename") == 0) return format_icon_device_name; | ||
if (strcmp(optarg, "raw") == 0) return format_raw; | ||
|
||
return format_invalid; | ||
} | ||
|
||
int main(int argc, char *argv[]) { | ||
static struct option long_options[] = { | ||
{"help", no_argument, 0, 'h'}, | ||
{"format", required_argument, 0, 'f'}, | ||
{"dont-follow", no_argument, 0, 'e'} | ||
}; | ||
bool dont_follow = false; | ||
int opt; | ||
while ((opt = getopt_long(argc, argv, "hef:", long_options, nullptr)) != -1) { | ||
switch (opt) { | ||
case 'h': | ||
help(argv[0]); | ||
return 0; | ||
case 'f': | ||
out_format = optarg_to_format(); | ||
if (out_format == format_invalid) { | ||
std::cerr << "Invalid format!\n" | ||
<< "Valid values are: waybar, icononly, icon+devicename, raw." << std::endl; | ||
|
||
return 1; | ||
} | ||
break; | ||
case 'e': | ||
dont_follow = true; | ||
break; | ||
default: | ||
return 1; | ||
} | ||
} | ||
|
||
// enumerate currently connected devices, and if applicable, add them to the watch_list | ||
auto proxy = sdbus::createProxy(UPOWER_IFACE, UPOWER_PATH); | ||
auto method = proxy->createMethodCall(UPOWER_IFACE, "EnumerateDevices"); | ||
auto reply = proxy->callMethod(method); | ||
|
||
std::vector<sdbus::ObjectPath> res; | ||
reply >> res; | ||
|
||
|
||
for (auto &obj : res) { | ||
_device_added(obj); | ||
} | ||
|
||
if (dont_follow) { | ||
_get_battery_infos(); | ||
return 0; | ||
} | ||
|
||
// signal handlers | ||
proxy->registerSignalHandler(UPOWER_IFACE, "DeviceAdded", &_device_added_signal); | ||
proxy->registerSignalHandler(UPOWER_IFACE, "DeviceRemoved", &_device_removed_signal); | ||
proxy->finishRegistration(); | ||
|
||
auto connection = &proxy->getConnection(); | ||
|
||
// `poll` event loop + timer for polling connected devices' battery | ||
while (true) | ||
{ | ||
auto processed = connection->processPendingRequest(); | ||
if (processed) | ||
continue; // Process next one | ||
|
||
int timeout = -1; | ||
if (!watch_list.empty()) { | ||
timeout = CHECK_INTERVAL; | ||
_get_battery_infos(); | ||
} | ||
|
||
auto pollData = connection->getEventLoopPollData(); | ||
struct pollfd fds[] = { | ||
{pollData.fd, pollData.events, 0} | ||
}; | ||
|
||
auto r = poll(fds, 1, timeout); | ||
|
||
if (r < 0 && errno == EINTR) | ||
continue; | ||
|
||
} | ||
} |