mpris: add tooltip and length limits

This commit is contained in:
chayleaf 2023-02-16 19:41:24 +07:00
parent 9fd2703cb9
commit a53c97f7f6
4 changed files with 251 additions and 35 deletions

View File

@ -44,6 +44,11 @@ class Mpris : public AModule {
auto getPlayerInfo() -> std::optional<PlayerInfo>; auto getPlayerInfo() -> std::optional<PlayerInfo>;
auto getIcon(const Json::Value&, const std::string&) -> std::string; 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::Box box_;
Gtk::Label label_; Gtk::Label label_;
@ -53,6 +58,19 @@ class Mpris : public AModule {
std::string format_playing_; std::string format_playing_;
std::string format_paused_; std::string format_paused_;
std::string format_stopped_; 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<std::string> dynamic_prio_;
bool tooltip_len_limits_;
std::chrono::seconds interval_; std::chrono::seconds interval_;
std::string player_; std::string player_;
std::vector<std::string> ignored_players_; std::vector<std::string> ignored_players_;

View File

@ -33,6 +33,48 @@ The *mpris* module displays currently playing media via libplayerctl.
typeof: string ++ typeof: string ++
The status-specific text format. 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*: ++ *on-click*: ++
typeof: string ++ typeof: string ++
default: play-pause ++ default: play-pause ++
@ -83,8 +125,8 @@ The *mpris* module displays currently playing media via libplayerctl.
``` ```
"mpris": { "mpris": {
"format": "DEFAULT: {player_icon} {dynamic}", "format": "{player_icon} {dynamic}",
"format-paused": "DEFAULT: {status_icon} <i>{dynamic}</i>", "format-paused": "{status_icon} <i>{dynamic}</i>",
"player-icons": { "player-icons": {
"default": "▶", "default": "▶",
"mpv": "🎵" "mpv": "🎵"

View File

@ -21,6 +21,13 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
box_(Gtk::ORIENTATION_HORIZONTAL, 0), box_(Gtk::ORIENTATION_HORIZONTAL, 0),
label_(), label_(),
format_(DEFAULT_FORMAT), 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), interval_(0),
player_("playerctld"), player_("playerctld"),
manager(), manager(),
@ -42,6 +49,46 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
if (config_["format-stopped"].isString()) { if (config_["format-stopped"].isString()) {
format_stopped_ = config_["format-stopped"].asString(); 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()) { if (config_["interval"].isUInt()) {
interval_ = std::chrono::seconds(config_["interval"].asUInt()); interval_ = std::chrono::seconds(config_["interval"].asUInt());
} }
@ -51,9 +98,11 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
if (config_["ignored-players"].isArray()) { if (config_["ignored-players"].isArray()) {
for (auto it = config_["ignored-players"].begin(); it != config_["ignored-players"].end(); for (auto it = config_["ignored-players"].begin(); it != config_["ignored-players"].end();
++it) { ++it) {
if (it->isString()) {
ignored_players_.push_back(it->asString()); ignored_players_.push_back(it->asString());
} }
} }
}
GError* error = nullptr; GError* error = nullptr;
manager = playerctl_player_manager_new(&error); manager = playerctl_player_manager_new(&error);
@ -129,6 +178,95 @@ auto Mpris::getIcon(const Json::Value& icons, const std::string& key) -> std::st
return ""; 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 << "<small>";
}
dynamic << length;
if (html) {
dynamic << "</small>";
}
}
return dynamic.str();
}
auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name, auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name,
gpointer data) -> void { gpointer data) -> void {
Mpris* mpris = static_cast<Mpris*>(data); Mpris* mpris = static_cast<Mpris*>(data);
@ -335,18 +473,6 @@ auto Mpris::update() -> void {
spdlog::debug("mpris[{}]: running update", info.name); 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 << " "
<< "<small>"
<< "[" << *info.length << "]"
<< "</small>";
// set css class for player status // set css class for player status
if (!lastStatus.empty() && box_.get_style_context()->has_class(lastStatus)) { if (!lastStatus.empty() && box_.get_style_context()->has_class(lastStatus)) {
box_.get_style_context()->remove_class(lastStatus); box_.get_style_context()->remove_class(lastStatus);
@ -366,25 +492,55 @@ auto Mpris::update() -> void {
lastPlayer = info.name; lastPlayer = info.name;
auto formatstr = format_; auto formatstr = format_;
auto tooltipstr = tooltip_;
switch (info.status) { switch (info.status) {
case PLAYERCTL_PLAYBACK_STATUS_PLAYING: case PLAYERCTL_PLAYBACK_STATUS_PLAYING:
if (!format_playing_.empty()) formatstr = format_playing_; if (!format_playing_.empty()) formatstr = format_playing_;
if (!tooltip_playing_.empty()) tooltipstr = tooltip_playing_;
break; break;
case PLAYERCTL_PLAYBACK_STATUS_PAUSED: case PLAYERCTL_PLAYBACK_STATUS_PAUSED:
if (!format_paused_.empty()) formatstr = format_paused_; if (!format_paused_.empty()) formatstr = format_paused_;
if (!tooltip_paused_.empty()) tooltipstr = tooltip_paused_;
break; break;
case PLAYERCTL_PLAYBACK_STATUS_STOPPED: case PLAYERCTL_PLAYBACK_STATUS_STOPPED:
if (!format_stopped_.empty()) formatstr = format_stopped_; if (!format_stopped_.empty()) formatstr = format_stopped_;
if (!tooltip_stopped_.empty()) tooltipstr = tooltip_stopped_;
break; break;
} }
auto label_format =
fmt::format(fmt::runtime(formatstr), fmt::arg("player", info.name), try {
fmt::arg("status", info.status_string), fmt::arg("artist", *info.artist), auto label_format = fmt::format(
fmt::arg("title", *info.title), fmt::arg("album", *info.album), fmt::runtime(formatstr), fmt::arg("player", info.name),
fmt::arg("length", *info.length), fmt::arg("dynamic", dynamic.str()), 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("player_icon", getIcon(config_["player-icons"], info.name)),
fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string))); fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string)));
label_.set_markup(label_format); 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); event_box_.set_visible(true);
// call parent update // call parent update