Merge branch 'master' into fix_power_calc

This commit is contained in:
Alex
2021-07-25 15:07:01 +02:00
committed by GitHub
39 changed files with 960 additions and 217 deletions

View File

@@ -76,7 +76,7 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
return "";
}
std::string ALabel::getIcon(uint16_t percentage, std::vector<std::string>& alts, uint16_t max) {
std::string ALabel::getIcon(uint16_t percentage, const std::vector<std::string>& alts, uint16_t max) {
auto format_icons = config_["format-icons"];
if (format_icons.isObject()) {
std::string _alt = "default";

View File

@@ -234,14 +234,62 @@ std::tuple<const std::string, const std::string> waybar::Client::getConfigs(
return {config_file, css_file};
}
auto waybar::Client::setupConfig(const std::string &config_file) -> void {
auto waybar::Client::setupConfig(const std::string &config_file, int depth) -> void {
if (depth > 100) {
throw std::runtime_error("Aborting due to likely recursive include in config files");
}
std::ifstream file(config_file);
if (!file.is_open()) {
throw std::runtime_error("Can't open config file");
}
std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
util::JsonParser parser;
config_ = parser.parse(str);
Json::Value tmp_config_ = parser.parse(str);
if (tmp_config_.isArray()) {
for (auto &config_part : tmp_config_) {
resolveConfigIncludes(config_part, depth);
}
} else {
resolveConfigIncludes(tmp_config_, depth);
}
mergeConfig(config_, tmp_config_);
}
auto waybar::Client::resolveConfigIncludes(Json::Value &config, int depth) -> void {
Json::Value includes = config["include"];
if (includes.isArray()) {
for (const auto &include : includes) {
spdlog::info("Including resource file: {}", include.asString());
setupConfig(getValidPath({include.asString()}), ++depth);
}
} else if (includes.isString()) {
spdlog::info("Including resource file: {}", includes.asString());
setupConfig(getValidPath({includes.asString()}), ++depth);
}
}
auto waybar::Client::mergeConfig(Json::Value &a_config_, Json::Value &b_config_) -> void {
if (!a_config_) {
// For the first config
a_config_ = b_config_;
} else if (a_config_.isObject() && b_config_.isObject()) {
for (const auto &key : b_config_.getMemberNames()) {
if (a_config_[key].isObject() && b_config_[key].isObject()) {
mergeConfig(a_config_[key], b_config_[key]);
} else {
a_config_[key] = b_config_[key];
}
}
} else if (a_config_.isArray() && b_config_.isArray()) {
// This can happen only on the top-level array of a multi-bar config
for (Json::Value::ArrayIndex i = 0; i < b_config_.size(); i++) {
if (a_config_[i].isObject() && b_config_[i].isObject()) {
mergeConfig(a_config_[i], b_config_[i]);
}
}
} else {
spdlog::error("Cannot merge config, conflicting or invalid JSON types");
}
}
auto waybar::Client::setupCss(const std::string &css_file) -> void {
@@ -320,7 +368,7 @@ int waybar::Client::main(int argc, char *argv[]) {
}
wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj());
auto [config_file, css_file] = getConfigs(config, style);
setupConfig(config_file);
setupConfig(config_file, 0);
setupCss(css_file);
bindInterfaces();
gtk_app->hold();

View File

@@ -70,6 +70,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
return new waybar::modules::Backlight(id, config_[name]);
}
#endif
#ifdef HAVE_LIBEVDEV
if (ref == "keyboard-state") {
return new waybar::modules::KeyboardState(id, bar_, config_[name]);
}
#endif
#ifdef HAVE_LIBPULSE
if (ref == "pulseaudio") {
return new waybar::modules::Pulseaudio(id, config_[name]);

View File

@@ -196,6 +196,9 @@ template <>
struct fmt::formatter<waybar_time> : fmt::formatter<std::tm> {
template <typename FormatContext>
auto format(const waybar_time& t, FormatContext& ctx) {
#if FMT_VERSION >= 80000
auto& tm_format = specs;
#endif
return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(tm_format), t.ztime));
}
};

View File

@@ -0,0 +1,152 @@
#include "modules/keyboard_state.hpp"
#include <filesystem>
#include <spdlog/spdlog.h>
extern "C" {
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
}
waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar, const Json::Value& config)
: AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
numlock_label_(""),
capslock_label_(""),
numlock_format_(config_["format"].isString() ? config_["format"].asString()
: config_["format"]["numlock"].isString() ? config_["format"]["numlock"].asString()
: "{name} {icon}"),
capslock_format_(config_["format"].isString() ? config_["format"].asString()
: config_["format"]["capslock"].isString() ? config_["format"]["capslock"].asString()
: "{name} {icon}"),
scrolllock_format_(config_["format"].isString() ? config_["format"].asString()
: config_["format"]["scrolllock"].isString() ? config_["format"]["scrolllock"].asString()
: "{name} {icon}"),
interval_(std::chrono::seconds(config_["interval"].isUInt() ? config_["interval"].asUInt() : 1)),
icon_locked_(config_["format-icons"]["locked"].isString()
? config_["format-icons"]["locked"].asString()
: "locked"),
icon_unlocked_(config_["format-icons"]["unlocked"].isString()
? config_["format-icons"]["unlocked"].asString()
: "unlocked"),
fd_(0),
dev_(nullptr) {
box_.set_name("keyboard-state");
if (config_["numlock"].asBool()) {
box_.pack_end(numlock_label_, false, false, 0);
}
if (config_["capslock"].asBool()) {
box_.pack_end(capslock_label_, false, false, 0);
}
if (config_["scrolllock"].asBool()) {
box_.pack_end(scrolllock_label_, false, false, 0);
}
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
event_box_.add(box_);
if (config_["device-path"].isString()) {
std::string dev_path = config_["device-path"].asString();
std::tie(fd_, dev_) = openDevice(dev_path);
} else {
DIR* dev_dir = opendir("/dev/input");
if (dev_dir == nullptr) {
throw std::runtime_error("Failed to open /dev/input");
}
dirent *ep;
while ((ep = readdir(dev_dir))) {
if (ep->d_type != DT_CHR) continue;
std::string dev_path = std::string("/dev/input/") + ep->d_name;
try {
std::tie(fd_, dev_) = openDevice(dev_path);
spdlog::info("Found device {} at '{}'", libevdev_get_name(dev_), dev_path);
break;
} catch (const std::runtime_error& e) {
continue;
}
}
if (dev_ == nullptr) {
throw std::runtime_error("Failed to find keyboard device");
}
}
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
}
waybar::modules::KeyboardState::~KeyboardState() {
libevdev_free(dev_);
int err = close(fd_);
if (err < 0) {
// Not much we can do, so ignore it.
}
}
auto waybar::modules::KeyboardState::openDevice(const std::string& path) -> std::pair<int, libevdev*> {
int fd = open(path.c_str(), O_NONBLOCK | O_CLOEXEC | O_RDONLY);
if (fd < 0) {
throw std::runtime_error("Can't open " + path);
}
libevdev* dev;
int err = libevdev_new_from_fd(fd, &dev);
if (err < 0) {
throw std::runtime_error("Can't create libevdev device");
}
if (!libevdev_has_event_type(dev, EV_LED)) {
throw std::runtime_error("Device doesn't support LED events");
}
if (!libevdev_has_event_code(dev, EV_LED, LED_NUML)
|| !libevdev_has_event_code(dev, EV_LED, LED_CAPSL)
|| !libevdev_has_event_code(dev, EV_LED, LED_SCROLLL)) {
throw std::runtime_error("Device doesn't support num lock, caps lock, or scroll lock events");
}
return std::make_pair(fd, dev);
}
auto waybar::modules::KeyboardState::update() -> void {
int err = LIBEVDEV_READ_STATUS_SUCCESS;
while (err == LIBEVDEV_READ_STATUS_SUCCESS) {
input_event ev;
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_NORMAL, &ev);
while (err == LIBEVDEV_READ_STATUS_SYNC) {
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_SYNC, &ev);
}
}
if (err != -EAGAIN) {
throw std::runtime_error("Failed to sync evdev device");
}
int numl = libevdev_get_event_value(dev_, EV_LED, LED_NUML);
int capsl = libevdev_get_event_value(dev_, EV_LED, LED_CAPSL);
int scrolll = libevdev_get_event_value(dev_, EV_LED, LED_SCROLLL);
struct {
bool state;
Gtk::Label& label;
const std::string& format;
const char* name;
} label_states[] = {
{(bool) numl, numlock_label_, numlock_format_, "Num"},
{(bool) capsl, capslock_label_, capslock_format_, "Caps"},
{(bool) scrolll, scrolllock_label_, scrolllock_format_, "Scroll"},
};
for (auto& label_state : label_states) {
std::string text;
text = fmt::format(label_state.format,
fmt::arg("icon", label_state.state ? icon_locked_ : icon_unlocked_),
fmt::arg("name", label_state.name));
label_state.label.set_markup(text);
if (label_state.state) {
label_state.label.get_style_context()->add_class("locked");
} else {
label_state.label.get_style_context()->remove_class("locked");
}
}
AModule::update();
}

