Merge pull request #1520 from robertgzr/mpris-module
This commit is contained in:
		
						commit
						86850f5c7a
					
				|  | @ -42,6 +42,9 @@ | |||
| #ifdef HAVE_DBUSMENU | ||||
| #include "modules/sni/tray.hpp" | ||||
| #endif | ||||
| #ifdef HAVE_MPRIS | ||||
| #include "modules/mpris/mpris.hpp" | ||||
| #endif | ||||
| #ifdef HAVE_LIBNL | ||||
| #include "modules/network.hpp" | ||||
| #endif | ||||
|  |  | |||
|  | @ -0,0 +1,67 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <iostream> | ||||
| #include <string> | ||||
| 
 | ||||
| #include "gtkmm/box.h" | ||||
| #include "gtkmm/label.h" | ||||
| 
 | ||||
| extern "C" { | ||||
| #include <playerctl/playerctl.h> | ||||
| } | ||||
| 
 | ||||
| #include "ALabel.hpp" | ||||
| #include "util/sleeper_thread.hpp" | ||||
| 
 | ||||
| namespace waybar::modules::mpris { | ||||
| 
 | ||||
| class Mpris : public AModule { | ||||
|  public: | ||||
|   Mpris(const std::string&, const Json::Value&); | ||||
|   ~Mpris(); | ||||
|   auto update() -> void; | ||||
|   bool handleToggle(GdkEventButton* const&); | ||||
| 
 | ||||
|  private: | ||||
|   static auto onPlayerNameAppeared(PlayerctlPlayerManager*, PlayerctlPlayerName*, gpointer) -> void; | ||||
|   static auto onPlayerNameVanished(PlayerctlPlayerManager*, PlayerctlPlayerName*, gpointer) -> void; | ||||
|   static auto onPlayerPlay(PlayerctlPlayer*, gpointer) -> void; | ||||
|   static auto onPlayerPause(PlayerctlPlayer*, gpointer) -> void; | ||||
|   static auto onPlayerStop(PlayerctlPlayer*, gpointer) -> void; | ||||
|   static auto onPlayerMetadata(PlayerctlPlayer*, GVariant*, gpointer) -> void; | ||||
| 
 | ||||
|   struct PlayerInfo { | ||||
|     std::string name; | ||||
|     PlayerctlPlaybackStatus status; | ||||
|     std::string status_string; | ||||
| 
 | ||||
|     std::optional<std::string> artist; | ||||
|     std::optional<std::string> album; | ||||
|     std::optional<std::string> title; | ||||
|     std::optional<std::string> length;  // as HH:MM:SS
 | ||||
|   }; | ||||
| 
 | ||||
|   auto getPlayerInfo() -> std::optional<PlayerInfo>; | ||||
|   auto getIcon(const Json::Value&, const std::string&) -> std::string; | ||||
| 
 | ||||
|   Gtk::Box box_; | ||||
|   Gtk::Label label_; | ||||
| 
 | ||||
|   // config
 | ||||
|   std::string format_; | ||||
|   std::string format_playing_; | ||||
|   std::string format_paused_; | ||||
|   std::string format_stopped_; | ||||
|   std::chrono::seconds interval_; | ||||
|   std::string player_; | ||||
|   std::vector<std::string> ignored_players_; | ||||
| 
 | ||||
|   PlayerctlPlayerManager* manager; | ||||
|   PlayerctlPlayer* player; | ||||
|   std::string lastStatus; | ||||
|   std::string lastPlayer; | ||||
| 
 | ||||
|   util::SleeperThread thread_; | ||||
| }; | ||||
| 
 | ||||
| }  // namespace waybar::modules::mpris
 | ||||
|  | @ -0,0 +1,103 @@ | |||
| waybar-mpris(5) | ||||
| 
 | ||||
| # NAME | ||||
| 
 | ||||
| waybar - MPRIS module | ||||
| 
 | ||||
| # DESCRIPTION | ||||
| 
 | ||||
| The *mpris* module displays currently playing media via libplayerctl. | ||||
| 
 | ||||
| # CONFIGURATION | ||||
| 
 | ||||
| *player*: ++ | ||||
| 	typeof: string ++ | ||||
| 	default: playerctld ++ | ||||
| 	Name of the MPRIS player to attach to. Using the default value always | ||||
| 	follows the currenly active player. | ||||
| 
 | ||||
| *ignored-players*: ++ | ||||
| 	typeof: []string ++ | ||||
| 	Ignore updates of the listed players, when using playerctld. | ||||
| 
 | ||||
