diff --git a/README.md b/README.md index 587a5540..c5806e3b 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ libdbusmenu-gtk3 [Tray module] libmpdclient [MPD module] libsndio [sndio module] libevdev [KeyboardState module] +xkbregistry ``` **Build dependencies** @@ -101,7 +102,8 @@ sudo apt install \ libsigc++-2.0-dev \ libspdlog-dev \ libwayland-dev \ - scdoc + scdoc \ + libxkbregistry-dev ``` diff --git a/include/modules/network.hpp b/include/modules/network.hpp index c91b598b..7b8281b1 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -73,7 +73,8 @@ class Network : public ALabel { int cidr_; int32_t signal_strength_dbm_; uint8_t signal_strength_; - uint32_t frequency_; + std::string signal_strength_app_; + float frequency_; uint32_t route_priority; util::SleeperThread thread_; diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index 3fe032f8..71c9fd24 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -3,11 +3,13 @@ #include "AModule.hpp" #include "bar.hpp" #include "client.hpp" +#include "giomm/desktopappinfo.h" #include "util/json.hpp" #include #include #include +#include #include #include @@ -61,15 +63,18 @@ class Task Gtk::Image icon_; Gtk::Label text_before_; Gtk::Label text_after_; - bool button_visible_ = false; - bool ignored_ = false; + Glib::RefPtr app_info_; + bool button_visible_; + bool ignored_; - bool with_icon_; + bool with_icon_ = false; + bool with_name_ = false; std::string format_before_; std::string format_after_; std::string format_tooltip_; + std::string name_; std::string title_; std::string app_id_; uint32_t state_ = 0; @@ -77,6 +82,8 @@ class Task private: std::string repr() const; std::string state_string(bool = false) const; + void set_app_info_from_app_id_list(const std::string& app_id_list); + bool image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, Glib::RefPtr app_info, int size); void hide_if_ignored(); public: @@ -136,6 +143,7 @@ class Taskbar : public waybar::AModule std::vector> icon_themes_; std::unordered_set ignore_list_; + std::map app_ids_replace_map_; struct zwlr_foreign_toplevel_manager_v1 *manager_; struct wl_seat *seat_; @@ -158,8 +166,9 @@ class Taskbar : public waybar::AModule bool show_output(struct wl_output *) const; bool all_outputs() const; - std::vector> icon_themes() const; + const std::vector>& icon_themes() const; const std::unordered_set& ignore_list() const; + const std::map& app_ids_replace_map() const; }; } /* namespace waybar::modules::wlr */ diff --git a/man/waybar-wlr-taskbar.5.scd b/man/waybar-wlr-taskbar.5.scd index 6645cc24..40be7be4 100644 --- a/man/waybar-wlr-taskbar.5.scd +++ b/man/waybar-wlr-taskbar.5.scd @@ -72,10 +72,16 @@ Addressed by *wlr/taskbar* typeof: array ++ List of app_id/titles to be invisible. +*app_ids-mapping*: ++ + typeof: object ++ + Dictionary of app_id to be replaced with + # FORMAT REPLACEMENTS *{icon}*: The icon of the application. +*{title}*: The application name as in desktop file if appropriate desktop fils found, otherwise same as {app_id} + *{title}*: The title of the application. *{app_id}*: The app_id (== application name) of the application. @@ -105,7 +111,10 @@ Addressed by *wlr/taskbar* "on-click-middle": "close", "ignore-list": [ "Alacritty" - ] + ], + "app_ids-mapping": { + "firefoxdeveloperedition": "firefox-developer-edition" + } } ``` diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index c42f6eb8..249ca7e4 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -82,6 +82,12 @@ Also a minimal example configuration can be found on the at the bottom of this m default: *true* ++ Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar. +*fixed-center* ++ + typeof: bool ++ + default: *true* + Prefer fixed center position for the `modules-center` block. The center block will stay in the middle of the bar whenever possible. It can still be pushed around if other blocks need more space. + When false, the center block is centered in the space between the left and right block. + *passthrough* ++ typeof: bool ++ default: *false* ++ diff --git a/meson.build b/meson.build index cfe2d513..2c434266 100644 --- a/meson.build +++ b/meson.build @@ -295,7 +295,7 @@ executable( gtk_layer_shell, libsndio, tz_dep, - xkbregistry + xkbregistry ], include_directories: [include_directories('include')], install: true, diff --git a/src/bar.cpp b/src/bar.cpp index 133c29aa..9685c7a9 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -767,7 +767,11 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk auto waybar::Bar::setupWidgets() -> void { window.add(box_); box_.pack_start(left_, false, false); - box_.set_center_widget(center_); + if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) { + box_.set_center_widget(center_); + } else { + box_.pack_start(center_, true, false); + } box_.pack_end(right_, false, false); // Convert to button code for every module that is used. diff --git a/src/modules/network.cpp b/src/modules/network.cpp index b86989f3..4825a099 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -88,7 +88,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf #ifdef WANT_RFKILL rfkill_{RFKILL_TYPE_WLAN}, #endif - frequency_(0) { + frequency_(0.0) { // Start with some "text" in the module's label_, update() will then // update it. Since the text should be different, update() will be able @@ -331,12 +331,13 @@ auto waybar::modules::Network::update() -> void { fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), + fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), - fmt::arg("frequency", frequency_), + fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), @@ -360,12 +361,13 @@ auto waybar::modules::Network::update() -> void { fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), + fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), - fmt::arg("frequency", frequency_), + fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), @@ -403,7 +405,8 @@ void waybar::modules::Network::clearIface() { cidr_ = 0; signal_strength_dbm_ = 0; signal_strength_ = 0; - frequency_ = 0; + signal_strength_app_.clear(); + frequency_ = 0.0; } int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { @@ -470,7 +473,8 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { net->essid_.clear(); net->signal_strength_dbm_ = 0; net->signal_strength_ = 0; - net->frequency_ = 0; + net->signal_strength_app_.clear(); + net->frequency_ = 0.0; } } net->carrier_ = carrier.value(); @@ -788,13 +792,30 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) { if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) { // signalstrength in dBm from mBm signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100; - // WiFi-hardware usually operates in the range -90 to -30dBm. - const int hardwareMax = -30; + + // If a signal is too strong, it can overwhelm receiving circuity that is designed + // to pick up and process a certain signal level. The following percentage is scaled to + // punish signals that are too strong (>= -45dBm) or too weak (<= -45 dBm). + const int hardwareOptimum = -45; const int hardwareMin = -90; const int strength = - ((signal_strength_dbm_ - hardwareMin) / double{hardwareMax - hardwareMin}) * 100; - signal_strength_ = std::clamp(strength, 0, 100); + 100 - ((abs(signal_strength_dbm_ - hardwareOptimum) / double{hardwareOptimum - hardwareMin}) * 100); + signal_strength_ = std::clamp(strength, 0, 100); + + if (signal_strength_dbm_ >= -50) { + signal_strength_app_ = "Great Connectivity"; + } else if (signal_strength_dbm_ >= -60) { + signal_strength_app_ = "Good Connectivity"; + } else if (signal_strength_dbm_ >= -67) { + signal_strength_app_ = "Streaming"; + } else if (signal_strength_dbm_ >= -70) { + signal_strength_app_ = "Web Surfing"; + } else if (signal_strength_dbm_ >= -80) { + signal_strength_app_ = "Basic Connectivity"; + } else { + signal_strength_app_ = "Poor Connectivity"; + } } if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) { signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]); @@ -803,8 +824,8 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) { void waybar::modules::Network::parseFreq(struct nlattr **bss) { if (bss[NL80211_BSS_FREQUENCY] != nullptr) { - // in MHz - frequency_ = nla_get_u32(bss[NL80211_BSS_FREQUENCY]); + // in GHz + frequency_ = (double) nla_get_u32(bss[NL80211_BSS_FREQUENCY]) / 1000; } } diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index af1b0da0..ddc360bb 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -11,7 +11,9 @@ #include #include #include +#include +#include #include #include @@ -86,8 +88,7 @@ static Glib::RefPtr load_icon_from_file(std::string icon_path, int } } -/* Method 1 - get the correct icon name from the desktop file */ -static std::string get_from_desktop_app_info(const std::string &app_id) +static Glib::RefPtr get_app_info_by_name(const std::string& app_id) { static std::vector prefixes = search_prefix(); @@ -103,33 +104,29 @@ static std::string get_from_desktop_app_info(const std::string &app_id) ".desktop" }; - Glib::RefPtr app_info; + for (auto& prefix : prefixes) { + for (auto& folder : app_folders) { + for (auto& suffix : suffixes) { + auto app_info_ = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix); + if (!app_info_) { + continue; + } - for (auto& prefix : prefixes) - for (auto& folder : app_folders) - for (auto& suffix : suffixes) - if (!app_info) - app_info = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix); + return app_info_; + } + } + } - if (app_info && app_info->get_icon()) - return app_info->get_icon()->to_string(); - - return ""; + return {}; } -/* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */ -static std::string get_from_icon_theme(const Glib::RefPtr& icon_theme, - const std::string &app_id) +Glib::RefPtr get_desktop_app_info(const std::string &app_id) { - if (icon_theme->lookup_icon(app_id, 24)) - return app_id; + auto app_info = get_app_info_by_name(app_id); + if (app_info) { + return app_info; + } - return ""; -} - -/* Method 3 - as last resort perform a search for most appropriate desktop info file */ -static std::string get_from_desktop_app_info_search(const std::string &app_id) -{ std::string desktop_file = ""; gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str()); @@ -151,65 +148,84 @@ static std::string get_from_desktop_app_info_search(const std::string &app_id) } g_free(desktop_list); - return get_from_desktop_app_info(desktop_file); + return get_app_info_by_name(desktop_file); } -static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, - const std::string &app_id_list, int size) -{ +void Task::set_app_info_from_app_id_list(const std::string& app_id_list) { std::string app_id; std::istringstream stream(app_id_list); - bool found = false; /* Wayfire sends a list of app-id's in space separated format, other compositors * send a single app-id, but in any case this works fine */ while (stream >> app_id) { + app_info_ = get_desktop_app_info(app_id); + if (app_info_) { + return; + } + + auto lower_app_id = app_id; + std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(), + [](char c){ return std::tolower(c); }); + app_info_ = get_desktop_app_info(lower_app_id); + if (app_info_) { + return; + } + size_t start = 0, end = app_id.size(); start = app_id.rfind(".", end); std::string app_name = app_id.substr(start+1, app_id.size()); + app_info_ = get_desktop_app_info(app_name); + if (app_info_) { + return; + } - auto lower_app_id = app_id; - std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(), - [](char c){ return std::tolower(c); }); + start = app_id.find("-"); + app_name = app_id.substr(0, start); + app_info_ = get_desktop_app_info(app_name); + } +} - std::string icon_name = get_from_icon_theme(icon_theme, app_id); +static std::string get_icon_name_from_icon_theme(const Glib::RefPtr& icon_theme, + const std::string &app_id) +{ + if (icon_theme->lookup_icon(app_id, 24)) + return app_id; - if (icon_name.empty()) - icon_name = get_from_icon_theme(icon_theme, lower_app_id); - if (icon_name.empty()) - icon_name = get_from_icon_theme(icon_theme, app_name); - if (icon_name.empty()) - icon_name = get_from_desktop_app_info(app_id); - if (icon_name.empty()) - icon_name = get_from_desktop_app_info(lower_app_id); - if (icon_name.empty()) - icon_name = get_from_desktop_app_info(app_name); - if (icon_name.empty()) - icon_name = get_from_desktop_app_info_search(app_id); + return ""; +} - if (icon_name.empty()) - icon_name = "unknown"; +bool Task::image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, Glib::RefPtr app_info, int size) +{ + std::string ret_icon_name = "unknown"; + if (app_info) { + std::string icon_name = get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class()); + if (!icon_name.empty()) { + ret_icon_name = icon_name; + } else { + if (app_info->get_icon()) { + ret_icon_name = app_info->get_icon()->to_string(); + } + } + } - Glib::RefPtr pixbuf; + Glib::RefPtr pixbuf; - try { - pixbuf = icon_theme->load_icon(icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE); - } catch(...) { - if (Glib::file_test(icon_name, Glib::FILE_TEST_EXISTS)) - pixbuf = load_icon_from_file(icon_name, size); - else - pixbuf = {}; - } + try { + pixbuf = icon_theme->load_icon(ret_icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE); + } catch(...) { + if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS)) + pixbuf = load_icon_from_file(ret_icon_name, size); + else + pixbuf = {}; + } - if (pixbuf) { - image.set(pixbuf); - found = true; - break; - } - } + if (pixbuf) { + image.set(pixbuf); + return true; + } - return found; + return false; } /* Task class implementation */ @@ -289,13 +305,15 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, content_.show(); button_.add(content_); - with_icon_ = false; format_before_.clear(); format_after_.clear(); if (config_["format"].isString()) { /* The user defined a format string, use it */ auto format = config_["format"].asString(); + if (format.find("{name}") != std::string::npos) { + with_name_ = true; + } auto icon_pos = format.find("{icon}"); if (icon_pos == 0) { @@ -402,13 +420,28 @@ void Task::handle_app_id(const char *app_id) app_id_ = app_id; hide_if_ignored(); - if (!with_icon_) + auto ids_replace_map = tbar_->app_ids_replace_map(); + if (ids_replace_map.count(app_id_)) { + auto replaced_id = ids_replace_map[app_id_]; + spdlog::debug(fmt::format("Task ({}) [{}] app_id was replaced with {}", id_, app_id_, replaced_id)); + app_id_ = replaced_id; + } + + if (!with_icon_ && !with_name_) { return; + } + + set_app_info_from_app_id_list(app_id_); + name_ = app_info_ ? app_info_->get_display_name() : app_id; + + if (!with_icon_) { + return; + } int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16; bool found = false; for (auto& icon_theme : tbar_->icon_themes()) { - if (image_load_icon(icon_, icon_theme, app_id_, icon_size)) { + if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) { found = true; break; } @@ -564,14 +597,17 @@ void Task::update() { bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false; std::string title = title_; + std::string name = name_; std::string app_id = app_id_; if (markup) { title = Glib::Markup::escape_text(title); + name = Glib::Markup::escape_text(name); app_id = Glib::Markup::escape_text(app_id); } if (!format_before_.empty()) { auto txt = fmt::format(format_before_, fmt::arg("title", title), + fmt::arg("name", name), fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("short_state", state_string(true)) @@ -585,6 +621,7 @@ void Task::update() if (!format_after_.empty()) { auto txt = fmt::format(format_after_, fmt::arg("title", title), + fmt::arg("name", name), fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("short_state", state_string(true)) @@ -599,6 +636,7 @@ void Task::update() if (!format_tooltip_.empty()) { auto txt = fmt::format(format_tooltip_, fmt::arg("title", title), + fmt::arg("name", name), fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("short_state", state_string(true)) @@ -726,6 +764,15 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu } } + // Load app_id remappings + if (config_["app_ids-mapping"].isObject()) { + const Json::Value& mapping = config_["app_ids-mapping"]; + const std::vector app_ids = config_["app_ids-mapping"].getMemberNames(); + for (auto& app_id : app_ids) { + app_ids_replace_map_.emplace(app_id, mapping[app_id].asString()); + } + } + icon_themes_.push_back(Gtk::IconTheme::get_default()); } @@ -857,10 +904,10 @@ bool Taskbar::all_outputs() const return config_["all-outputs"].isBool() && config_["all-outputs"].asBool(); } -std::vector> Taskbar::icon_themes() const -{ - return icon_themes_; -} -const std::unordered_set &Taskbar::ignore_list() const { return ignore_list_; } +const std::vector>& Taskbar::icon_themes() const { return icon_themes_; } + +const std::unordered_set& Taskbar::ignore_list() const { return ignore_list_; } + +const std::map& Taskbar::app_ids_replace_map() const { return app_ids_replace_map_; } } /* namespace waybar::modules::wlr */