View File

@@ -1,6 +1,7 @@
#include "modules/network.hpp"
#include <spdlog/spdlog.h>
#include <sys/eventfd.h>
#include <linux/if.h>
#include <fstream>
#include <cassert>
#include <optional>
@@ -18,6 +19,7 @@ constexpr const char *NETSTAT_FILE =
constexpr std::string_view BANDWIDTH_CATEGORY = "IpExt";
constexpr std::string_view BANDWIDTH_DOWN_TOTAL_KEY = "InOctets";
constexpr std::string_view BANDWIDTH_UP_TOTAL_KEY = "OutOctets";
constexpr const char *DEFAULT_FORMAT = "{ifname}";
std::ifstream netstat(NETSTAT_FILE);
std::optional<unsigned long long> read_netstat(std::string_view category, std::string_view key) {
@@ -81,7 +83,7 @@ std::optional<unsigned long long> read_netstat(std::string_view category, std::s
} // namespace
waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
: ALabel(config, "network", id, "{ifname}", 60),
: ALabel(config, "network", id, DEFAULT_FORMAT, 60),
ifid_(-1),
family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET),
efd_(-1),
@@ -322,6 +324,10 @@ auto waybar::modules::Network::update() -> void {
}
if (config_["format-" + state].isString()) {
default_format_ = config_["format-" + state].asString();
} else if (config_["format"].isString()) {
default_format_ = config_["format"].asString();
} else {
default_format_ = DEFAULT_FORMAT;
}
if (config_["tooltip-format-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + state].asString();
@@ -400,6 +406,7 @@ bool waybar::modules::Network::checkInterface(std::string name) {
void waybar::modules::Network::clearIface() {
ifid_ = -1;
ifname_.clear();
essid_.clear();
ipaddr_.clear();
netmask_.clear();
@@ -431,6 +438,20 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
return NL_OK;
}
// Check if the interface goes "down" and if we want to detect the
// external interface.
if (net->ifid_ != -1 && !(ifi->ifi_flags & IFF_UP)
&& !net->config_["interface"].isString()) {
// The current interface is now down, all the routes associated with
// it have been deleted, so start looking for a new default route.
spdlog::debug("network: if{} down", net->ifid_);
net->clearIface();
net->dp.emit();
net->want_route_dump_ = true;
net->askForStateDump();
return NL_OK;
}
for (; RTA_OK(ifla, attrlen); ifla = RTA_NEXT(ifla, attrlen)) {
switch (ifla->rta_type) {
case IFLA_IFNAME:
@@ -572,11 +593,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
bool has_gateway = false;
bool has_destination = false;
int temp_idx = -1;
/* If we found the correct answer, skip parsing the attributes. */
if (!is_del_event && net->ifid_ != -1) {
return NL_OK;
}
uint32_t priority = 0;
/* Find the message(s) concerting the main routing table, each message
* corresponds to a single routing table entry.
@@ -620,52 +637,59 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
/* The output interface index. */
temp_idx = *static_cast<int *>(RTA_DATA(attr));
break;
case RTA_PRIORITY:
priority = *(uint32_t*)RTA_DATA(attr);
break;
default:
break;
}
}
/* If this is the default route, and we know the interface index,
* we can stop parsing this message.
*/
if (has_gateway && !has_destination && temp_idx != -1) {
if (!is_del_event) {
net->ifid_ = temp_idx;
// Check if we have a default route.
if (has_gateway && !has_destination && temp_idx != -1) {
// Check if this is the first default route we see, or if this new
// route have a higher priority.
if (!is_del_event && ((net->ifid_ == -1) || (priority < net->route_priority))) {
// Clear if's state for the case were there is a higher priority
// route on a different interface.
net->clearIface();
net->ifid_ = temp_idx;
net->route_priority = priority;
spdlog::debug("network: new default route via if{}", temp_idx);
spdlog::debug("network: new default route via if{} metric {}", temp_idx, priority);
/* Ask ifname associated with temp_idx as well as carrier status */
struct ifinfomsg ifinfo_hdr = {
.ifi_family = AF_UNSPEC,
.ifi_index = temp_idx,
};
int err;
err = nl_send_simple(net->ev_sock_, RTM_GETLINK, NLM_F_REQUEST,
&ifinfo_hdr, sizeof (ifinfo_hdr));
if (err < 0) {
spdlog::error("network: failed to ask link info: {}", err);
/* Ask for a dump of all links instead */
net->want_link_dump_ = true;
}
/* Also ask for the address. Asking for a addresses of a specific
* interface doesn't seems to work so ask for a dump of all
* addresses. */
net->want_addr_dump_ = true;
net->askForStateDump();
net->thread_timer_.wake_up();
} else if (is_del_event && temp_idx == net->ifid_) {
spdlog::debug("network: default route deleted {}/if{}",
net->ifname_, temp_idx);
net->ifname_.clear();
net->clearIface();
net->dp.emit();
/* Ask for a dump of all routes in case another one is already
* setup. If there's none, there'll be an event with new one
* later. */
net->want_route_dump_ = true;
net->askForStateDump();
/* Ask ifname associated with temp_idx as well as carrier status */
struct ifinfomsg ifinfo_hdr = {
.ifi_family = AF_UNSPEC,
.ifi_index = temp_idx,
};
int err;
err = nl_send_simple(net->ev_sock_, RTM_GETLINK, NLM_F_REQUEST,
&ifinfo_hdr, sizeof (ifinfo_hdr));
if (err < 0) {
spdlog::error("network: failed to ask link info: {}", err);
/* Ask for a dump of all links instead */
net->want_link_dump_ = true;
}
/* Also ask for the address. Asking for a addresses of a specific
* interface doesn't seems to work so ask for a dump of all
* addresses. */
net->want_addr_dump_ = true;
net->askForStateDump();
net->thread_timer_.wake_up();
} else if (is_del_event && temp_idx == net->ifid_
&& net->route_priority == priority) {
spdlog::debug("network: default route deleted {}/if{} metric {}",
net->ifname_, temp_idx, priority);
net->clearIface();
net->dp.emit();
/* Ask for a dump of all routes in case another one is already
* setup. If there's none, there'll be an event with new one
* later. */
net->want_route_dump_ = true;
net->askForStateDump();
}
}
break;

View File

@@ -151,8 +151,24 @@ void waybar::modules::Pulseaudio::sourceInfoCb(pa_context * /*context*/, const p
*/
void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i,
int /*eol*/, void *data) {
if (i == nullptr)
return;
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
if (i != nullptr && pa->default_sink_name_ == i->name) {
if (pa->current_sink_name_ == i->name) {
if (i->state != PA_SINK_RUNNING) {
pa->current_sink_running_ = false;
} else {
pa->current_sink_running_ = true;
}
}
if (!pa->current_sink_running_ && i->state == PA_SINK_RUNNING) {
pa->current_sink_name_ = i->name;
pa->current_sink_running_ = true;
}
if (pa->current_sink_name_ == i->name) {
pa->pa_volume_ = i->volume;
float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) / float{PA_VOLUME_NORM};
pa->sink_idx_ = i->index;
@@ -175,11 +191,11 @@ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_
void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, const pa_server_info *i,
void *data) {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
pa->default_sink_name_ = i->default_sink_name;
pa->current_sink_name_ = i->default_sink_name;
pa->default_source_name_ = i->default_source_name;
pa_context_get_sink_info_by_name(context, i->default_sink_name, sinkInfoCb, data);
pa_context_get_source_info_by_name(context, i->default_source_name, sourceInfoCb, data);
pa_context_get_sink_info_list(context, sinkInfoCb, data);
pa_context_get_source_info_list(context, sourceInfoCb, data);
}
static const std::array<std::string, 9> ports = {
@@ -194,15 +210,17 @@ static const std::array<std::string, 9> ports = {
"phone",
};
const std::string waybar::modules::Pulseaudio::getPortIcon() const {
const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {
std::vector<std::string> res = {default_source_name_};
std::string nameLC = port_name_ + form_factor_;
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
for (auto const &port : ports) {
if (nameLC.find(port) != std::string::npos) {
return port;
res.push_back(port);
return res;
}
}
return port_name_;
return res;
}
auto waybar::modules::Pulseaudio::update() -> void {
@@ -252,7 +270,7 @@ auto waybar::modules::Pulseaudio::update() -> void {
fmt::arg("format_source", format_source),
fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_),
fmt::arg("icon", getIcon(volume_, getPortIcon()))));
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
getState(volume_);
if (tooltipEnabled()) {
@@ -267,7 +285,7 @@ auto waybar::modules::Pulseaudio::update() -> void {
fmt::arg("format_source", format_source),
fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_),
fmt::arg("icon", getIcon(volume_, getPortIcon()))));
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
} else {
label_.set_tooltip_text(desc_);
}

View File

@@ -1,7 +1,12 @@
#include "modules/sni/item.hpp"
#include <gdkmm/general.h>
#include <glibmm/main.h>
#include <gtkmm/tooltip.h>
#include <spdlog/spdlog.h>
#include <fstream>
#include <map>
template <>
struct fmt::formatter<Glib::ustring> : formatter<std::string> {
@@ -39,14 +44,22 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf
object_path(op),
icon_size(16),
effective_icon_size(0),
icon_theme(Gtk::IconTheme::create()),
update_pending_(false) {
icon_theme(Gtk::IconTheme::create()) {
if (config["icon-size"].isUInt()) {
icon_size = config["icon-size"].asUInt();
}
if (config["smooth-scrolling-threshold"].isNumeric()) {
scroll_threshold_ = config["smooth-scrolling-threshold"].asDouble();
}
if (config["show-passive-items"].isBool()) {
show_passive_ = config["show-passive-items"].asBool();
}
event_box.add(image);
event_box.add_events(Gdk::BUTTON_PRESS_MASK);
event_box.add_events(Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick));
event_box.signal_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll));
// initial visibility
event_box.set_visible(show_passive_);
cancellable_ = Gio::Cancellable::create();
@@ -73,12 +86,11 @@ void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
this->proxy_->signal_signal().connect(sigc::mem_fun(*this, &Item::onSignal));
if (this->id.empty() || this->category.empty() || this->status.empty()) {
if (this->id.empty() || this->category.empty()) {
spdlog::error("Invalid Status Notifier Item: {}, {}", bus_name, object_path);
return;
}
this->updateImage();
// this->event_box.set_tooltip_text(this->title);
} catch (const Glib::Error& err) {
spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what());
@@ -88,10 +100,24 @@ void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
}
template <typename T>
T get_variant(Glib::VariantBase& value) {
T get_variant(const Glib::VariantBase& value) {
return Glib::VariantBase::cast_dynamic<Glib::Variant<T>>(value).get();
}
template <>
ToolTip get_variant<ToolTip>(const Glib::VariantBase& value) {
ToolTip result;
// Unwrap (sa(iiay)ss)
auto container = value.cast_dynamic<Glib::VariantContainerBase>(value);
result.icon_name = get_variant<Glib::ustring>(container.get_child(0));
result.text = get_variant<Glib::ustring>(container.get_child(2));
auto description = get_variant<Glib::ustring>(container.get_child(3));
if (!description.empty()) {
result.text = fmt::format("<b>{}</b>\n{}", result.text, description);
}
return result;
}
void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
try {
spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, name, value);
@@ -102,10 +128,11 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
id = get_variant<std::string>(value);
} else if (name == "Title") {
title = get_variant<std::string>(value);
if (tooltip.text.empty()) {
event_box.set_tooltip_markup(title);
}
} else if (name == "Status") {
status = get_variant<std::string>(value);
} else if (name == "WindowId") {
window_id = get_variant<int32_t>(value);
setStatus(get_variant<Glib::ustring>(value));
} else if (name == "IconName") {
icon_name = get_variant<std::string>(value);
} else if (name == "IconPixmap") {
@@ -121,7 +148,10 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
} else if (name == "AttentionMovieName") {
attention_movie_name = get_variant<std::string>(value);
} else if (name == "ToolTip") {
// TODO: tooltip
tooltip = get_variant<ToolTip>(value);
if (!tooltip.text.empty()) {
event_box.set_tooltip_markup(tooltip.text);
}
} else if (name == "IconThemePath") {
icon_theme_path = get_variant<std::string>(value);
if (!icon_theme_path.empty()) {
@@ -148,9 +178,22 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
}
}
void Item::getUpdatedProperties() {
update_pending_ = false;
void Item::setStatus(const Glib::ustring& value) {
Glib::ustring lower = value.lowercase();
event_box.set_visible(show_passive_ || lower.compare("passive") != 0);
auto style = event_box.get_style_context();
for (const auto& class_name : style->list_classes()) {
style->remove_class(class_name);
}
if (lower.compare("needsattention") == 0) {
// convert status to dash-case for CSS
lower = "needs-attention";
}
style->add_class(lower);
}
void Item::getUpdatedProperties() {
auto params = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)});
proxy_->call("org.freedesktop.DBus.Properties.GetAll",
@@ -167,33 +210,48 @@ void Item::processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& _result) {
auto properties = properties_variant.get();
for (const auto& [name, value] : properties) {
Glib::VariantBase old_value;
proxy_->get_cached_property(old_value, name);
if (!old_value || !value.equal(old_value)) {
proxy_->set_cached_property(name, value);
if (update_pending_.count(name.raw())) {
setProperty(name, const_cast<Glib::VariantBase&>(value));
}
}
this->updateImage();
// this->event_box.set_tooltip_text(this->title);
} catch (const Glib::Error& err) {
spdlog::warn("Failed to update properties: {}", err.what());
} catch (const std::exception& err) {
spdlog::warn("Failed to update properties: {}", err.what());
}
update_pending_.clear();
}
/**
* Mapping from a signal name to a set of possibly changed properties.
* Commented signals are not handled by the tray module at the moment.
*/
static const std::map<std::string_view, std::set<std::string_view>> signal2props = {
{"NewTitle", {"Title"}},
{"NewIcon", {"IconName", "IconPixmap"}},
// {"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}},
// {"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}},
{"NewIconThemePath", {"IconThemePath"}},
{"NewToolTip", {"ToolTip"}},
{"NewStatus", {"Status"}},
// {"XAyatanaNewLabel", {"XAyatanaLabel"}},
};
void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
const Glib::VariantContainerBase& arguments) {
spdlog::trace("Tray item '{}' got signal {}", id, signal_name);
if (!update_pending_ && signal_name.compare(0, 3, "New") == 0) {
/* Debounce signals and schedule update of all properties.
* Based on behavior of Plasma dataengine for StatusNotifierItem.
*/
update_pending_ = true;
Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties),
UPDATE_DEBOUNCE_TIME);
auto changed = signal2props.find(signal_name.raw());
if (changed != signal2props.end()) {
if (update_pending_.empty()) {
/* Debounce signals and schedule update of all properties.
* Based on behavior of Plasma dataengine for StatusNotifierItem.
*/
Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties),
UPDATE_DEBOUNCE_TIME);
}
update_pending_.insert(changed->second.begin(), changed->second.end());
}
}
@@ -252,33 +310,42 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
}
void Item::updateImage() {
auto scale_factor = image.get_scale_factor();
auto scaled_icon_size = icon_size * scale_factor;
image.set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU);
image.set_pixel_size(icon_size);
image.set_pixel_size(scaled_icon_size);
if (!icon_name.empty()) {
try {
// Try to find icons specified by path and filename
std::ifstream temp(icon_name);
if (temp.is_open()) {
auto pixbuf = Gdk::Pixbuf::create_from_file(icon_name);
if (pixbuf->gobj() != nullptr) {
// An icon specified by path and filename may be the wrong size for
// the tray
// Keep the aspect ratio and scale to make the height equal to icon_size
// Keep the aspect ratio and scale to make the height equal to scaled_icon_size
// If people have non square icons, assume they want it to grow in width not height
int width = icon_size * pixbuf->get_width() / pixbuf->get_height();
int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();
pixbuf = pixbuf->scale_simple(width, icon_size, Gdk::InterpType::INTERP_BILINEAR);
image.set(pixbuf);
pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, 0, image.get_window());
image.set(surface);
}
} else {
image.set(getIconByName(icon_name, icon_size));
auto icon_by_name = getIconByName(icon_name, scaled_icon_size);
auto surface = Gdk::Cairo::create_surface_from_pixbuf(icon_by_name, 0, image.get_window());
image.set(surface);
}
} catch (Glib::Error& e) {
spdlog::error("Item '{}': {}", id, static_cast<std::string>(e.what()));
}
} else if (icon_pixmap) {
// An icon extracted may be the wrong size for the tray
icon_pixmap = icon_pixmap->scale_simple(icon_size, icon_size, Gdk::InterpType::INTERP_BILINEAR);
icon_pixmap = icon_pixmap->scale_simple(icon_size, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
auto surface = Gdk::Cairo::create_surface_from_pixbuf(icon_pixmap, 0, image.get_window());
image.set(icon_pixmap);
}
}
@@ -360,4 +427,52 @@ bool Item::handleClick(GdkEventButton* const& ev) {
return false;
}
bool Item::handleScroll(GdkEventScroll* const& ev) {
int dx = 0, dy = 0;
switch (ev->direction) {
case GDK_SCROLL_UP:
dy = -1;
break;
case GDK_SCROLL_DOWN:
dy = 1;
break;
case GDK_SCROLL_LEFT:
dx = -1;
break;
case GDK_SCROLL_RIGHT:
dx = 1;
break;
case GDK_SCROLL_SMOOTH:
distance_scrolled_x_ += ev->delta_x;
distance_scrolled_y_ += ev->delta_y;
// check against the configured threshold and ensure that the absolute value >= 1
if (distance_scrolled_x_ > scroll_threshold_) {
dx = (int)lround(std::max(distance_scrolled_x_, 1.0));
distance_scrolled_x_ = 0;
} else if (distance_scrolled_x_ < -scroll_threshold_) {
dx = (int)lround(std::min(distance_scrolled_x_, -1.0));
distance_scrolled_x_ = 0;
}
if (distance_scrolled_y_ > scroll_threshold_) {
dy = (int)lround(std::max(distance_scrolled_y_, 1.0));
distance_scrolled_y_ = 0;
} else if (distance_scrolled_y_ < -scroll_threshold_) {
dy = (int)lround(std::min(distance_scrolled_y_, -1.0));
distance_scrolled_y_ = 0;
}
break;
}
if (dx != 0) {
auto parameters = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<int>::create(dx), Glib::Variant<Glib::ustring>::create("horizontal")});
proxy_->call("Scroll", parameters);
}
if (dy != 0) {
auto parameters = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<int>::create(dy), Glib::Variant<Glib::ustring>::create("vertical")});
proxy_->call("Scroll", parameters);
}
return true;
}
} // namespace waybar::modules::SNI