| *interval*: ++ | ||||
| 	typeof: integer ++ | ||||
| 	Refresh MPRIS information on a timer. | ||||
| 
 | ||||
| *format*: ++ | ||||
| 	typeof: string ++ | ||||
| 	default: {player} ({status}) {dynamic} ++ | ||||
| 	The text format. | ||||
| 
 | ||||
| *format-[status]*: ++ | ||||
| 	typeof: string ++ | ||||
| 	The status-specific text format. | ||||
| 
 | ||||
| *on-click*: ++ | ||||
| 	typeof: string ++ | ||||
| 	default: play-pause ++ | ||||
| 	Overwrite default action toggles. | ||||
| 
 | ||||
| *on-middle-click*: ++ | ||||
| 	typeof: string ++ | ||||
| 	default: previous track ++ | ||||
| 	Overwrite default action toggles. | ||||
| 
 | ||||
| *on-right-click*: ++ | ||||
| 	typeof: string ++ | ||||
| 	default: next track ++ | ||||
| 	Overwrite default action toggles. | ||||
| 
 | ||||
| *player-icons*: ++ | ||||
| 	typeof: map[string]string | ||||
| 	Allows setting _{player-icon}_ based on player-name property. | ||||
| 
 | ||||
| *status-icons*: ++ | ||||
| 	typeof: map[string]string | ||||
| 	Allows setting _{status-icon}_ based on player status (playing, paused, | ||||
| 	stopped). | ||||
| 
 | ||||
| 
 | ||||
| # FORMAT REPLACEMENTS | ||||
| 
 | ||||
| *{player}*: The name of the current media player | ||||
| 
 | ||||
| *{status}*: The current status (playing, paused, stopped) | ||||
| 
 | ||||
| *{artist}*: The artist of the current track | ||||
| 
 | ||||
| *{album}*: The album title of the current track | ||||
| 
 | ||||
| *{title}*: The title of the current track | ||||
| 
 | ||||
| *{length}*: Length of the track, formatted as HH:MM:SS | ||||
| 
 | ||||
| *{dynamic}*: Use _{artist}_, _{album}_, _{title}_ and _{length}_, automatically omit++ | ||||
|            empty values | ||||
| 
 | ||||
| *{player-icon}*: Chooses an icon from _player-icons_ based on _{player}_ | ||||
| 
 | ||||
| *{status-icon}*: Chooses an icon from _status-icons_ based on _{status}_ | ||||
| 
 | ||||
| # EXAMPLES | ||||
| 
 | ||||
