Merge pull request #2011 from chayleaf/improve-mpris
commit
ba6faa7859
|
@ -16,7 +16,7 @@ extern "C" {
|
||||||
|
|
||||||
namespace waybar::modules::mpris {
|
namespace waybar::modules::mpris {
|
||||||
|
|
||||||
class Mpris : public AModule {
|
class Mpris : public ALabel {
|
||||||
public:
|
public:
|
||||||
Mpris(const std::string&, const Json::Value&);
|
Mpris(const std::string&, const Json::Value&);
|
||||||
virtual ~Mpris();
|
virtual ~Mpris();
|
||||||
|
@ -40,20 +40,37 @@ class Mpris : public AModule {
|
||||||
std::optional<std::string> album;
|
std::optional<std::string> album;
|
||||||
std::optional<std::string> title;
|
std::optional<std::string> title;
|
||||||
std::optional<std::string> length; // as HH:MM:SS
|
std::optional<std::string> length; // as HH:MM:SS
|
||||||
|
std::optional<std::string> position; // same format
|
||||||
};
|
};
|
||||||
|
|
||||||
auto getPlayerInfo() -> std::optional<PlayerInfo>;
|
auto getPlayerInfo() -> std::optional<PlayerInfo>;
|
||||||
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;
|
||||||
Gtk::Box box_;
|
auto getAlbumStr(const PlayerInfo&, bool) -> std::string;
|
||||||
Gtk::Label label_;
|
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;
|
||||||
|
|
||||||
// config
|
// config
|
||||||
std::string format_;
|
|
||||||
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::chrono::seconds interval_;
|
|
||||||
|
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 truncate_hours_;
|
||||||
|
bool tooltip_len_limits_;
|
||||||
|
std::string ellipsis_;
|
||||||
|
|
||||||
std::string player_;
|
std::string player_;
|
||||||
std::vector<std::string> ignored_players_;
|
std::vector<std::string> ignored_players_;
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,84 @@ 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 ++
|
||||||
|
The status-specific tooltip format.
|
||||||
|
|
||||||
|
*artist-len*: ++
|
||||||
|
typeof: integer ++
|
||||||
|
Maximum length of the Artist tag (Wide/Fullwidth Unicode characters
|
||||||
|
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). 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). 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 ++
|
||||||
|
displayed.
|
||||||
|
|
||||||
|
*dynamic-priority*: ++
|
||||||
|
typeof: []string ++
|
||||||
|
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 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. By default
|
||||||
|
the tooltip ignores all length limits.
|
||||||
|
|
||||||
|
*ellipsis*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: "…" ++
|
||||||
|
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*: ++
|
*on-click*: ++
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
default: play-pause ++
|
default: play-pause ++
|
||||||
|
@ -49,11 +127,11 @@ The *mpris* module displays currently playing media via libplayerctl.
|
||||||
Overwrite default action toggles.
|
Overwrite default action toggles.
|
||||||
|
|
||||||
*player-icons*: ++
|
*player-icons*: ++
|
||||||
typeof: map[string]string
|
typeof: map[string]string ++
|
||||||
Allows setting _{player-icon}_ based on player-name property.
|
Allows setting _{player-icon}_ based on player-name property.
|
||||||
|
|
||||||
*status-icons*: ++
|
*status-icons*: ++
|
||||||
typeof: map[string]string
|
typeof: map[string]string ++
|
||||||
Allows setting _{status-icon}_ based on player status (playing, paused,
|
Allows setting _{status-icon}_ based on player status (playing, paused,
|
||||||
stopped).
|
stopped).
|
||||||
|
|
||||||
|
@ -83,8 +161,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": "🎵"
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
#include "modules/mpris/mpris.hpp"
|
|
||||||
|
|
||||||
#include <fmt/core.h>
|
#include <fmt/core.h>
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "modules/mpris/mpris.hpp"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <playerctl/playerctl.h>
|
#include <playerctl/playerctl.h>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
namespace waybar::modules::mpris {
|
namespace waybar::modules::mpris {
|
||||||
|
@ -17,22 +18,20 @@ namespace waybar::modules::mpris {
|
||||||
const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}";
|
const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}";
|
||||||
|
|
||||||
Mpris::Mpris(const std::string& id, const Json::Value& config)
|
Mpris::Mpris(const std::string& id, const Json::Value& config)
|
||||||
: AModule(config, "mpris", id),
|
: ALabel(config, "mpris", id, DEFAULT_FORMAT, 5, false, true),
|
||||||
box_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
tooltip_(DEFAULT_FORMAT),
|
||||||
label_(),
|
artist_len_(-1),
|
||||||
format_(DEFAULT_FORMAT),
|
album_len_(-1),
|
||||||
interval_(0),
|
title_len_(-1),
|
||||||
|
dynamic_len_(-1),
|
||||||
|
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"),
|
||||||
player_("playerctld"),
|
player_("playerctld"),
|
||||||
manager(),
|
manager(),
|
||||||
player() {
|
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()) {
|
if (config_["format-playing"].isString()) {
|
||||||
format_playing_ = config_["format-playing"].asString();
|
format_playing_ = config_["format-playing"].asString();
|
||||||
}
|
}
|
||||||
|
@ -42,8 +41,51 @@ 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 (config_["interval"].isUInt()) {
|
if (config_["ellipsis"].isString()) {
|
||||||
interval_ = std::chrono::seconds(config_["interval"].asUInt());
|
ellipsis_ = config_["ellipsis"].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_["truncate-hours"].isBool()) {
|
||||||
|
truncate_hours_ = config["truncate-hours"].asBool();
|
||||||
}
|
}
|
||||||
if (config_["player"].isString()) {
|
if (config_["player"].isString()) {
|
||||||
player_ = config_["player"].asString();
|
player_ = config_["player"].asString();
|
||||||
|
@ -51,9 +93,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);
|
||||||
|
@ -118,7 +162,7 @@ Mpris::~Mpris() {
|
||||||
if (player != NULL) g_object_unref(player);
|
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.isObject()) {
|
||||||
if (icons[key].isString()) {
|
if (icons[key].isString()) {
|
||||||
return icons[key].asString();
|
return icons[key].asString();
|
||||||
|
@ -129,6 +173,181 @@ auto Mpris::getIcon(const Json::Value& icons, const std::string& key) -> std::st
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) && 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 && !g_unichar_isspace(c))
|
||||||
|
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<std::string&>(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 {
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
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()) {
|
||||||
|
auto length = info.length.value();
|
||||||
|
return (truncated && length.substr(0, 3) == "00:") ? length.substr(3) : length;
|
||||||
|
}
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Mpris::getPositionStr(const PlayerInfo& info, bool truncated) -> std::string {
|
||||||
|
if (info.position.has_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 {
|
||||||
|
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
|
||||||
|
auto position = getPositionStr(info, truncated && truncate_hours_ && length.length() < 6);
|
||||||
|
|
||||||
|
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_;
|
||||||
|
if (showArtist) artistLen += 3;
|
||||||
|
if (showAlbum) albumLen += 3;
|
||||||
|
if (showLength) lengthLen += 3;
|
||||||
|
if (showPos) posLen += 3;
|
||||||
|
|
||||||
|
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 if (showArtist) {
|
||||||
|
totalLen += artistLen;
|
||||||
|
}
|
||||||
|
} else if (*it == "album") {
|
||||||
|
if (totalLen + albumLen > dynamicLen) {
|
||||||
|
showAlbum = false;
|
||||||
|
} else if (showAlbum) {
|
||||||
|
totalLen += albumLen;
|
||||||
|
}
|
||||||
|
} else if (*it == "title") {
|
||||||
|
if (totalLen + titleLen > dynamicLen) {
|
||||||
|
showTitle = false;
|
||||||
|
} else if (showTitle) {
|
||||||
|
totalLen += titleLen;
|
||||||
|
}
|
||||||
|
} else if (*it == "length") {
|
||||||
|
if (totalLen + lengthLen > dynamicLen) {
|
||||||
|
showLength = false;
|
||||||
|
} else if (showLength) {
|
||||||
|
totalLen += lengthLen;
|
||||||
|
posLen = std::max((size_t)2, posLen) - 2;
|
||||||
|
}
|
||||||
|
} else if (*it == "position") {
|
||||||
|
if (totalLen + posLen > dynamicLen) {
|
||||||
|
showPos = false;
|
||||||
|
} else if (showPos) {
|
||||||
|
totalLen += posLen;
|
||||||
|
lengthLen = std::max((size_t)2, lengthLen) - 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
if (showLength || showPos) {
|
||||||
|
dynamic << ' ';
|
||||||
|
if (html) dynamic << "<small>";
|
||||||
|
dynamic << '[';
|
||||||
|
if (showPos) {
|
||||||
|
dynamic << position;
|
||||||
|
if (showLength) dynamic << '/';
|
||||||
|
}
|
||||||
|
if (showLength) dynamic << length;
|
||||||
|
dynamic << ']';
|
||||||
|
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);
|
||||||
|
@ -248,21 +467,21 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
|
||||||
|
|
||||||
if (auto artist_ = playerctl_player_get_artist(player, &error)) {
|
if (auto artist_ = playerctl_player_get_artist(player, &error)) {
|
||||||
spdlog::debug("mpris[{}]: artist = {}", info.name, artist_);
|
spdlog::debug("mpris[{}]: artist = {}", info.name, artist_);
|
||||||
info.artist = Glib::Markup::escape_text(artist_);
|
info.artist = artist_;
|
||||||
g_free(artist_);
|
g_free(artist_);
|
||||||
}
|
}
|
||||||
if (error) goto errorexit;
|
if (error) goto errorexit;
|
||||||
|
|
||||||
if (auto album_ = playerctl_player_get_album(player, &error)) {
|
if (auto album_ = playerctl_player_get_album(player, &error)) {
|
||||||
spdlog::debug("mpris[{}]: album = {}", info.name, album_);
|
spdlog::debug("mpris[{}]: album = {}", info.name, album_);
|
||||||
info.album = Glib::Markup::escape_text(album_);
|
info.album = album_;
|
||||||
g_free(album_);
|
g_free(album_);
|
||||||
}
|
}
|
||||||
if (error) goto errorexit;
|
if (error) goto errorexit;
|
||||||
|
|
||||||
if (auto title_ = playerctl_player_get_title(player, &error)) {
|
if (auto title_ = playerctl_player_get_title(player, &error)) {
|
||||||
spdlog::debug("mpris[{}]: title = {}", info.name, title_);
|
spdlog::debug("mpris[{}]: title = {}", info.name, title_);
|
||||||
info.title = Glib::Markup::escape_text(title_);
|
info.title = title_;
|
||||||
g_free(title_);
|
g_free(title_);
|
||||||
}
|
}
|
||||||
if (error) goto errorexit;
|
if (error) goto errorexit;
|
||||||
|
@ -272,12 +491,28 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
|
||||||
std::chrono::microseconds len = std::chrono::microseconds(std::strtol(length_, nullptr, 10));
|
std::chrono::microseconds len = std::chrono::microseconds(std::strtol(length_, nullptr, 10));
|
||||||
auto len_h = std::chrono::duration_cast<std::chrono::hours>(len);
|
auto len_h = std::chrono::duration_cast<std::chrono::hours>(len);
|
||||||
auto len_m = std::chrono::duration_cast<std::chrono::minutes>(len - len_h);
|
auto len_m = std::chrono::duration_cast<std::chrono::minutes>(len - len_h);
|
||||||
auto len_s = std::chrono::duration_cast<std::chrono::seconds>(len - len_m);
|
auto len_s = std::chrono::duration_cast<std::chrono::seconds>(len - len_h - len_m);
|
||||||
info.length = fmt::format("{:02}:{:02}:{:02}", len_h.count(), len_m.count(), len_s.count());
|
info.length = fmt::format("{:02}:{:02}:{:02}", len_h.count(), len_m.count(), len_s.count());
|
||||||
g_free(length_);
|
g_free(length_);
|
||||||
}
|
}
|
||||||
if (error) goto errorexit;
|
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<std::chrono::hours>(len);
|
||||||
|
auto len_m = std::chrono::duration_cast<std::chrono::minutes>(len - len_h);
|
||||||
|
auto len_s = std::chrono::duration_cast<std::chrono::seconds>(len - len_h - len_m);
|
||||||
|
info.position = fmt::format("{:02}:{:02}:{:02}", len_h.count(), len_m.count(), len_s.count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
|
|
||||||
errorexit:
|
errorexit:
|
||||||
|
@ -296,19 +531,19 @@ bool Mpris::handleToggle(GdkEventButton* const& e) {
|
||||||
switch (e->button) {
|
switch (e->button) {
|
||||||
case 1: // left-click
|
case 1: // left-click
|
||||||
if (config_["on-click"].isString()) {
|
if (config_["on-click"].isString()) {
|
||||||
return AModule::handleToggle(e);
|
return ALabel::handleToggle(e);
|
||||||
}
|
}
|
||||||
playerctl_player_play_pause(player, &error);
|
playerctl_player_play_pause(player, &error);
|
||||||
break;
|
break;
|
||||||
case 2: // middle-click
|
case 2: // middle-click
|
||||||
if (config_["on-middle-click"].isString()) {
|
if (config_["on-middle-click"].isString()) {
|
||||||
return AModule::handleToggle(e);
|
return ALabel::handleToggle(e);
|
||||||
}
|
}
|
||||||
playerctl_player_previous(player, &error);
|
playerctl_player_previous(player, &error);
|
||||||
break;
|
break;
|
||||||
case 3: // right-click
|
case 3: // right-click
|
||||||
if (config_["on-right-click"].isString()) {
|
if (config_["on-right-click"].isString()) {
|
||||||
return AModule::handleToggle(e);
|
return ALabel::handleToggle(e);
|
||||||
}
|
}
|
||||||
playerctl_player_next(player, &error);
|
playerctl_player_next(player, &error);
|
||||||
break;
|
break;
|
||||||
|
@ -327,7 +562,7 @@ auto Mpris::update() -> void {
|
||||||
auto opt = getPlayerInfo();
|
auto opt = getPlayerInfo();
|
||||||
if (!opt) {
|
if (!opt) {
|
||||||
event_box_.set_visible(false);
|
event_box_.set_visible(false);
|
||||||
AModule::update();
|
ALabel::update();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto info = *opt;
|
auto info = *opt;
|
||||||
|
@ -339,60 +574,86 @@ 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() && event_box_.get_style_context()->has_class(lastStatus)) {
|
||||||
box_.get_style_context()->remove_class(lastStatus);
|
event_box_.get_style_context()->remove_class(lastStatus);
|
||||||
}
|
}
|
||||||
if (!box_.get_style_context()->has_class(info.status_string)) {
|
if (!event_box_.get_style_context()->has_class(info.status_string)) {
|
||||||
box_.get_style_context()->add_class(info.status_string);
|
event_box_.get_style_context()->add_class(info.status_string);
|
||||||
}
|
}
|
||||||
lastStatus = info.status_string;
|
lastStatus = info.status_string;
|
||||||
|
|
||||||
// set css class for player name
|
// set css class for player name
|
||||||
if (!lastPlayer.empty() && box_.get_style_context()->has_class(lastPlayer)) {
|
if (!lastPlayer.empty() && event_box_.get_style_context()->has_class(lastPlayer)) {
|
||||||
box_.get_style_context()->remove_class(lastPlayer);
|
event_box_.get_style_context()->remove_class(lastPlayer);
|
||||||
}
|
}
|
||||||
if (!box_.get_style_context()->has_class(info.name)) {
|
if (!event_box_.get_style_context()->has_class(info.name)) {
|
||||||
box_.get_style_context()->add_class(info.name);
|
event_box_.get_style_context()->add_class(info.name);
|
||||||
}
|
}
|
||||||
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),
|
std::string length = getLengthStr(info, truncate_hours_);
|
||||||
fmt::arg("status", info.status_string), fmt::arg("artist", *info.artist),
|
std::string tooltipLength =
|
||||||
fmt::arg("title", *info.title), fmt::arg("album", *info.album),
|
(tooltip_len_limits_ || length.length() > 5) ? length : getLengthStr(info, false);
|
||||||
fmt::arg("length", *info.length), fmt::arg("dynamic", dynamic.str()),
|
// keep position format same as length format
|
||||||
fmt::arg("player_icon", getIcon(config_["player-icons"], info.name)),
|
std::string position = getPositionStr(info, truncate_hours_ && length.length() < 6);
|
||||||
fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string)));
|
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", length), fmt::arg("position", position),
|
||||||
|
fmt::arg("dynamic", getDynamicStr(info, true, true)),
|
||||||
|
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);
|
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", tooltipLength), fmt::arg("position", tooltipPosition),
|
||||||
|
fmt::arg("dynamic", getDynamicStr(info, tooltip_len_limits_, false)),
|
||||||
|
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) {
|
||||||
|
spdlog::warn("mpris: format error (tooltip): {}", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
event_box_.set_visible(true);
|
event_box_.set_visible(true);
|
||||||
// call parent update
|
// call parent update
|
||||||
AModule::update();
|
ALabel::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace waybar::modules::mpris
|
} // namespace waybar::modules::mpris
|
||||||
|
|
Loading…
Reference in New Issue