feat(github): New module
Module used to query the GitHub API for information. Currently only supports notification count. Ref #84
This commit is contained in:
parent
e72f85079f
commit
b417c9f812
@ -33,6 +33,14 @@ if(NOT DEFINED ENABLE_I3 AND NOT I3_BINARY)
|
||||
set(ENABLE_I3 OFF CACHE STRING "Module support for i3wm")
|
||||
endif()
|
||||
|
||||
# }}}
|
||||
# Default value for: ENABLE_CURL {{{
|
||||
|
||||
find_package(CURL QUIET)
|
||||
if(NOT DEFINED ENABLE_CURL AND NOT CURL_FOUND)
|
||||
set(ENABLE_CURL OFF CACHE STRING "Module support for libcurl")
|
||||
endif()
|
||||
|
||||
# }}}
|
||||
|
||||
# Define build options {{{
|
||||
@ -47,6 +55,7 @@ option(DEBUG_HINTS "Enable hints rendering" OFF)
|
||||
|
||||
option(ENABLE_CCACHE "Enable ccache support" OFF)
|
||||
option(ENABLE_ALSA "Enable alsa support" ON)
|
||||
option(ENABLE_CURL "Enable curl support" ON)
|
||||
option(ENABLE_I3 "Enable i3 support" ON)
|
||||
option(ENABLE_MPD "Enable mpd support" ON)
|
||||
option(ENABLE_NETWORK "Enable network support" ON)
|
||||
|
@ -51,6 +51,7 @@ colored_option(STATUS " Draw debug hints ${DEBUG_HINTS}" DEBUG_HINTS "32;1"
|
||||
colored_option(STATUS " Enable ccache ${ENABLE_CCACHE}" ENABLE_CCACHE "32;1" "37;2")
|
||||
message(STATUS "--------------------------")
|
||||
colored_option(STATUS " Enable alsa ${ENABLE_ALSA}" ENABLE_ALSA "32;1" "37;2")
|
||||
colored_option(STATUS " Enable curl ${ENABLE_CURL}" ENABLE_CURL "32;1" "37;2")
|
||||
colored_option(STATUS " Enable i3 ${ENABLE_I3}" ENABLE_I3 "32;1" "37;2")
|
||||
colored_option(STATUS " Enable mpd ${ENABLE_MPD}" ENABLE_MPD "32;1" "37;2")
|
||||
colored_option(STATUS " Enable network ${ENABLE_NETWORK}" ENABLE_NETWORK "32;1" "37;2")
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "components/logger.hpp"
|
||||
#include "errors.hpp"
|
||||
#include "utils/env.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "x11/xresources.hpp"
|
||||
|
||||
@ -196,6 +197,8 @@ class config {
|
||||
return dereference_env<T>(path.substr(4), fallback);
|
||||
} else if (path.compare(0, 5, "xrdb:") == 0) {
|
||||
return dereference_xrdb<T>(path.substr(5), fallback);
|
||||
} else if (path.compare(0, 5, "file:") == 0) {
|
||||
return dereference_file<T>(path.substr(5), fallback);
|
||||
} else if ((pos = path.find(".")) != string::npos) {
|
||||
return dereference_local<T>(path.substr(0, pos), path.substr(pos + 1), section);
|
||||
} else {
|
||||
@ -274,6 +277,21 @@ class config {
|
||||
return str.empty() ? fallback : convert<T>(move(str));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dereference file reference by reading its contents
|
||||
* ${file:/absolute/file/path}
|
||||
*/
|
||||
template <typename T>
|
||||
T dereference_file(string var, const T& fallback) const {
|
||||
string filename{move(var)};
|
||||
|
||||
if (file_util::exists(filename)) {
|
||||
return convert<T>(string_util::trim(file_util::get_contents(filename), '\n'));
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr const char* KEY_INHERIT{"inherit"};
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#cmakedefine01 ENABLE_MPD
|
||||
#cmakedefine01 ENABLE_NETWORK
|
||||
#cmakedefine01 ENABLE_I3
|
||||
#cmakedefine01 ENABLE_CURL
|
||||
|
||||
#cmakedefine01 WITH_XRANDR
|
||||
#cmakedefine01 WITH_XRENDER
|
||||
@ -78,6 +79,7 @@ auto print_build_info = [](bool extended = false) {
|
||||
<< "\n"
|
||||
<< "Features: "
|
||||
<< (ENABLE_ALSA ? "+" : "-") << "alsa "
|
||||
<< (ENABLE_CURL ? "+" : "-") << "curl "
|
||||
<< (ENABLE_I3 ? "+" : "-") << "i3 "
|
||||
<< (ENABLE_MPD ? "+" : "-") << "mpd "
|
||||
<< (ENABLE_NETWORK ? "+" : "-") << "network "
|
||||
|
30
include/modules/github.hpp
Normal file
30
include/modules/github.hpp
Normal file
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "config.hpp"
|
||||
#include "modules/meta/timer_module.hpp"
|
||||
#include "utils/http.hpp"
|
||||
|
||||
POLYBAR_NS
|
||||
|
||||
namespace modules {
|
||||
/**
|
||||
* Module used to query the GitHub API for notification count
|
||||
*/
|
||||
class github_module : public timer_module<github_module> {
|
||||
public:
|
||||
explicit github_module(const bar_settings&, string);
|
||||
|
||||
void setup();
|
||||
bool update();
|
||||
bool build(builder* builder, const string& tag) const;
|
||||
|
||||
private:
|
||||
static constexpr auto TAG_LABEL = "<label>";
|
||||
|
||||
label_t m_label{};
|
||||
string m_accesstoken{};
|
||||
unique_ptr<http_downloader> m_http{};
|
||||
};
|
||||
}
|
||||
|
||||
POLYBAR_NS_END
|
@ -25,6 +25,9 @@ namespace modules {
|
||||
thread_.join();
|
||||
}
|
||||
}
|
||||
if (m_mainthread.joinable()) {
|
||||
m_mainthread.join();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
@ -44,7 +47,7 @@ namespace modules {
|
||||
|
||||
template <typename Impl>
|
||||
bool module<Impl>::running() const {
|
||||
return m_enabled.load(std::memory_order_relaxed);
|
||||
return static_cast<bool>(m_enabled);
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
@ -66,24 +69,20 @@ namespace modules {
|
||||
}
|
||||
|
||||
m_log.info("%s: Stopping", name());
|
||||
m_enabled.store(false, std::memory_order_relaxed);
|
||||
m_enabled = false;
|
||||
|
||||
wakeup();
|
||||
|
||||
std::lock_guard<std::mutex> guard_a(m_buildlock);
|
||||
std::lock_guard<std::mutex> guard_b(m_updatelock);
|
||||
std::lock(m_buildlock, m_updatelock);
|
||||
std::lock_guard<std::mutex> guard_a(m_buildlock, std::adopt_lock);
|
||||
std::lock_guard<std::mutex> guard_b(m_updatelock, std::adopt_lock);
|
||||
{
|
||||
CAST_MOD(Impl)->wakeup();
|
||||
CAST_MOD(Impl)->teardown();
|
||||
|
||||
if (m_mainthread.joinable()) {
|
||||
m_mainthread.join();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_stop_callback) {
|
||||
m_stop_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Impl>
|
||||
void module<Impl>::halt(string error_message) {
|
||||
|
@ -31,10 +31,13 @@
|
||||
#if ENABLE_ALSA
|
||||
#include "modules/volume.hpp"
|
||||
#endif
|
||||
#if ENABLE_CURL
|
||||
#include "modules/github.hpp"
|
||||
#endif
|
||||
#if WITH_XKB
|
||||
#include "modules/xkeyboard.hpp"
|
||||
#endif
|
||||
#if not(ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && ENABLE_ALSA && WITH_XKB)
|
||||
#if not(ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && ENABLE_ALSA && ENABLE_CURL && WITH_XKB)
|
||||
#include "modules/unsupported.hpp"
|
||||
#endif
|
||||
|
||||
@ -57,6 +60,8 @@ namespace {
|
||||
return new cpu_module(forward<Args>(args)...);
|
||||
} else if (name == "internal/date") {
|
||||
return new date_module(forward<Args>(args)...);
|
||||
} else if (name == "internal/github") {
|
||||
return new github_module(forward<Args>(args)...);
|
||||
} else if (name == "internal/fs") {
|
||||
return new fs_module(forward<Args>(args)...);
|
||||
} else if (name == "internal/memory") {
|
||||
|
@ -15,13 +15,16 @@ namespace modules {
|
||||
void timer_module<Impl>::runner() {
|
||||
try {
|
||||
while (CONST_MOD(Impl).running()) {
|
||||
std::lock_guard<std::mutex> guard(this->m_updatelock);
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->m_updatelock);
|
||||
|
||||
if (CAST_MOD(Impl)->update())
|
||||
CAST_MOD(Impl)->broadcast();
|
||||
}
|
||||
if (CONST_MOD(Impl).running()) {
|
||||
CAST_MOD(Impl)->sleep(m_interval);
|
||||
}
|
||||
}
|
||||
} catch (const module_error& err) {
|
||||
CAST_MOD(Impl)->halt(err.what());
|
||||
} catch (const std::exception& err) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
#if ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && ENABLE_ALSA && WITH_XKB
|
||||
#if ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && ENABLE_ALSA && ENABLE_CURL && WITH_XKB
|
||||
#error "Support has been enabled for all optional modules"
|
||||
#endif
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
#if not(ENABLE_ALSA && ENABLE_I3 && ENABLE_MPD)
|
||||
#include "modules/meta/event_module.inl"
|
||||
#endif
|
||||
#if not ENABLE_NETWORK
|
||||
#if not(ENABLE_NETWORK && ENABLE_CURL)
|
||||
#include "modules/meta/timer_module.inl"
|
||||
#endif
|
||||
#if not WITH_XKB
|
||||
@ -62,6 +62,9 @@ namespace modules {
|
||||
#if not ENABLE_ALSA
|
||||
DEFINE_UNSUPPORTED_MODULE(volume_module, "internal/volume");
|
||||
#endif
|
||||
#if not ENABLE_CURL
|
||||
DEFINE_UNSUPPORTED_MODULE(github_module, "internal/github");
|
||||
#endif
|
||||
#if not WITH_XKB
|
||||
DEFINE_UNSUPPORTED_MODULE(xkeyboard_module, "internal/xkeyboard");
|
||||
#endif
|
||||
|
30
include/utils/http.hpp
Normal file
30
include/utils/http.hpp
Normal file
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.hpp"
|
||||
#include "utils/factory.hpp"
|
||||
|
||||
POLYBAR_NS
|
||||
|
||||
class http_downloader {
|
||||
public:
|
||||
http_downloader(int connection_timeout = 5);
|
||||
~http_downloader();
|
||||
|
||||
string get(const string& url);
|
||||
long response_code();
|
||||
|
||||
protected:
|
||||
static size_t write(void* p, size_t size, size_t bytes, void* stream);
|
||||
|
||||
private:
|
||||
void* m_curl;
|
||||
};
|
||||
|
||||
namespace http_util {
|
||||
template <typename... Args>
|
||||
decltype(auto) make_downloader(Args&&... args) {
|
||||
return factory_util::unique<http_downloader>(forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
POLYBAR_NS_END
|
@ -18,6 +18,9 @@ endif()
|
||||
if(NOT ENABLE_I3)
|
||||
list(REMOVE_ITEM SOURCES modules/i3.cpp utils/i3.cpp)
|
||||
endif()
|
||||
if(NOT ENABLE_CURL)
|
||||
list(REMOVE_ITEM SOURCES utils/http.cpp modules/github.cpp)
|
||||
endif()
|
||||
|
||||
if(NOT WITH_XCOMPOSITE)
|
||||
list(REMOVE_ITEM SOURCES x11/composite.cpp)
|
||||
@ -128,6 +131,16 @@ if(ENABLE_I3)
|
||||
endif()
|
||||
|
||||
# }}}
|
||||
# Optional dependency: i3ipcpp {{{
|
||||
|
||||
if(ENABLE_CURL)
|
||||
find_package(CURL REQUIRED)
|
||||
set(APP_LIBRARIES ${APP_LIBRARIES} ${CURL_LIBRARY})
|
||||
set(APP_INCLUDE_DIRS ${APP_INCLUDE_DIRS} ${CURL_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
# }}}
|
||||
|
||||
# Create executable target {{{
|
||||
|
||||
make_executable(${PROJECT_NAME} SOURCES
|
||||
|
79
src/modules/github.cpp
Normal file
79
src/modules/github.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
#include "modules/github.hpp"
|
||||
#include "drawtypes/label.hpp"
|
||||
|
||||
#include "modules/meta/base.inl"
|
||||
#include "modules/meta/timer_module.inl"
|
||||
|
||||
POLYBAR_NS
|
||||
|
||||
namespace modules {
|
||||
template class module<github_module>;
|
||||
template class timer_module<github_module>;
|
||||
|
||||
/**
|
||||
* Construct module
|
||||
*/
|
||||
github_module::github_module(const bar_settings& bar, string name)
|
||||
: timer_module<github_module>(bar, name), m_http(http_util::make_downloader()) {}
|
||||
|
||||
/**
|
||||
* Bootstrap module
|
||||
*/
|
||||
void github_module::setup() {
|
||||
m_accesstoken = m_conf.get<string>(name(), "token");
|
||||
m_interval = m_conf.get<chrono::seconds>(name(), "interval", 60s);
|
||||
|
||||
m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL});
|
||||
|
||||
if (m_formatter->has(TAG_LABEL)) {
|
||||
m_label = load_optional_label(m_conf, name(), TAG_LABEL, "Notifications: %notifications%");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update module contents
|
||||
*/
|
||||
bool github_module::update() {
|
||||
string content{m_http->get("https://api.github.com/notifications?access_token=" + m_accesstoken)};
|
||||
long response_code{m_http->response_code()};
|
||||
|
||||
switch (response_code) {
|
||||
case 200:
|
||||
break;
|
||||
case 401:
|
||||
throw module_error("Bad credentials");
|
||||
case 403:
|
||||
throw module_error("Maximum number of login attempts exceeded");
|
||||
default:
|
||||
throw module_error("Unspecified error (" + to_string(response_code) + ")");
|
||||
}
|
||||
|
||||
size_t pos{0};
|
||||
size_t notifications{0};
|
||||
|
||||
while ((pos = content.find("\"unread\":true", pos + 1)) != string::npos) {
|
||||
notifications++;
|
||||
}
|
||||
|
||||
if (m_label) {
|
||||
m_label->reset_tokens();
|
||||
m_label->replace_token("%notifications%", to_string(notifications));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build module content
|
||||
*/
|
||||
bool github_module::build(builder* builder, const string& tag) const {
|
||||
if (tag == TAG_LABEL) {
|
||||
builder->node(m_label);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
POLYBAR_NS_END
|
51
src/utils/http.cpp
Normal file
51
src/utils/http.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
#include <curl/curl.h>
|
||||
#include <curl/curlbuild.h>
|
||||
#include <curl/easy.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "errors.hpp"
|
||||
#include "utils/http.hpp"
|
||||
|
||||
POLYBAR_NS
|
||||
|
||||
http_downloader::http_downloader(int connection_timeout) {
|
||||
m_curl = curl_easy_init();
|
||||
curl_easy_setopt(m_curl, CURLOPT_ACCEPT_ENCODING, "deflate");
|
||||
curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, connection_timeout);
|
||||
curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_easy_setopt(m_curl, CURLOPT_NOSIGNAL, true);
|
||||
curl_easy_setopt(m_curl, CURLOPT_USERAGENT, "polybar/" GIT_TAG);
|
||||
curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, http_downloader::write);
|
||||
}
|
||||
|
||||
http_downloader::~http_downloader() {
|
||||
curl_easy_cleanup(m_curl);
|
||||
}
|
||||
|
||||
string http_downloader::get(const string& url) {
|
||||
stringstream out{};
|
||||
curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &out);
|
||||
|
||||
auto res = curl_easy_perform(m_curl);
|
||||
if (res != CURLE_OK) {
|
||||
throw application_error(curl_easy_strerror(res), res);
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
long http_downloader::response_code() {
|
||||
long code{0};
|
||||
curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &code);
|
||||
return code;
|
||||
}
|
||||
|
||||
size_t http_downloader::write(void* p, size_t size, size_t bytes, void* stream) {
|
||||
string data{static_cast<const char*>(p), size * bytes};
|
||||
*(static_cast<stringstream*>(stream)) << data << '\n';
|
||||
return size * bytes;
|
||||
}
|
||||
|
||||
POLYBAR_NS_END
|
Loading…
Reference in New Issue
Block a user