| ``` | ||||
| "mpris": { | ||||
| 	"format": "DEFAULT: {player_icon} {dynamic}", | ||||
| 	"format-paused": "DEFAULT: {status_icon} <i>{dynamic}</i>", | ||||
| 	"player-icons": { | ||||
| 		"default": "▶", | ||||
| 		"mpv": "🎵" | ||||
| 	}, | ||||
| 	"status-icons": { | ||||
| 		"paused": "⏸" | ||||
| 	}, | ||||
| 	// "ignored-players": ["firefox"] | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| # STYLE | ||||
| 
 | ||||
| - *#mpris* | ||||
| - *#mpris.${status}* | ||||
| - *#mpris.${player}* | ||||
|  | @ -266,6 +266,7 @@ A module group is defined by specifying a module named "group/some-group-name". | |||
| - *waybar-keyboard-state(5)* | ||||
| - *waybar-memory(5)* | ||||
| - *waybar-mpd(5)* | ||||
| - *waybar-mpris(5)* | ||||
| - *waybar-network(5)* | ||||
| - *waybar-pulseaudio(5)* | ||||
| - *waybar-river-mode(5)* | ||||
|  |  | |||
							
								
								
									
										13
									
								
								meson.build
								
								
								
								
							
							
						
						
									
										13
									
								
								meson.build
								
								
								
								
							|  | @ -86,7 +86,10 @@ wayland_cursor = dependency('wayland-cursor') | |||
| wayland_protos = dependency('wayland-protocols') | ||||
| gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0']) | ||||
| dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk')) | ||||
| giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled() or get_option('upower_glib').enabled())) | ||||
| giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or | ||||
|                                                 get_option('logind').enabled() or | ||||
|                                                 get_option('upower_glib').enabled() or | ||||
|                                                 get_option('mpris').enabled())) | ||||
| jsoncpp = dependency('jsoncpp', version : ['>=1.9.2'], fallback : ['jsoncpp', 'jsoncpp_dep']) | ||||
| sigcpp = dependency('sigc++-2.0') | ||||
| libinotify = dependency('libinotify', required: false) | ||||
|  | @ -95,6 +98,7 @@ libinput = dependency('libinput', required: get_option('libinput')) | |||
| libnl = dependency('libnl-3.0', required: get_option('libnl')) | ||||
| libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl')) | ||||
| upower_glib = dependency('upower-glib', required: get_option('upower_glib')) | ||||
| playerctl = dependency('playerctl', version : ['>=2.0.0'], required: get_option('mpris')) | ||||
| libpulse = dependency('libpulse', required: get_option('pulseaudio')) | ||||
| libudev = dependency('libudev', required: get_option('libudev')) | ||||
| libevdev = dependency('libevdev', required: get_option('libevdev')) | ||||
|  | @ -239,6 +243,11 @@ if (upower_glib.found() and giounix.found() and not get_option('logind').disable | |||
|     src_files += 'src/modules/upower/upower_tooltip.cpp' | ||||
| endif | ||||
| 
 | ||||
| if (playerctl.found() and giounix.found() and not get_option('logind').disabled()) | ||||
|     add_project_arguments('-DHAVE_MPRIS', language: 'cpp') | ||||
|     src_files += 'src/modules/mpris/mpris.cpp' | ||||
| endif | ||||
| 
 | ||||
| if libpulse.found() | ||||
|     add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp') | ||||
|     src_files += 'src/modules/pulseaudio.cpp' | ||||
|  | @ -335,6 +344,7 @@ executable( | |||
|         libnl, | ||||
|         libnlgen, | ||||
|         upower_glib, | ||||
|         playerctl, | ||||
|         libpulse, | ||||
|         libjack, | ||||
|         libwireplumber, | ||||
|  | @ -388,6 +398,7 @@ if scdoc.found() | |||
|         'waybar-keyboard-state.5.scd', | ||||
|         'waybar-memory.5.scd', | ||||
|         'waybar-mpd.5.scd', | ||||
|         'waybar-mpris.5.scd', | ||||
|         'waybar-network.5.scd', | ||||
|         'waybar-pulseaudio.5.scd', | ||||
|         'waybar-river-mode.5.scd', | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev s | |||
| option('libevdev', type: 'feature', value: 'auto', description: 'Enable libevdev support for evdev related features') | ||||
| option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio') | ||||
| option('upower_glib', type: 'feature', value: 'auto', description: 'Enable support for upower') | ||||
| option('mpris', type: 'feature', value: 'auto', description: 'Enable support for mpris') | ||||
| option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit') | ||||
| option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray') | ||||
| option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') | ||||
|  |  | |||
|  | @ -22,6 +22,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { | |||
|       return new waybar::modules::upower::UPower(id, config_[name]); | ||||
|     } | ||||
| #endif | ||||
| #ifdef HAVE_MPRIS | ||||
|     if (ref == "mpris") { | ||||
|       return new waybar::modules::mpris::Mpris(id, config_[name]); | ||||
|     } | ||||
| #endif | ||||
| #ifdef HAVE_SWAY | ||||
|     if (ref == "sway/mode") { | ||||
|       return new waybar::modules::sway::Mode(id, config_[name]); | ||||
|  |  | |||
|  | @ -0,0 +1,394 @@ | |||
| #include "modules/mpris/mpris.hpp" | ||||
| 
 | ||||
| #include <fmt/core.h> | ||||
| 
 | ||||
| #include <optional> | ||||
| #include <sstream> | ||||
| #include <string> | ||||
| 
 | ||||
| extern "C" { | ||||
| #include <playerctl/playerctl.h> | ||||
| } | ||||
| 
 | ||||
| #include <spdlog/spdlog.h> | ||||
| 
 | ||||
| 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), | ||||
|       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(); | ||||
|   } | ||||
|   if (config_["format-paused"].isString()) { | ||||
|     format_paused_ = config_["format-paused"].asString(); | ||||
|   } | ||||
|   if (config_["format-stopped"].isString()) { | ||||
|     format_stopped_ = config_["format-stopped"].asString(); | ||||
|   } | ||||
|   if (config_["interval"].isUInt()) { | ||||
|     interval_ = std::chrono::seconds(config_["interval"].asUInt()); | ||||
|   } | ||||
|   if (config_["player"].isString()) { | ||||
|     player_ = config_["player"].asString(); | ||||
|   } | ||||
|   if (config_["ignored-players"].isArray()) { | ||||
|     for (auto it = config_["ignored-players"].begin(); it != config_["ignored-players"].end(); | ||||
|          ++it) { | ||||
|       ignored_players_.push_back(it->asString()); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   GError* error = nullptr; | ||||
|   manager = playerctl_player_manager_new(&error); | ||||
|   if (error) { | ||||
|     throw std::runtime_error(fmt::format("unable to create MPRIS client: {}", error->message)); | ||||
|   } | ||||
| 
 | ||||
|   g_object_connect(manager, "signal::name-appeared", G_CALLBACK(onPlayerNameAppeared), this, NULL); | ||||
|   g_object_connect(manager, "signal::name-vanished", G_CALLBACK(onPlayerNameVanished), this, NULL); | ||||
| 
 | ||||
|   if (player_ == "playerctld") { | ||||
|     // use playerctld proxy
 | ||||
|     PlayerctlPlayerName name = { | ||||
|         .instance = (gchar*)player_.c_str(), | ||||
|         .source = PLAYERCTL_SOURCE_DBUS_SESSION, | ||||
|     }; | ||||
|     player = playerctl_player_new_from_name(&name, &error); | ||||
| 
 | ||||
|   } else { | ||||
|     GList* players = playerctl_list_players(&error); | ||||
|     if (error) { | ||||
|       auto e = fmt::format("unable to list players: {}", error->message); | ||||
|       g_error_free(error); | ||||
|       throw std::runtime_error(e); | ||||
|     } | ||||
| 
 | ||||
|     for (auto p = players; p != NULL; p = p->next) { | ||||
|       auto pn = static_cast<PlayerctlPlayerName*>(p->data); | ||||
|       if (strcmp(pn->name, player_.c_str()) == 0) { | ||||
|         player = playerctl_player_new_from_name(pn, &error); | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if (error) { | ||||
|     throw std::runtime_error( | ||||
|         fmt::format("unable to connect to player {}: {}", player_, error->message)); | ||||
|   } | ||||
| 
 | ||||
|   if (player) { | ||||
|     g_object_connect(player, "signal::play", G_CALLBACK(onPlayerPlay), this, "signal::pause", | ||||
|                      G_CALLBACK(onPlayerPause), this, "signal::stop", G_CALLBACK(onPlayerStop), | ||||
|                      this, "signal::stop", G_CALLBACK(onPlayerStop), this, "signal::metadata", | ||||
|                      G_CALLBACK(onPlayerMetadata), this, NULL); | ||||
|   } | ||||
| 
 | ||||
|   // allow setting an interval count that triggers periodic refreshes
 | ||||
|   if (interval_.count() > 0) { | ||||
|     thread_ = [this] { | ||||
|       dp.emit(); | ||||
|       thread_.sleep_for(interval_); | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   // trigger initial update
 | ||||
|   dp.emit(); | ||||
| } | ||||
| 
 | ||||
| Mpris::~Mpris() { | ||||
|   if (manager != NULL) g_object_unref(manager); | ||||
|   if (player != NULL) g_object_unref(player); | ||||
| } | ||||
| 
 | ||||
| auto Mpris::getIcon(const Json::Value& icons, const std::string& key) -> std::string { | ||||
|   if (icons.isObject()) { | ||||
|     if (icons[key].isString()) { | ||||
|       return icons[key].asString(); | ||||
|     } else if (icons["default"].isString()) { | ||||
|       return icons["default"].asString(); | ||||
|     } | ||||
|   } | ||||
|   return ""; | ||||
| } | ||||
| 
 | ||||
| auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name, | ||||
|                                  gpointer data) -> void { | ||||
|   Mpris* mpris = static_cast<Mpris*>(data); | ||||
|   if (!mpris) return; | ||||
| 
 | ||||
|   spdlog::debug("mpris: name-appeared callback: {}", player_name->name); | ||||
| 
 | ||||
|   if (std::string(player_name->name) != mpris->player_) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   GError* error = nullptr; | ||||
|   mpris->player = playerctl_player_new_from_name(player_name, &error); | ||||
|   g_object_connect(mpris->player, "signal::play", G_CALLBACK(onPlayerPlay), mpris, "signal::pause", | ||||
|                    G_CALLBACK(onPlayerPause), mpris, "signal::stop", G_CALLBACK(onPlayerStop), | ||||
|                    mpris, "signal::stop", G_CALLBACK(onPlayerStop), mpris, "signal::metadata", | ||||
|                    G_CALLBACK(onPlayerMetadata), mpris, NULL); | ||||
| 
 | ||||
|   mpris->dp.emit(); | ||||
| } | ||||
| 
 | ||||
| auto Mpris::onPlayerNameVanished(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name, | ||||
|                                  gpointer data) -> void { | ||||
|   Mpris* mpris = static_cast<Mpris*>(data); | ||||
|   if (!mpris) return; | ||||
| 
 | ||||
|   spdlog::debug("mpris: player-vanished callback: {}", player_name->name); | ||||
| 
 | ||||
|   if (std::string(player_name->name) == mpris->player_) { | ||||
|     mpris->player = nullptr; | ||||
|     mpris->dp.emit(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| auto Mpris::onPlayerPlay(PlayerctlPlayer* player, gpointer data) -> void { | ||||
|   Mpris* mpris = static_cast<Mpris*>(data); | ||||
|   if (!mpris) return; | ||||
| 
 | ||||
|   spdlog::debug("mpris: player-play callback"); | ||||
|   // update widget
 | ||||
|   mpris->dp.emit(); | ||||
| } | ||||
| 
 | ||||
| auto Mpris::onPlayerPause(PlayerctlPlayer* player, gpointer data) -> void { | ||||
|   Mpris* mpris = static_cast<Mpris*>(data); | ||||
|   if (!mpris) return; | ||||
| 
 | ||||
|   spdlog::debug("mpris: player-pause callback"); | ||||
|   // update widget
 | ||||
|   mpris->dp.emit(); | ||||
| } | ||||
| 
 | ||||
| auto Mpris::onPlayerStop(PlayerctlPlayer* player, gpointer data) -> void { | ||||
|   Mpris* mpris = static_cast<Mpris*>(data); | ||||
|   if (!mpris) return; | ||||
| 
 | ||||
|   spdlog::debug("mpris: player-stop callback"); | ||||
| 
 | ||||
|   // hide widget
 | ||||
|   mpris->event_box_.set_visible(false); | ||||
|   // update widget
 | ||||
|   mpris->dp.emit(); | ||||
| } | ||||
| 
 | ||||
| auto Mpris::onPlayerMetadata(PlayerctlPlayer* player, GVariant* metadata, gpointer data) -> void { | ||||
|   Mpris* mpris = static_cast<Mpris*>(data); | ||||
|   if (!mpris) return; | ||||
| 
 | ||||
|   spdlog::debug("mpris: player-metadata callback"); | ||||
|   // update widget
 | ||||
|   mpris->dp.emit(); | ||||
| } | ||||
| 
 | ||||
| auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> { | ||||
|   if (!player) { | ||||
|     return std::nullopt; | ||||
|   } | ||||
| 
 | ||||
|   GError* error = nullptr; | ||||
| 
 | ||||
|   char* player_status = nullptr; | ||||
|   auto player_playback_status = PLAYERCTL_PLAYBACK_STATUS_STOPPED; | ||||
|   g_object_get(player, "status", &player_status, "playback-status", &player_playback_status, NULL); | ||||
| 
 | ||||
|   std::string player_name = player_; | ||||
|   if (player_name == "playerctld") { | ||||
|     GList* players = playerctl_list_players(&error); | ||||
|     if (error) { | ||||
|       auto e = fmt::format("unable to list players: {}", error->message); | ||||
|       g_error_free(error); | ||||
|       throw std::runtime_error(e); | ||||
|     } | ||||
|     // > get the list of players [..] in order of activity
 | ||||
|     // https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249
 | ||||
|     players = g_list_first(players); | ||||
|     if (players) player_name = static_cast<PlayerctlPlayerName*>(players->data)->name; | ||||
|   } | ||||
| 
 | ||||
|   if (std::any_of(ignored_players_.begin(), ignored_players_.end(), | ||||
|                   [&](const std::string& pn) { return player_name == pn; })) { | ||||
|     spdlog::warn("mpris[{}]: ignoring player update", player_name); | ||||
|     return std::nullopt; | ||||
|   } | ||||
| 
 | ||||
|   // make status lowercase
 | ||||
|   player_status[0] = std::tolower(player_status[0]); | ||||
| 
 | ||||
|   PlayerInfo info = { | ||||
|       .name = player_name, | ||||
|       .status = player_playback_status, | ||||
|       .status_string = player_status, | ||||
|   }; | ||||
| 
 | ||||
|   if (auto artist_ = playerctl_player_get_artist(player, &error)) { | ||||
|     spdlog::debug("mpris[{}]: artist = {}", info.name, artist_); | ||||
|     info.artist = Glib::Markup::escape_text(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_); | ||||
|     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_); | ||||
|     g_free(title_); | ||||
|   } | ||||
|   if (error) goto errorexit; | ||||
| 
 | ||||
|   if (auto length_ = playerctl_player_print_metadata_prop(player, "mpris:length", &error)) { | ||||
|     spdlog::debug("mpris[{}]: mpris:length = {}", info.name, length_); | ||||
|     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_m = std::chrono::duration_cast<std::chrono::minutes>(len - len_h); | ||||
|     auto len_s = std::chrono::duration_cast<std::chrono::seconds>(len - len_m); | ||||
|     info.length = fmt::format("{:02}:{:02}:{:02}", len_h.count(), len_m.count(), len_s.count()); | ||||
|     g_free(length_); | ||||
|   } | ||||
|   if (error) goto errorexit; | ||||
| 
 | ||||
|   return info; | ||||
| 
 | ||||
| errorexit: | ||||
|   spdlog::error("mpris[{}]: {}", info.name, error->message); | ||||
|   g_error_free(error); | ||||
|   return std::nullopt; | ||||
| } | ||||
| 
 | ||||
| bool Mpris::handleToggle(GdkEventButton* const& e) { | ||||
|   GError* error = nullptr; | ||||
| 
 | ||||
|   auto info = getPlayerInfo(); | ||||
|   if (!info) return false; | ||||
| 
 | ||||
|   if (e->type == GdkEventType::GDK_BUTTON_PRESS) { | ||||
|     switch (e->button) { | ||||
|       case 1:  // left-click
 | ||||
|         if (config_["on-click"].isString()) { | ||||
|           return AModule::handleToggle(e); | ||||
|         } | ||||
|         playerctl_player_play_pause(player, &error); | ||||
|         break; | ||||
|       case 2:  // middle-click
 | ||||
|         if (config_["on-middle-click"].isString()) { | ||||
|           return AModule::handleToggle(e); | ||||
|         } | ||||
|         playerctl_player_previous(player, &error); | ||||
|         break; | ||||
|       case 3:  // right-click
 | ||||
|         if (config_["on-right-click"].isString()) { | ||||
|           return AModule::handleToggle(e); | ||||
|         } | ||||
|         playerctl_player_next(player, &error); | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
|   if (error) { | ||||
|     spdlog::error("mpris[{}]: error running builtin on-click action: {}", (*info).name, | ||||
|                   error->message); | ||||
|     g_error_free(error); | ||||
|     return false; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| auto Mpris::update() -> void { | ||||
|   auto opt = getPlayerInfo(); | ||||
|   if (!opt) { | ||||
|     event_box_.set_visible(false); | ||||
|     AModule::update(); | ||||
|     return; | ||||
|   } | ||||
|   auto info = *opt; | ||||
| 
 | ||||
|   if (info.status == PLAYERCTL_PLAYBACK_STATUS_STOPPED) { | ||||
|     spdlog::debug("mpris[{}]: player stopped, skipping update", info.name); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   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
 | ||||
|   if (!lastStatus.empty() && box_.get_style_context()->has_class(lastStatus)) { | ||||
|     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); | ||||
|   } | ||||
|   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 (!box_.get_style_context()->has_class(info.name)) { | ||||
|     box_.get_style_context()->add_class(info.name); | ||||
|   } | ||||
|   lastPlayer = info.name; | ||||
| 
 | ||||
|   auto formatstr = format_; | ||||
|   switch (info.status) { | ||||
|     case PLAYERCTL_PLAYBACK_STATUS_PLAYING: | ||||
|       if (!format_playing_.empty()) formatstr = format_playing_; | ||||
|       break; | ||||
|     case PLAYERCTL_PLAYBACK_STATUS_PAUSED: | ||||
|       if (!format_paused_.empty()) formatstr = format_paused_; | ||||
|       break; | ||||
|     case PLAYERCTL_PLAYBACK_STATUS_STOPPED: | ||||
|       if (!format_stopped_.empty()) formatstr = format_stopped_; | ||||
|       break; | ||||
|   } | ||||
|   auto label_format = | ||||
|       fmt::format(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); | ||||
| 
 | ||||
|   event_box_.set_visible(true); | ||||
|   // call parent update
 | ||||
|   AModule::update(); | ||||
| } | ||||
| 
 | ||||
| }  // namespace waybar::modules::mpris
 | ||||
		Loading…
	
		Reference in New Issue