From a53c97f7f6c34dadf4ba360c6baad6efe8227f8f Mon Sep 17 00:00:00 2001 From: chayleaf Date: Thu, 16 Feb 2023 19:41:24 +0700 Subject: [PATCH 1/6] mpris: add tooltip and length limits --- include/modules/mpris/mpris.hpp | 18 +++ man/waybar-mpd.5.scd | 24 ++-- man/waybar-mpris.5.scd | 46 +++++++- src/modules/mpris/mpris.cpp | 198 ++++++++++++++++++++++++++++---- 4 files changed, 251 insertions(+), 35 deletions(-) diff --git a/include/modules/mpris/mpris.hpp b/include/modules/mpris/mpris.hpp index e82c9295..30c747a7 100644 --- a/include/modules/mpris/mpris.hpp +++ b/include/modules/mpris/mpris.hpp @@ -44,6 +44,11 @@ class Mpris : public AModule { auto getPlayerInfo() -> std::optional; auto getIcon(const Json::Value&, const std::string&) -> std::string; + auto getArtistStr(const PlayerInfo&, bool) -> std::string; + auto getAlbumStr(const PlayerInfo&, bool) -> std::string; + auto getTitleStr(const PlayerInfo&, bool) -> std::string; + auto getLengthStr(const PlayerInfo&, bool) -> std::string; + auto getDynamicStr(const PlayerInfo&, bool, bool) -> std::string; Gtk::Box box_; Gtk::Label label_; @@ -53,6 +58,19 @@ class Mpris : public AModule { std::string format_playing_; std::string format_paused_; std::string format_stopped_; + + std::string tooltip_; + std::string tooltip_playing_; + std::string tooltip_paused_; + std::string tooltip_stopped_; + + int artist_len_; + int album_len_; + int title_len_; + int dynamic_len_; + std::vector dynamic_prio_; + bool tooltip_len_limits_; + std::chrono::seconds interval_; std::string player_; std::vector ignored_players_; diff --git a/man/waybar-mpd.5.scd b/man/waybar-mpd.5.scd index 044af98e..1dde8f79 100644 --- a/man/waybar-mpd.5.scd +++ b/man/waybar-mpd.5.scd @@ -74,20 +74,20 @@ Addressed by *mpd* Tooltip information displayed when the MPD server can't be reached. *artist-len*: ++ - typeof: integer ++ - Maximum length of the Artist tag. + typeof: integer ++ + Maximum length of the Artist tag. *album-len*: ++ - typeof: integer ++ - Maximum length of the Album tag. + typeof: integer ++ + Maximum length of the Album tag. *album-artist-len*: ++ - typeof: integer ++ - Maximum length of the Album Artist tag. + typeof: integer ++ + Maximum length of the Album Artist tag. *title-len*: ++ - typeof: integer ++ - Maximum length of the Title tag. + typeof: integer ++ + Maximum length of the Title tag. *rotate*: ++ typeof: integer ++ @@ -98,12 +98,12 @@ Addressed by *mpd* The maximum length in character the module should display. *min-length*: ++ - typeof: integer ++ - The minimum length in characters the module should take up. + typeof: integer ++ + The minimum length in characters the module should take up. *align*: ++ - typeof: float ++ - The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. + typeof: float ++ + The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *on-click*: ++ typeof: string ++ diff --git a/man/waybar-mpris.5.scd b/man/waybar-mpris.5.scd index d2a72d98..b2d4097c 100644 --- a/man/waybar-mpris.5.scd +++ b/man/waybar-mpris.5.scd @@ -33,6 +33,48 @@ The *mpris* module displays currently playing media via libplayerctl. typeof: string ++ The status-specific text format. +*tooltip*: ++ + typeof: bool ++ + default: true ++ + Option to disable tooltip on hover. + +*tooltip-format*: ++ + typeof: string ++ + default: {player} ({status}) {dynamic} ++ + The tooltip text format. + +*tooltip-format-[status]*: ++ + typeof: string ++ + default: "MPD (disconnected)" ++ + The status-specif tooltip format. + +*artist-len*: ++ + typeof: integer ++ + Maximum length of the Artist tag. + +*album-len*: ++ + typeof: integer ++ + Maximum length of the Album tag. + +*title-len*: ++ + typeof: integer ++ + Maximum length of the Title tag. + +*dynamic-len*: ++ + typeof: integer ++ + Maximum length of the Dynamic tag. + +*dynamic-priority* ++ + typeof: []string ++ + default: ["title", "length", "artist", "album"] + Priority of the tags when truncating the Dynamic tag (absence in this + list means force inclusion). + +*enable-tooltip-len-limits*: ++ + typeof: bool ++ + default: false ++ + Option to enable the limits for the tooltip as well + *on-click*: ++ typeof: string ++ default: play-pause ++ @@ -83,8 +125,8 @@ The *mpris* module displays currently playing media via libplayerctl. ``` "mpris": { - "format": "DEFAULT: {player_icon} {dynamic}", - "format-paused": "DEFAULT: {status_icon} {dynamic}", + "format": "{player_icon} {dynamic}", + "format-paused": "{status_icon} {dynamic}", "player-icons": { "default": "▶", "mpv": "🎵" diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index f11821fc..d0883459 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -21,6 +21,13 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) box_(Gtk::ORIENTATION_HORIZONTAL, 0), label_(), format_(DEFAULT_FORMAT), + tooltip_(DEFAULT_FORMAT), + artist_len_(-1), + album_len_(-1), + title_len_(-1), + dynamic_len_(-1), + dynamic_prio_({"title", "length", "artist", "album"}), + tooltip_len_limits_(false), interval_(0), player_("playerctld"), manager(), @@ -42,6 +49,46 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) if (config_["format-stopped"].isString()) { format_stopped_ = config_["format-stopped"].asString(); } + if (tooltipEnabled()) { + if (config_["tooltip-format"].isString()) { + tooltip_ = config_["tooltip-format"].asString(); + } + if (config_["tooltip-format-playing"].isString()) { + tooltip_playing_ = config_["tooltip-format-playing"].asString(); + } + if (config_["tooltip-format-paused"].isString()) { + tooltip_paused_ = config_["tooltip-format-paused"].asString(); + } + if (config_["tooltip-format-stopped"].isString()) { + tooltip_stopped_ = config_["tooltip-format-stopped"].asString(); + } + if (config_["enable-tooltip-len-limits"].isBool()) { + tooltip_len_limits_ = config["enable-tooltip-len-limits"].asBool(); + } + } + + if (config["artist-len"].isUInt()) { + artist_len_ = config["artist-len"].asUInt(); + } + if (config["album-len"].isUInt()) { + album_len_ = config["album-len"].asUInt(); + } + if (config["title-len"].isUInt()) { + title_len_ = config["title-len"].asUInt(); + } + if (config["dynamic-len"].isUInt()) { + dynamic_len_ = config["dynamic-len"].asUInt(); + } + if (config_["dynamic-priority"].isArray()) { + dynamic_prio_.clear(); + for (auto it = config_["dynamic-priority"].begin(); it != config_["dynamic-priority"].end(); + ++it) { + if (it->isString()) { + dynamic_prio_.push_back(it->asString()); + } + } + } + if (config_["interval"].isUInt()) { interval_ = std::chrono::seconds(config_["interval"].asUInt()); } @@ -51,7 +98,9 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) if (config_["ignored-players"].isArray()) { for (auto it = config_["ignored-players"].begin(); it != config_["ignored-players"].end(); ++it) { - ignored_players_.push_back(it->asString()); + if (it->isString()) { + ignored_players_.push_back(it->asString()); + } } } @@ -129,6 +178,95 @@ auto Mpris::getIcon(const Json::Value& icons, const std::string& key) -> std::st return ""; } +auto Mpris::getArtistStr(const PlayerInfo &info, bool truncated) -> std::string { + std::string artist = info.artist.value_or(std::string()); + return (truncated && artist_len_ >= 0) ? artist.substr(0, artist_len_) : artist; +} + +auto Mpris::getAlbumStr(const PlayerInfo &info, bool truncated) -> std::string { + std::string album = info.album.value_or(std::string()); + return (truncated && album_len_ >= 0) ? album.substr(0, album_len_) : album; +} + +auto Mpris::getTitleStr(const PlayerInfo &info, bool truncated) -> std::string { + std::string title = info.title.value_or(std::string()); + return (truncated && title_len_ >= 0) ? title.substr(0, title_len_) : title; +} + +auto Mpris::getLengthStr(const PlayerInfo &info, bool from_dynamic) -> std::string { + if (info.length.has_value()) { + return from_dynamic ? ("[" + info.length.value() + "]") : info.length.value(); + } + return std::string(); +} + +auto Mpris::getDynamicStr(const PlayerInfo &info, bool truncated, bool html) -> std::string { + std::string artist = getArtistStr(info, truncated); + std::string album = getAlbumStr(info, truncated); + std::string title = getTitleStr(info, truncated); + std::string length = getLengthStr(info, true); + + std::stringstream dynamic; + bool showArtist, showAlbum, showTitle, showLength; + showArtist = artist.length() != 0; + showAlbum = album.length() != 0; + showTitle = title.length() != 0; + showLength = length.length() != 0; + + if (truncated && dynamic_len_ >= 0) { + size_t dynamicLen = dynamic_len_; + size_t artistLen = showArtist ? artist.length() + 3 : 0; + size_t albumLen = showAlbum ? album.length() + 3 : 0; + size_t titleLen = title.length(); + size_t lengthLen = showLength ? length.length() + 1 : 0; + + size_t totalLen = 0; + + for (auto it = dynamic_prio_.begin(); it != dynamic_prio_.end(); ++it) { + if (*it == "artist") { + if (totalLen + artistLen > dynamicLen) { + showArtist = false; + } else { + totalLen += artistLen; + } + } else if (*it == "album") { + if (totalLen + albumLen > dynamicLen) { + showAlbum = false; + } else { + totalLen += albumLen; + } + } else if (*it == "title") { + if (totalLen + titleLen > dynamicLen) { + showTitle = false; + } else { + totalLen += titleLen; + } + } else if (*it == "length") { + if (totalLen + lengthLen > dynamicLen) { + showLength = false; + } else { + totalLen += lengthLen; + } + } + } + } + + if (showArtist) dynamic << artist << " - "; + if (showAlbum) dynamic << album << " - "; + if (showTitle) dynamic << title; + if (showLength) { + dynamic << " "; + if (html) { + dynamic << ""; + } + dynamic << length; + if (html) { + dynamic << ""; + } + } + return dynamic.str(); +} + auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name, gpointer data) -> void { Mpris* mpris = static_cast(data); @@ -335,18 +473,6 @@ auto Mpris::update() -> void { spdlog::debug("mpris[{}]: running update", info.name); - // dynamic is the auto-formatted string containing a nice out-of-the-box - // format text - std::stringstream dynamic; - if (info.artist) dynamic << *info.artist << " - "; - if (info.album) dynamic << *info.album << " - "; - if (info.title) dynamic << *info.title; - if (info.length) - dynamic << " " - << "" - << "[" << *info.length << "]" - << ""; - // set css class for player status if (!lastStatus.empty() && box_.get_style_context()->has_class(lastStatus)) { box_.get_style_context()->remove_class(lastStatus); @@ -366,25 +492,55 @@ auto Mpris::update() -> void { lastPlayer = info.name; auto formatstr = format_; + auto tooltipstr = tooltip_; switch (info.status) { case PLAYERCTL_PLAYBACK_STATUS_PLAYING: if (!format_playing_.empty()) formatstr = format_playing_; + if (!tooltip_playing_.empty()) tooltipstr = tooltip_playing_; break; case PLAYERCTL_PLAYBACK_STATUS_PAUSED: if (!format_paused_.empty()) formatstr = format_paused_; + if (!tooltip_paused_.empty()) tooltipstr = tooltip_paused_; break; case PLAYERCTL_PLAYBACK_STATUS_STOPPED: if (!format_stopped_.empty()) formatstr = format_stopped_; + if (!tooltip_stopped_.empty()) tooltipstr = tooltip_stopped_; break; } - auto label_format = - fmt::format(fmt::runtime(formatstr), fmt::arg("player", info.name), - fmt::arg("status", info.status_string), fmt::arg("artist", *info.artist), - fmt::arg("title", *info.title), fmt::arg("album", *info.album), - fmt::arg("length", *info.length), fmt::arg("dynamic", dynamic.str()), - fmt::arg("player_icon", getIcon(config_["player-icons"], info.name)), - fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string))); - label_.set_markup(label_format); + + try { + auto label_format = fmt::format( + fmt::runtime(formatstr), fmt::arg("player", info.name), + fmt::arg("status", info.status_string), fmt::arg("artist", getArtistStr(info, true)), + fmt::arg("title", getTitleStr(info, true)), fmt::arg("album", getAlbumStr(info, true)), + fmt::arg("length", getLengthStr(info, false)), + fmt::arg("dynamic", getDynamicStr(info, true, true)), + fmt::arg("player_icon", getIcon(config_["player-icons"], info.name)), + fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string))); + + label_.set_markup(label_format); + } catch (fmt::format_error const& e) { + spdlog::warn("mpris: format error: {}", e.what()); + } + + if (tooltipEnabled()) { + try { + auto tooltip_text = fmt::format( + fmt::runtime(tooltipstr), fmt::arg("player", info.name), + fmt::arg("status", info.status_string), + fmt::arg("artist", getArtistStr(info, tooltip_len_limits_)), + fmt::arg("title", getTitleStr(info, tooltip_len_limits_)), + fmt::arg("album", getAlbumStr(info, tooltip_len_limits_)), + fmt::arg("length", getLengthStr(info, false)), + fmt::arg("dynamic", getDynamicStr(info, tooltip_len_limits_, false)), + fmt::arg("player_icon", getIcon(config_["player-icons"], info.name)), + fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string))); + + label_.set_tooltip_text(tooltip_text); + } catch (fmt::format_error const& e) { + spdlog::warn("mpris: format error (tooltip): {}", e.what()); + } + } event_box_.set_visible(true); // call parent update From 5383f7bd567fbb02dd86c37907e6bf2a652d41ab Mon Sep 17 00:00:00 2001 From: chayleaf Date: Fri, 17 Feb 2023 03:41:38 +0700 Subject: [PATCH 2/6] mpris: add unicode support; add position tag --- include/modules/mpris/mpris.hpp | 6 +- man/waybar-mpris.5.scd | 30 +++++- src/modules/mpris/mpris.cpp | 178 ++++++++++++++++++++++++++------ 3 files changed, 176 insertions(+), 38 deletions(-) diff --git a/include/modules/mpris/mpris.hpp b/include/modules/mpris/mpris.hpp index 30c747a7..ddb0cb67 100644 --- a/include/modules/mpris/mpris.hpp +++ b/include/modules/mpris/mpris.hpp @@ -39,7 +39,8 @@ class Mpris : public AModule { std::optional artist; std::optional album; std::optional title; - std::optional length; // as HH:MM:SS + std::optional length; // as HH:MM:SS + std::optional position; // same format }; auto getPlayerInfo() -> std::optional; @@ -48,6 +49,7 @@ class Mpris : public AModule { auto getAlbumStr(const PlayerInfo&, bool) -> std::string; auto getTitleStr(const PlayerInfo&, bool) -> std::string; auto getLengthStr(const PlayerInfo&, bool) -> std::string; + auto getPositionStr(const PlayerInfo&, bool) -> std::string; auto getDynamicStr(const PlayerInfo&, bool, bool) -> std::string; Gtk::Box box_; @@ -69,7 +71,9 @@ class Mpris : public AModule { int title_len_; int dynamic_len_; std::vector dynamic_prio_; + bool truncate_hours_; bool tooltip_len_limits_; + std::string ellipsis_; std::chrono::seconds interval_; std::string player_; diff --git a/man/waybar-mpris.5.scd b/man/waybar-mpris.5.scd index b2d4097c..bc735b93 100644 --- a/man/waybar-mpris.5.scd +++ b/man/waybar-mpris.5.scd @@ -50,19 +50,23 @@ The *mpris* module displays currently playing media via libplayerctl. *artist-len*: ++ typeof: integer ++ - Maximum length of the Artist tag. + Maximum length of the Artist tag (Wide/Fullwidth Unicode characters + count as two). *album-len*: ++ typeof: integer ++ - Maximum length of the Album tag. + Maximum length of the Album tag (Wide/Fullwidth Unicode characters count + as two). *title-len*: ++ typeof: integer ++ - Maximum length of the Title tag. + Maximum length of the Title tag (Wide/Fullwidth Unicode characters count + as two). *dynamic-len*: ++ typeof: integer ++ - Maximum length of the Dynamic tag. + Maximum length of the Dynamic tag (Wide/Fullwidth Unicode characters + count as two). *dynamic-priority* ++ typeof: []string ++ @@ -70,10 +74,26 @@ The *mpris* module displays currently playing media via libplayerctl. Priority of the tags when truncating the Dynamic tag (absence in this list means force inclusion). +*truncate-hours*: ++ + typeof: bool ++ + default: true ++ + Whether to truncate hours when media duration is less than an hour long + *enable-tooltip-len-limits*: ++ typeof: bool ++ default: false ++ - Option to enable the limits for the tooltip as well + Option to enable the length limits for the tooltip as well + +*ellipsis*: ++ + typeof: string ++ + default: "…" ++ + Override the default ellipsis (set to empty string to simply truncate + the tags when needed instead). + +*on-click*: ++ + typeof: string ++ + default: play-pause ++ + Overwrite default action toggles. *on-click*: ++ typeof: string ++ diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index d0883459..6e8004d6 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -1,15 +1,16 @@ -#include "modules/mpris/mpris.hpp" - #include #include #include #include +#include "modules/mpris/mpris.hpp" + extern "C" { #include } +#include #include namespace waybar::modules::mpris { @@ -26,8 +27,11 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) album_len_(-1), title_len_(-1), dynamic_len_(-1), - dynamic_prio_({"title", "length", "artist", "album"}), + dynamic_prio_({"title", "length", "position", "artist", "album"}), + truncate_hours_(true), tooltip_len_limits_(false), + // this character is used in Gnome so it's fine to use it here + ellipsis_(u8"\u2026"), interval_(0), player_("playerctld"), manager(), @@ -49,6 +53,9 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) if (config_["format-stopped"].isString()) { format_stopped_ = config_["format-stopped"].asString(); } + if (config_["ellipsis"].isString()) { + ellipsis_ = config_["ellipsis"].asString(); + } if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { tooltip_ = config_["tooltip-format"].asString(); @@ -89,6 +96,9 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) } } + if (config_["truncate-hours"].isBool()) { + truncate_hours_ = config["truncate-hours"].asBool(); + } if (config_["interval"].isUInt()) { interval_ = std::chrono::seconds(config_["interval"].asUInt()); } @@ -178,47 +188,116 @@ auto Mpris::getIcon(const Json::Value& icons, const std::string& key) -> std::st return ""; } -auto Mpris::getArtistStr(const PlayerInfo &info, bool truncated) -> std::string { +// Wide characters count as two, zero-width characters count as zero +// Modifies str in-place (unless width = std::string::npos) +// Returns the total width of the string pre-truncating +size_t utf8_truncate(std::string& str, size_t width = std::string::npos) { + if (str.length() == 0) return 0; + + const gchar* trunc_end = nullptr; + + size_t total_width = 0; + + for (gchar *data = str.data(), *end = data + str.size(); data;) { + gunichar c = g_utf8_get_char_validated(data, end - data); + if (c == -1 || c == -2) { + // invalid unicode, treat string as ascii + if (width != std::string::npos && str.length() > width) str.resize(width); + return str.length(); + } else if (g_unichar_iswide(c)) { + total_width += 2; + } else if (!g_unichar_iszerowidth(c)) { + total_width += 1; + } + + data = g_utf8_find_next_char(data, end); + if (width != std::string::npos && total_width <= width) trunc_end = data; + } + + if (trunc_end) str.resize(trunc_end - str.data()); + + return total_width; +} + +size_t utf8_width(const std::string& str) { return utf8_truncate(const_cast(str)); } + +void truncate(std::string& s, const std::string& ellipsis, size_t max_len) { + if (max_len == 0) { + s.resize(0); + return; + } + size_t len = utf8_truncate(s, max_len); + if (len > max_len) { + size_t ellipsis_len = utf8_width(ellipsis); + if (max_len >= ellipsis_len) { + if (ellipsis_len) utf8_truncate(s, max_len - ellipsis_len); + s += ellipsis; + } else { + s.resize(0); + } + } +} + +auto Mpris::getArtistStr(const PlayerInfo& info, bool truncated) -> std::string { std::string artist = info.artist.value_or(std::string()); - return (truncated && artist_len_ >= 0) ? artist.substr(0, artist_len_) : artist; + if (truncated && artist_len_ >= 0) truncate(artist, ellipsis_, artist_len_); + return artist; } -auto Mpris::getAlbumStr(const PlayerInfo &info, bool truncated) -> std::string { +auto Mpris::getAlbumStr(const PlayerInfo& info, bool truncated) -> std::string { std::string album = info.album.value_or(std::string()); - return (truncated && album_len_ >= 0) ? album.substr(0, album_len_) : album; + if (truncated && album_len_ >= 0) truncate(album, ellipsis_, album_len_); + return album; } -auto Mpris::getTitleStr(const PlayerInfo &info, bool truncated) -> std::string { +auto Mpris::getTitleStr(const PlayerInfo& info, bool truncated) -> std::string { std::string title = info.title.value_or(std::string()); - return (truncated && title_len_ >= 0) ? title.substr(0, title_len_) : title; + if (truncated && title_len_ >= 0) truncate(title, ellipsis_, title_len_); + return title; } -auto Mpris::getLengthStr(const PlayerInfo &info, bool from_dynamic) -> std::string { +auto Mpris::getLengthStr(const PlayerInfo& info, bool truncated) -> std::string { if (info.length.has_value()) { - return from_dynamic ? ("[" + info.length.value() + "]") : info.length.value(); + std::string length = info.length.value(); + return (truncated && length.substr(0, 3) == "00:") ? length.substr(3) : length; } return std::string(); } -auto Mpris::getDynamicStr(const PlayerInfo &info, bool truncated, bool html) -> std::string { +auto Mpris::getPositionStr(const PlayerInfo& info, bool truncated) -> std::string { + if (info.position.has_value()) { + std::string position = info.position.value(); + return (truncated && position.substr(0, 3) == "00:") ? position.substr(3) : position; + } + return std::string(); +} + +auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) -> std::string { std::string artist = getArtistStr(info, truncated); std::string album = getAlbumStr(info, truncated); std::string title = getTitleStr(info, truncated); - std::string length = getLengthStr(info, true); + std::string length = getLengthStr(info, truncated && truncate_hours_); + // keep position format same as length format + std::string position = getPositionStr(info, truncated && truncate_hours_ && length.length() < 6); - std::stringstream dynamic; - bool showArtist, showAlbum, showTitle, showLength; - showArtist = artist.length() != 0; - showAlbum = album.length() != 0; - showTitle = title.length() != 0; - showLength = length.length() != 0; + size_t artistLen = utf8_width(artist); + size_t albumLen = utf8_width(album); + size_t titleLen = utf8_width(title); + size_t lengthLen = length.length(); + size_t posLen = position.length(); + + bool showArtist = artistLen != 0; + bool showAlbum = albumLen != 0; + bool showTitle = titleLen != 0; + bool showLength = lengthLen != 0; + bool showPos = posLen != 0; if (truncated && dynamic_len_ >= 0) { size_t dynamicLen = dynamic_len_; - size_t artistLen = showArtist ? artist.length() + 3 : 0; - size_t albumLen = showAlbum ? album.length() + 3 : 0; - size_t titleLen = title.length(); - size_t lengthLen = showLength ? length.length() + 1 : 0; + if (showArtist) artistLen += 3; + if (showAlbum) albumLen += 3; + if (showLength) lengthLen += 3; + if (showPos) posLen += 3; size_t totalLen = 0; @@ -246,23 +325,34 @@ auto Mpris::getDynamicStr(const PlayerInfo &info, bool truncated, bool html) -> showLength = false; } else { totalLen += lengthLen; + posLen = std::max((size_t)2, posLen) - 2; + } + } else if (*it == "position") { + if (totalLen + posLen > dynamicLen) { + showPos = false; + } else { + totalLen += posLen; + lengthLen = std::max((size_t)2, lengthLen) - 2; } } } } + std::stringstream dynamic; if (showArtist) dynamic << artist << " - "; if (showAlbum) dynamic << album << " - "; if (showTitle) dynamic << title; - if (showLength) { + if (showLength || showPos) { dynamic << " "; - if (html) { - dynamic << ""; - } - dynamic << length; - if (html) { - dynamic << ""; + if (html) dynamic << ""; + dynamic << '['; + if (showPos) { + dynamic << position; + if (showLength) dynamic << '/'; } + if (showLength) dynamic << length; + dynamic << ']'; + if (html) dynamic << ""; } return dynamic.str(); } @@ -412,6 +502,22 @@ auto Mpris::getPlayerInfo() -> std::optional { } if (error) goto errorexit; + { + auto position_ = playerctl_player_get_position(player, &error); + if (error) { + // it's fine to have an error here because not all players report a position + g_error_free(error); + error = nullptr; + } else { + spdlog::debug("mpris[{}]: position = {}", info.name, position_); + std::chrono::microseconds len = std::chrono::microseconds(position_); + auto len_h = std::chrono::duration_cast(len); + auto len_m = std::chrono::duration_cast(len - len_h); + auto len_s = std::chrono::duration_cast(len - len_m); + info.position = fmt::format("{:02}:{:02}:{:02}", len_h.count(), len_m.count(), len_s.count()); + } + } + return info; errorexit: @@ -508,12 +614,20 @@ auto Mpris::update() -> void { break; } + std::string length = getLengthStr(info, truncate_hours_); + std::string tooltipLength = + (tooltip_len_limits_ || length.length() > 5) ? length : getLengthStr(info, false); + // keep position format same as length format + std::string position = getPositionStr(info, truncate_hours_ && length.length() < 6); + std::string tooltipPosition = + (tooltip_len_limits_ || position.length() > 5) ? position : getPositionStr(info, false); + try { auto label_format = fmt::format( fmt::runtime(formatstr), fmt::arg("player", info.name), fmt::arg("status", info.status_string), fmt::arg("artist", getArtistStr(info, true)), fmt::arg("title", getTitleStr(info, true)), fmt::arg("album", getAlbumStr(info, true)), - fmt::arg("length", getLengthStr(info, false)), + fmt::arg("length", length), fmt::arg("position", position), fmt::arg("dynamic", getDynamicStr(info, true, true)), fmt::arg("player_icon", getIcon(config_["player-icons"], info.name)), fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string))); @@ -531,7 +645,7 @@ auto Mpris::update() -> void { fmt::arg("artist", getArtistStr(info, tooltip_len_limits_)), fmt::arg("title", getTitleStr(info, tooltip_len_limits_)), fmt::arg("album", getAlbumStr(info, tooltip_len_limits_)), - fmt::arg("length", getLengthStr(info, false)), + fmt::arg("length", tooltipLength), fmt::arg("position", tooltipPosition), fmt::arg("dynamic", getDynamicStr(info, tooltip_len_limits_, false)), fmt::arg("player_icon", getIcon(config_["player-icons"], info.name)), fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string))); From a301b8c4cd561a77dc5b93eb1577a7879804da87 Mon Sep 17 00:00:00 2001 From: chayleaf Date: Fri, 17 Feb 2023 15:50:07 +0700 Subject: [PATCH 3/6] mpris: update docs --- man/waybar-mpris.5.scd | 25 ++++++++++++------------- src/modules/mpris/mpris.cpp | 13 +++++++++---- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/man/waybar-mpris.5.scd b/man/waybar-mpris.5.scd index bc735b93..0abe4cc2 100644 --- a/man/waybar-mpris.5.scd +++ b/man/waybar-mpris.5.scd @@ -45,8 +45,7 @@ The *mpris* module displays currently playing media via libplayerctl. *tooltip-format-[status]*: ++ typeof: string ++ - default: "MPD (disconnected)" ++ - The status-specif tooltip format. + The status-specific tooltip format. *artist-len*: ++ typeof: integer ++ @@ -66,34 +65,34 @@ The *mpris* module displays currently playing media via libplayerctl. *dynamic-len*: ++ typeof: integer ++ Maximum length of the Dynamic tag (Wide/Fullwidth Unicode characters - count as two). + count as two). The dynamic tag will not truncate any tags beyond their + set length limits, instead, it will attempt to fit as much of the + available tags as possible. It is recommended you set title-len to + something less than or equal to this value, so the title will always be + displayed. *dynamic-priority* ++ typeof: []string ++ - default: ["title", "length", "artist", "album"] + default: ["title", "length", "position", "artist", "album"] Priority of the tags when truncating the Dynamic tag (absence in this list means force inclusion). *truncate-hours*: ++ typeof: bool ++ default: true ++ - Whether to truncate hours when media duration is less than an hour long + Whether to hide hours when media duration is less than an hour long. *enable-tooltip-len-limits*: ++ typeof: bool ++ default: false ++ - Option to enable the length limits for the tooltip as well + Option to enable the length limits for the tooltip as well. By default + the tooltip ignores all length limits. *ellipsis*: ++ typeof: string ++ default: "…" ++ - Override the default ellipsis (set to empty string to simply truncate - the tags when needed instead). - -*on-click*: ++ - typeof: string ++ - default: play-pause ++ - Overwrite default action toggles. + This character will be used when any of the tags exceed their maximum + length. If you don't want to use an ellipsis, set this to empty string. *on-click*: ++ typeof: string ++ diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 6e8004d6..d2837734 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -206,7 +206,7 @@ size_t utf8_truncate(std::string& str, size_t width = std::string::npos) { return str.length(); } else if (g_unichar_iswide(c)) { total_width += 2; - } else if (!g_unichar_iszerowidth(c)) { + } else if (!g_unichar_iszerowidth(c) && c != 0xAD) { // neither zero-width nor soft hyphen total_width += 1; } @@ -339,6 +339,11 @@ auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) -> } std::stringstream dynamic; + if (html) { + artist = Glib::Markup::escape_text(artist); + album = Glib::Markup::escape_text(album); + title = Glib::Markup::escape_text(title); + } if (showArtist) dynamic << artist << " - "; if (showAlbum) dynamic << album << " - "; if (showTitle) dynamic << title; @@ -472,21 +477,21 @@ auto Mpris::getPlayerInfo() -> std::optional { if (auto artist_ = playerctl_player_get_artist(player, &error)) { spdlog::debug("mpris[{}]: artist = {}", info.name, artist_); - info.artist = Glib::Markup::escape_text(artist_); + info.artist = artist_; g_free(artist_); } if (error) goto errorexit; if (auto album_ = playerctl_player_get_album(player, &error)) { spdlog::debug("mpris[{}]: album = {}", info.name, album_); - info.album = Glib::Markup::escape_text(album_); + info.album = album_; g_free(album_); } if (error) goto errorexit; if (auto title_ = playerctl_player_get_title(player, &error)) { spdlog::debug("mpris[{}]: title = {}", info.name, title_); - info.title = Glib::Markup::escape_text(title_); + info.title = title_; g_free(title_); } if (error) goto errorexit; From 774d8880cdf7c3bb555eaa980fce4e9eeca1a34a Mon Sep 17 00:00:00 2001 From: chayleaf Date: Mon, 20 Feb 2023 22:48:21 +0700 Subject: [PATCH 4/6] mpris: fix potentially invalid seconds count on >1h media --- src/modules/mpris/mpris.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index d2837734..32896bb9 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -348,7 +348,7 @@ auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) -> if (showAlbum) dynamic << album << " - "; if (showTitle) dynamic << title; if (showLength || showPos) { - dynamic << " "; + dynamic << ' '; if (html) dynamic << ""; dynamic << '['; if (showPos) { @@ -501,7 +501,7 @@ auto Mpris::getPlayerInfo() -> std::optional { std::chrono::microseconds len = std::chrono::microseconds(std::strtol(length_, nullptr, 10)); auto len_h = std::chrono::duration_cast(len); auto len_m = std::chrono::duration_cast(len - len_h); - auto len_s = std::chrono::duration_cast(len - len_m); + auto len_s = std::chrono::duration_cast(len - len_h - len_m); info.length = fmt::format("{:02}:{:02}:{:02}", len_h.count(), len_m.count(), len_s.count()); g_free(length_); } @@ -518,7 +518,7 @@ auto Mpris::getPlayerInfo() -> std::optional { std::chrono::microseconds len = std::chrono::microseconds(position_); auto len_h = std::chrono::duration_cast(len); auto len_m = std::chrono::duration_cast(len - len_h); - auto len_s = std::chrono::duration_cast(len - len_m); + auto len_s = std::chrono::duration_cast(len - len_h - len_m); info.position = fmt::format("{:02}:{:02}:{:02}", len_h.count(), len_m.count(), len_s.count()); } } From e5524d505995ec857bdf23bc112bbaa24cfa4a69 Mon Sep 17 00:00:00 2001 From: chayleaf Date: Tue, 21 Feb 2023 02:37:49 +0700 Subject: [PATCH 5/6] mpris: switch base class from AModule to ALabel --- include/modules/mpris/mpris.hpp | 9 +--- man/waybar-mpris.5.scd | 16 +++++++ src/modules/mpris/mpris.cpp | 82 ++++++++++++++------------------- 3 files changed, 52 insertions(+), 55 deletions(-) diff --git a/include/modules/mpris/mpris.hpp b/include/modules/mpris/mpris.hpp index ddb0cb67..0563ceec 100644 --- a/include/modules/mpris/mpris.hpp +++ b/include/modules/mpris/mpris.hpp @@ -16,7 +16,7 @@ extern "C" { namespace waybar::modules::mpris { -class Mpris : public AModule { +class Mpris : public ALabel { public: Mpris(const std::string&, const Json::Value&); virtual ~Mpris(); @@ -44,7 +44,7 @@ class Mpris : public AModule { }; auto getPlayerInfo() -> std::optional; - auto getIcon(const Json::Value&, const std::string&) -> std::string; + auto getIconFromJson(const Json::Value&, const std::string&) -> std::string; auto getArtistStr(const PlayerInfo&, bool) -> std::string; auto getAlbumStr(const PlayerInfo&, bool) -> std::string; auto getTitleStr(const PlayerInfo&, bool) -> std::string; @@ -52,11 +52,7 @@ class Mpris : public AModule { auto getPositionStr(const PlayerInfo&, bool) -> std::string; auto getDynamicStr(const PlayerInfo&, bool, bool) -> std::string; - Gtk::Box box_; - Gtk::Label label_; - // config - std::string format_; std::string format_playing_; std::string format_paused_; std::string format_stopped_; @@ -75,7 +71,6 @@ class Mpris : public AModule { bool tooltip_len_limits_; std::string ellipsis_; - std::chrono::seconds interval_; std::string player_; std::vector ignored_players_; diff --git a/man/waybar-mpris.5.scd b/man/waybar-mpris.5.scd index 0abe4cc2..e8894116 100644 --- a/man/waybar-mpris.5.scd +++ b/man/waybar-mpris.5.scd @@ -94,6 +94,22 @@ The *mpris* module displays currently playing media via libplayerctl. This character will be used when any of the tags exceed their maximum length. If you don't want to use an ellipsis, set this to empty string. +*rotate*: ++ + typeof: integer ++ + Positive value to rotate the text label. + +*max-length*: ++ + typeof: integer ++ + The maximum length in character the module should display. + +*min-length*: ++ + typeof: integer ++ + The minimum length in characters the module should take up. + +*align*: ++ + typeof: float ++ + The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. + *on-click*: ++ typeof: string ++ default: play-pause ++ diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 32896bb9..01f74c22 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -18,10 +18,7 @@ namespace waybar::modules::mpris { const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}"; Mpris::Mpris(const std::string& id, const Json::Value& config) - : AModule(config, "mpris", id), - box_(Gtk::ORIENTATION_HORIZONTAL, 0), - label_(), - format_(DEFAULT_FORMAT), + : ALabel(config, "mpris", id, DEFAULT_FORMAT, 5, false, true), tooltip_(DEFAULT_FORMAT), artist_len_(-1), album_len_(-1), @@ -32,18 +29,10 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) tooltip_len_limits_(false), // this character is used in Gnome so it's fine to use it here ellipsis_(u8"\u2026"), - interval_(0), player_("playerctld"), manager(), player() { - box_.pack_start(label_); - box_.set_name(name_); - event_box_.add(box_); - event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &Mpris::handleToggle)); - if (config_["format"].isString()) { - format_ = config_["format"].asString(); - } if (config_["format-playing"].isString()) { format_playing_ = config_["format-playing"].asString(); } @@ -99,9 +88,6 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) if (config_["truncate-hours"].isBool()) { truncate_hours_ = config["truncate-hours"].asBool(); } - if (config_["interval"].isUInt()) { - interval_ = std::chrono::seconds(config_["interval"].asUInt()); - } if (config_["player"].isString()) { player_ = config_["player"].asString(); } @@ -177,7 +163,7 @@ Mpris::~Mpris() { if (player != NULL) g_object_unref(player); } -auto Mpris::getIcon(const Json::Value& icons, const std::string& key) -> std::string { +auto Mpris::getIconFromJson(const Json::Value& icons, const std::string& key) -> std::string { if (icons.isObject()) { if (icons[key].isString()) { return icons[key].asString(); @@ -239,26 +225,26 @@ void truncate(std::string& s, const std::string& ellipsis, size_t max_len) { } auto Mpris::getArtistStr(const PlayerInfo& info, bool truncated) -> std::string { - std::string artist = info.artist.value_or(std::string()); + auto artist = info.artist.value_or(std::string()); if (truncated && artist_len_ >= 0) truncate(artist, ellipsis_, artist_len_); return artist; } auto Mpris::getAlbumStr(const PlayerInfo& info, bool truncated) -> std::string { - std::string album = info.album.value_or(std::string()); + auto album = info.album.value_or(std::string()); if (truncated && album_len_ >= 0) truncate(album, ellipsis_, album_len_); return album; } auto Mpris::getTitleStr(const PlayerInfo& info, bool truncated) -> std::string { - std::string title = info.title.value_or(std::string()); + auto title = info.title.value_or(std::string()); if (truncated && title_len_ >= 0) truncate(title, ellipsis_, title_len_); return title; } auto Mpris::getLengthStr(const PlayerInfo& info, bool truncated) -> std::string { if (info.length.has_value()) { - std::string length = info.length.value(); + auto length = info.length.value(); return (truncated && length.substr(0, 3) == "00:") ? length.substr(3) : length; } return std::string(); @@ -266,19 +252,19 @@ auto Mpris::getLengthStr(const PlayerInfo& info, bool truncated) -> std::string auto Mpris::getPositionStr(const PlayerInfo& info, bool truncated) -> std::string { if (info.position.has_value()) { - std::string position = info.position.value(); + auto position = info.position.value(); return (truncated && position.substr(0, 3) == "00:") ? position.substr(3) : position; } return std::string(); } auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) -> std::string { - std::string artist = getArtistStr(info, truncated); - std::string album = getAlbumStr(info, truncated); - std::string title = getTitleStr(info, truncated); - std::string length = getLengthStr(info, truncated && truncate_hours_); + auto artist = getArtistStr(info, truncated); + auto album = getAlbumStr(info, truncated); + auto title = getTitleStr(info, truncated); + auto length = getLengthStr(info, truncated && truncate_hours_); // keep position format same as length format - std::string position = getPositionStr(info, truncated && truncate_hours_ && length.length() < 6); + auto position = getPositionStr(info, truncated && truncate_hours_ && length.length() < 6); size_t artistLen = utf8_width(artist); size_t albumLen = utf8_width(album); @@ -305,32 +291,32 @@ auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) -> if (*it == "artist") { if (totalLen + artistLen > dynamicLen) { showArtist = false; - } else { + } else if (showArtist) { totalLen += artistLen; } } else if (*it == "album") { if (totalLen + albumLen > dynamicLen) { showAlbum = false; - } else { + } else if (showAlbum) { totalLen += albumLen; } } else if (*it == "title") { if (totalLen + titleLen > dynamicLen) { showTitle = false; - } else { + } else if (showTitle) { totalLen += titleLen; } } else if (*it == "length") { if (totalLen + lengthLen > dynamicLen) { showLength = false; - } else { + } else if (showLength) { totalLen += lengthLen; posLen = std::max((size_t)2, posLen) - 2; } } else if (*it == "position") { if (totalLen + posLen > dynamicLen) { showPos = false; - } else { + } else if (showPos) { totalLen += posLen; lengthLen = std::max((size_t)2, lengthLen) - 2; } @@ -541,19 +527,19 @@ bool Mpris::handleToggle(GdkEventButton* const& e) { switch (e->button) { case 1: // left-click if (config_["on-click"].isString()) { - return AModule::handleToggle(e); + return ALabel::handleToggle(e); } playerctl_player_play_pause(player, &error); break; case 2: // middle-click if (config_["on-middle-click"].isString()) { - return AModule::handleToggle(e); + return ALabel::handleToggle(e); } playerctl_player_previous(player, &error); break; case 3: // right-click if (config_["on-right-click"].isString()) { - return AModule::handleToggle(e); + return ALabel::handleToggle(e); } playerctl_player_next(player, &error); break; @@ -572,7 +558,7 @@ auto Mpris::update() -> void { auto opt = getPlayerInfo(); if (!opt) { event_box_.set_visible(false); - AModule::update(); + ALabel::update(); return; } auto info = *opt; @@ -585,20 +571,20 @@ auto Mpris::update() -> void { spdlog::debug("mpris[{}]: running update", info.name); // set css class for player status - if (!lastStatus.empty() && box_.get_style_context()->has_class(lastStatus)) { - box_.get_style_context()->remove_class(lastStatus); + if (!lastStatus.empty() && event_box_.get_style_context()->has_class(lastStatus)) { + event_box_.get_style_context()->remove_class(lastStatus); } - if (!box_.get_style_context()->has_class(info.status_string)) { - box_.get_style_context()->add_class(info.status_string); + if (!event_box_.get_style_context()->has_class(info.status_string)) { + event_box_.get_style_context()->add_class(info.status_string); } lastStatus = info.status_string; // set css class for player name - if (!lastPlayer.empty() && box_.get_style_context()->has_class(lastPlayer)) { - box_.get_style_context()->remove_class(lastPlayer); + if (!lastPlayer.empty() && event_box_.get_style_context()->has_class(lastPlayer)) { + event_box_.get_style_context()->remove_class(lastPlayer); } - if (!box_.get_style_context()->has_class(info.name)) { - box_.get_style_context()->add_class(info.name); + if (!event_box_.get_style_context()->has_class(info.name)) { + event_box_.get_style_context()->add_class(info.name); } lastPlayer = info.name; @@ -634,8 +620,8 @@ auto Mpris::update() -> void { fmt::arg("title", getTitleStr(info, true)), fmt::arg("album", getAlbumStr(info, true)), fmt::arg("length", length), fmt::arg("position", position), fmt::arg("dynamic", getDynamicStr(info, true, true)), - fmt::arg("player_icon", getIcon(config_["player-icons"], info.name)), - fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string))); + fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)), + fmt::arg("status_icon", getIconFromJson(config_["status-icons"], info.status_string))); label_.set_markup(label_format); } catch (fmt::format_error const& e) { @@ -652,8 +638,8 @@ auto Mpris::update() -> void { fmt::arg("album", getAlbumStr(info, tooltip_len_limits_)), fmt::arg("length", tooltipLength), fmt::arg("position", tooltipPosition), fmt::arg("dynamic", getDynamicStr(info, tooltip_len_limits_, false)), - fmt::arg("player_icon", getIcon(config_["player-icons"], info.name)), - fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string))); + fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)), + fmt::arg("status_icon", getIconFromJson(config_["status-icons"], info.status_string))); label_.set_tooltip_text(tooltip_text); } catch (fmt::format_error const& e) { @@ -663,7 +649,7 @@ auto Mpris::update() -> void { event_box_.set_visible(true); // call parent update - AModule::update(); + ALabel::update(); } } // namespace waybar::modules::mpris From 0cfd1c7094fca18082b166a2dd4f43e761903fed Mon Sep 17 00:00:00 2001 From: chayleaf Date: Tue, 21 Feb 2023 03:38:26 +0700 Subject: [PATCH 6/6] mpris: don't put ellipsis after whitespace --- include/modules/mpris/mpris.hpp | 4 ++-- man/waybar-mpris.5.scd | 27 ++++++++++++++------------- src/modules/mpris/mpris.cpp | 6 +++--- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/include/modules/mpris/mpris.hpp b/include/modules/mpris/mpris.hpp index 0563ceec..ea5f7de6 100644 --- a/include/modules/mpris/mpris.hpp +++ b/include/modules/mpris/mpris.hpp @@ -39,8 +39,8 @@ class Mpris : public ALabel { std::optional artist; std::optional album; std::optional title; - std::optional length; // as HH:MM:SS - std::optional position; // same format + std::optional length; // as HH:MM:SS + std::optional position; // same format }; auto getPlayerInfo() -> std::optional; diff --git a/man/waybar-mpris.5.scd b/man/waybar-mpris.5.scd index e8894116..40f132d8 100644 --- a/man/waybar-mpris.5.scd +++ b/man/waybar-mpris.5.scd @@ -50,30 +50,30 @@ The *mpris* module displays currently playing media via libplayerctl. *artist-len*: ++ typeof: integer ++ Maximum length of the Artist tag (Wide/Fullwidth Unicode characters - count as two). + count as two). Set to zero to hide the artist in `{dynamic}` tag. *album-len*: ++ typeof: integer ++ Maximum length of the Album tag (Wide/Fullwidth Unicode characters count - as two). + as two). Set to zero to hide the album in `{dynamic}` tag. *title-len*: ++ typeof: integer ++ Maximum length of the Title tag (Wide/Fullwidth Unicode characters count - as two). + as two). Set to zero to hide the title in `{dynamic}` tag. *dynamic-len*: ++ typeof: integer ++ - Maximum length of the Dynamic tag (Wide/Fullwidth Unicode characters - count as two). The dynamic tag will not truncate any tags beyond their - set length limits, instead, it will attempt to fit as much of the - available tags as possible. It is recommended you set title-len to - something less than or equal to this value, so the title will always be + Maximum length of the Dynamic tag (Wide/Fullwidth Unicode characters ++ + count as two). The dynamic tag will not truncate any tags beyond their ++ + set length limits, instead, it will attempt to fit as much of the ++ + available tags as possible. It is recommended you set title-len to ++ + something less than or equal to this value, so the title will always be ++ displayed. -*dynamic-priority* ++ +*dynamic-priority*: ++ typeof: []string ++ - default: ["title", "length", "position", "artist", "album"] + default: ["title", "length", "position", "artist", "album"] ++ Priority of the tags when truncating the Dynamic tag (absence in this list means force inclusion). @@ -108,7 +108,8 @@ The *mpris* module displays currently playing media via libplayerctl. *align*: ++ typeof: float ++ - The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. + The alignment of the text, where 0 is left-aligned and 1 is right-aligned. + If the module is rotated, it will follow the flow of the text. *on-click*: ++ typeof: string ++ @@ -126,11 +127,11 @@ The *mpris* module displays currently playing media via libplayerctl. Overwrite default action toggles. *player-icons*: ++ - typeof: map[string]string + typeof: map[string]string ++ Allows setting _{player-icon}_ based on player-name property. *status-icons*: ++ - typeof: map[string]string + typeof: map[string]string ++ Allows setting _{status-icon}_ based on player status (playing, paused, stopped). diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 01f74c22..d1c94eca 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -32,7 +32,6 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) player_("playerctld"), manager(), player() { - if (config_["format-playing"].isString()) { format_playing_ = config_["format-playing"].asString(); } @@ -192,12 +191,13 @@ size_t utf8_truncate(std::string& str, size_t width = std::string::npos) { return str.length(); } else if (g_unichar_iswide(c)) { total_width += 2; - } else if (!g_unichar_iszerowidth(c) && c != 0xAD) { // neither zero-width nor soft hyphen + } else if (!g_unichar_iszerowidth(c) && c != 0xAD) { // neither zero-width nor soft hyphen total_width += 1; } data = g_utf8_find_next_char(data, end); - if (width != std::string::npos && total_width <= width) trunc_end = data; + if (width != std::string::npos && total_width <= width && !g_unichar_isspace(c)) + trunc_end = data; } if (trunc_end) str.resize(trunc_end - str.data());