View File

@@ -1,10 +1,28 @@
#include "modules/sway/language.hpp"
#include <fmt/core.h>
#include <json/json.h>
#include <spdlog/spdlog.h>
#include <xkbcommon/xkbregistry.h>
#include <cstring>
#include <string>
#include <vector>
#include "modules/sway/ipc/ipc.hpp"
#include "util/string.hpp"
namespace waybar::modules::sway {
const std::string Language::XKB_LAYOUT_NAMES_KEY = "xkb_layout_names";
const std::string Language::XKB_ACTIVE_LAYOUT_NAME_KEY = "xkb_active_layout_name";
Language::Language(const std::string& id, const Json::Value& config)
: ALabel(config, "language", id, "{}", 0, true) {
is_variant_displayed = format_.find("{variant}") != std::string::npos;
if (config.isMember("tooltip-format")) {
tooltip_format_ = config["tooltip-format"].asString();
}
ipc_.subscribe(R"(["input"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent));
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd));
@@ -21,18 +39,31 @@ Language::Language(const std::string& id, const Json::Value& config)
}
void Language::onCmd(const struct Ipc::ipc_response& res) {
if (res.type != static_cast<uint32_t>(IPC_GET_INPUTS)) {
return;
}
try {
auto payload = parser_.parse(res.payload);
//Display current layout of a device with a maximum count of layouts, expecting that all will be OK
Json::Value::ArrayIndex maxId = 0, max = 0;
for(Json::Value::ArrayIndex i = 0; i < payload.size(); i++) {
if(payload[i]["xkb_layout_names"].size() > max) {
max = payload[i]["xkb_layout_names"].size();
maxId = i;
std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload);
std::vector<std::string> used_layouts;
// Display current layout of a device with a maximum count of layouts, expecting that all will
// be OK
Json::ArrayIndex max_id = 0, max = 0;
for (Json::ArrayIndex i = 0; i < payload.size(); i++) {
auto size = payload[i][XKB_LAYOUT_NAMES_KEY].size();
if (size > max) {
max = size;
max_id = i;
}
}
auto layout_name = payload[maxId]["xkb_active_layout_name"].asString().substr(0,2);
lang_ = Glib::Markup::escape_text(layout_name);
for (const auto& layout : payload[max_id][XKB_LAYOUT_NAMES_KEY]) {
used_layouts.push_back(layout.asString());
}
init_layouts_map(used_layouts);
set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
dp.emit();
} catch (const std::exception& e) {
spdlog::error("Language: {}", e.what());
@@ -40,12 +71,15 @@ void Language::onCmd(const struct Ipc::ipc_response& res) {
}
void Language::onEvent(const struct Ipc::ipc_response& res) {
if (res.type != static_cast<uint32_t>(IPC_EVENT_INPUT)) {
return;
}
try {
std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload)["input"];
auto payload = parser_.parse(res.payload)["input"];
if (payload["type"].asString() == "keyboard") {
auto layout_name = payload["xkb_active_layout_name"].asString().substr(0,2);
lang_ = Glib::Markup::escape_text(layout_name);
set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
}
dp.emit();
} catch (const std::exception& e) {
@@ -54,17 +88,102 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
}
auto Language::update() -> void {
if (lang_.empty()) {
event_box_.hide();
} else {
label_.set_markup(fmt::format(format_, lang_));
if (tooltipEnabled()) {
label_.set_tooltip_text(lang_);
}
event_box_.show();
auto display_layout = trim(fmt::format(format_,
fmt::arg("short", layout_.short_name),
fmt::arg("long", layout_.full_name),
fmt::arg("variant", layout_.variant)));
label_.set_markup(display_layout);
if (tooltipEnabled()) {
if (tooltip_format_ != "") {
auto tooltip_display_layout = trim(fmt::format(tooltip_format_,
fmt::arg("short", layout_.short_name),
fmt::arg("long", layout_.full_name),
fmt::arg("variant", layout_.variant)));
label_.set_tooltip_markup(tooltip_display_layout);
} else {
label_.set_tooltip_markup(display_layout);
}
}
event_box_.show();
// Call parent update
ALabel::update();
}
auto Language::set_current_layout(std::string current_layout) -> void {
layout_ = layouts_map_[current_layout];
}
auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> void {
std::map<std::string, std::vector<Layout*>> found_by_short_names;
auto layout = xkb_context_.next_layout();
for (; layout != nullptr; layout = xkb_context_.next_layout()) {
if (std::find(used_layouts.begin(), used_layouts.end(), layout->full_name) ==
used_layouts.end()) {
continue;
}
if (!is_variant_displayed) {
auto short_name = layout->short_name;
if (found_by_short_names.count(short_name) > 0) {
found_by_short_names[short_name].push_back(layout);
} else {
found_by_short_names[short_name] = {layout};
}
}
layouts_map_.emplace(layout->full_name, *layout);
}
if (is_variant_displayed || found_by_short_names.size() == 0) {
return;
}
std::map<std::string, int> short_name_to_number_map;
for (const auto& used_layout_name : used_layouts) {
auto used_layout = &layouts_map_.find(used_layout_name)->second;
auto layouts_with_same_name_list = found_by_short_names[used_layout->short_name];
spdlog::info("SIZE: " + std::to_string(layouts_with_same_name_list.size()));
if (layouts_with_same_name_list.size() < 2) {
continue;
}
if (short_name_to_number_map.count(used_layout->short_name) == 0) {
short_name_to_number_map[used_layout->short_name] = 1;
}
used_layout->short_name =
used_layout->short_name + std::to_string(short_name_to_number_map[used_layout->short_name]++);
}
}
Language::XKBContext::XKBContext() {
context_ = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
rxkb_context_include_path_append_default(context_);
rxkb_context_parse_default_ruleset(context_);
}
auto Language::XKBContext::next_layout() -> Layout* {
if (xkb_layout_ == nullptr) {
xkb_layout_ = rxkb_layout_first(context_);
} else {
xkb_layout_ = rxkb_layout_next(xkb_layout_);
}
if (xkb_layout_ == nullptr) {
return nullptr;
}
auto description = std::string(rxkb_layout_get_description(xkb_layout_));
auto name = std::string(rxkb_layout_get_name(xkb_layout_));
auto variant_ = rxkb_layout_get_variant(xkb_layout_);
std::string variant = variant_ == nullptr ? "" : std::string(variant_);
layout_ = new Layout{description, name, variant};
return layout_;
}
Language::XKBContext::~XKBContext() { rxkb_context_unref(context_); }
} // namespace waybar::modules::sway