#include #include #include #include #include "dbus_info.h" #include "string_utils.h" using ms = std::chrono::milliseconds; struct metadata spotify; struct metadata generic_mpris; typedef std::vector> string_pair_vec; typedef std::unordered_map string_pair_vec_map; typedef std::unordered_map string_map; std::string format_signal(const DBusSignal& s) { std::stringstream ss; ss << "type='signal',interface='" << s.intf << "'"; ss << ",member='" << s.signal << "'"; return ss.str(); } static bool check_msg_arg(libdbus_loader& dbus, DBusMessageIter *iter, int type) { int curr_type = DBUS_TYPE_INVALID; if ((curr_type = dbus.message_iter_get_arg_type (iter)) != type) { #ifndef NDEBUG std::cerr << "Argument is not of type '" << (char)type << "' != '" << (char) curr_type << "'" << std::endl; #endif return false; } return true; } bool get_string_array(libdbus_loader& dbus, DBusMessageIter *iter_, std::vector& entries) { DBusMessageIter iter = *iter_; DBusMessageIter subiter; int current_type = DBUS_TYPE_INVALID; current_type = dbus.message_iter_get_arg_type (&iter); if (current_type == DBUS_TYPE_VARIANT) { dbus.message_iter_recurse (&iter, &iter); current_type = dbus.message_iter_get_arg_type (&iter); } if (current_type != DBUS_TYPE_ARRAY) { #ifndef NDEBUG std::cerr << "Not an array: '" << (char)current_type << "'" << std::endl; #endif return false; } char *val = nullptr; dbus.message_iter_recurse (&iter, &subiter); entries.clear(); while ((current_type = dbus.message_iter_get_arg_type (&subiter)) != DBUS_TYPE_INVALID) { if (current_type == DBUS_TYPE_STRING) { dbus.message_iter_get_basic (&subiter, &val); entries.push_back(val); } dbus.message_iter_next (&subiter); } return true; } static bool get_variant_string(libdbus_loader& dbus, DBusMessageIter *iter_, std::string &val, bool key_or_value = false) { DBusMessageIter iter = *iter_; char *str = nullptr; int type = dbus.message_iter_get_arg_type (&iter); if (type != DBUS_TYPE_VARIANT && type != DBUS_TYPE_DICT_ENTRY) return false; dbus.message_iter_recurse (&iter, &iter); if (key_or_value) { dbus.message_iter_next (&iter); if (!check_msg_arg (dbus, &iter, DBUS_TYPE_VARIANT)) return false; dbus.message_iter_recurse (&iter, &iter); } if (!check_msg_arg (dbus, &iter, DBUS_TYPE_STRING)) return false; dbus.message_iter_get_basic(&iter, &str); val = str; return true; } static bool get_variant_string(libdbus_loader& dbus, DBusMessage *msg, std::string &val, bool key_or_value = false) { DBusMessageIter iter; dbus.message_iter_init (msg, &iter); return get_variant_string(dbus, &iter, val, key_or_value); } static void parse_mpris_metadata(libdbus_loader& dbus, DBusMessageIter *iter_, string_pair_vec& entries) { DBusMessageIter subiter, iter = *iter_; std::string key, val; std::vector list; while (dbus.message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) { //std::cerr << "\ttype: " << (char)dbus.message_iter_get_arg_type(&iter) << std::endl; if (!get_variant_string(dbus, &iter, key)) return; dbus.message_iter_recurse (&iter, &subiter); dbus.message_iter_next (&subiter); //std::cerr << "\tkey: " << key << std::endl; if (get_variant_string(dbus, &subiter, val)) { //std::cerr << "\t\t" << val << std::endl; entries.push_back({key, val}); } else if (get_string_array(dbus, &subiter, list)) { for (auto& s : list) { //std::cerr << "\t\t" << s << std::endl; entries.push_back({key, s}); } } dbus.message_iter_next (&iter); } } static void parse_mpris_properties(libdbus_loader& dbus, DBusMessage *msg, std::string& source, string_pair_vec_map& entries_map) { const char *val_char = nullptr; DBusMessageIter iter; std::string key, val; std::vector stack; stack.push_back({}); dbus.message_iter_init (msg, &stack.back()); // Should be 'org.mpris.MediaPlayer2.Player' if (!check_msg_arg(dbus, &stack.back(), DBUS_TYPE_STRING)) return; dbus.message_iter_get_basic(&stack.back(), &val_char); source = val_char; if (source != "org.mpris.MediaPlayer2.Player") return; dbus.message_iter_next (&stack.back()); //std::cerr << "type: " << (char)dbus.message_iter_get_arg_type(&stack.back()) << std::endl; if (!check_msg_arg(dbus, &stack.back(), DBUS_TYPE_ARRAY)) return; dbus.message_iter_recurse (&stack.back(), &iter); stack.push_back(iter); while (dbus.message_iter_get_arg_type(&stack.back()) != DBUS_TYPE_INVALID) { if (!get_variant_string(dbus, &stack.back(), key)) { dbus.message_iter_next (&stack.back()); continue; } if (key == "Metadata") { #ifndef NDEBUG std::cerr << __func__ << ": Found Metadata!" << std::endl; #endif // dive into Metadata dbus.message_iter_recurse (&stack.back(), &iter); // get the array of entries dbus.message_iter_next (&iter); if (!check_msg_arg(dbus, &iter, DBUS_TYPE_VARIANT)) continue; dbus.message_iter_recurse (&iter, &iter); if (!check_msg_arg(dbus, &iter, DBUS_TYPE_ARRAY)) continue; dbus.message_iter_recurse (&iter, &iter); parse_mpris_metadata(dbus, &iter, entries_map["Metadata"]); } else if (key == "PlaybackStatus") { dbus.message_iter_recurse (&stack.back(), &iter); dbus.message_iter_next (&iter); if (get_variant_string(dbus, &iter, val)) entries_map["PlaybackStatus"].push_back({key, val}); } dbus.message_iter_next (&stack.back()); } } static void parse_property_changed(libdbus_loader& dbus, DBusMessage *msg, std::string& source, string_pair_vec& entries) { char *name = nullptr; int i; uint64_t u64; double d; std::vector stack; stack.push_back({}); #ifndef NDEBUG std::vector padding; padding.push_back('\0'); #endif dbus.message_iter_init (msg, &stack.back()); int type, prev_type = 0; type = dbus.message_iter_get_arg_type (&stack.back()); if (type != DBUS_TYPE_STRING) { #ifndef NDEBUG std::cerr << __func__ << "First element is not a string" << std::endl; #endif return; } dbus.message_iter_get_basic(&stack.back(), &name); source = name; #ifndef NDEBUG std::cout << name << std::endl; #endif std::pair kv; dbus.message_iter_next (&stack.back()); // the loop should be able parse the whole message if used for generic use-cases while ((type = dbus.message_iter_get_arg_type (&stack.back())) != DBUS_TYPE_INVALID) { #ifndef NDEBUG padding.back() = ' '; padding.resize(stack.size() + 1, ' '); padding.back() = '\0'; std::cout << padding.data() << "Type: " << (char)type; #endif if (type == DBUS_TYPE_STRING) { dbus.message_iter_get_basic(&stack.back(), &name); #ifndef NDEBUG std::cout << "=" << name << std::endl; #endif if (prev_type == DBUS_TYPE_DICT_ENTRY) // is key ? kv.first = name; if (prev_type == DBUS_TYPE_VARIANT || prev_type == DBUS_TYPE_ARRAY) { // is value ? kv.second = name; entries.push_back(kv); } } else if (type == DBUS_TYPE_INT32) { dbus.message_iter_get_basic(&stack.back(), &i); #ifndef NDEBUG std::cout << "=" << i << std::endl; #endif } else if (type == DBUS_TYPE_UINT64) { dbus.message_iter_get_basic(&stack.back(), &u64); #ifndef NDEBUG std::cout << "=" << u64 << std::endl; #endif } else if (type == DBUS_TYPE_DOUBLE) { dbus.message_iter_get_basic(&stack.back(), &d); #ifndef NDEBUG std::cout << "=" << d << std::endl; #endif } else if (type == DBUS_TYPE_ARRAY || type == DBUS_TYPE_DICT_ENTRY || type == DBUS_TYPE_VARIANT) { #ifndef NDEBUG std::cout << std::endl; #endif prev_type = type; DBusMessageIter iter; dbus.message_iter_recurse (&stack.back(), &iter); if (dbus.message_iter_get_arg_type (&stack.back()) != DBUS_TYPE_INVALID) stack.push_back(iter); continue; } else { #ifndef NDEBUG std::cout << std::endl; #endif } while(FALSE == dbus.message_iter_next (&stack.back()) && stack.size() > 1) { stack.pop_back(); prev_type = 0; } } } bool get_dict_string_array(libdbus_loader& dbus, DBusMessage *msg, string_pair_vec& entries) { DBusMessageIter iter, outer_iter; dbus.message_iter_init (msg, &outer_iter); int current_type = DBUS_TYPE_INVALID; current_type = dbus.message_iter_get_arg_type (&outer_iter); if (current_type == DBUS_TYPE_VARIANT) { dbus.message_iter_recurse (&outer_iter, &outer_iter); current_type = dbus.message_iter_get_arg_type (&outer_iter); } if (current_type != DBUS_TYPE_ARRAY) { #ifndef NDEBUG std::cerr << "Not an array " << (char)current_type << std::endl; #endif return false; } char *val_key = nullptr, *val_value = nullptr; dbus.message_iter_recurse (&outer_iter, &outer_iter); while ((current_type = dbus.message_iter_get_arg_type (&outer_iter)) != DBUS_TYPE_INVALID) { // printf("type: %d\n", current_type); if (current_type == DBUS_TYPE_DICT_ENTRY) { dbus.message_iter_recurse (&outer_iter, &iter); // dict entry key //printf("\tentry: {%c, ", dbus.message_iter_get_arg_type (&iter)); dbus.message_iter_get_basic (&iter, &val_key); std::string key = val_key; // dict entry value dbus.message_iter_next (&iter); if (dbus.message_iter_get_arg_type (&iter) == DBUS_TYPE_VARIANT) dbus.message_iter_recurse (&iter, &iter); if (dbus.message_iter_get_arg_type (&iter) == DBUS_TYPE_ARRAY) { dbus.message_iter_recurse (&iter, &iter); if (dbus.message_iter_get_arg_type (&iter) == DBUS_TYPE_STRING) { //printf("%c}\n", dbus.message_iter_get_arg_type (&iter)); dbus.message_iter_get_basic (&iter, &val_value); entries.push_back({val_key, val_value}); } } else if (dbus.message_iter_get_arg_type (&iter) == DBUS_TYPE_STRING) { //printf("%c}\n", dbus.message_iter_get_arg_type (&iter)); dbus.message_iter_get_basic (&iter, &val_value); entries.push_back({val_key, val_value}); } } dbus.message_iter_next (&outer_iter); } return true; } static void assign_metadata(metadata& meta, string_pair_vec_map& entries_map) { string_pair_vec_map::const_iterator it; it = entries_map.find("Metadata"); if (it != entries_map.end()) { meta.title.clear(); meta.artists.clear(); meta.album.clear(); std::lock_guard lk(meta.mutex); std::vector artists; meta.valid = false; for (auto& kv : it->second) { #ifndef NDEBUG std::cerr << kv.first << " = " << kv.second << std::endl; #endif if (kv.first == "xesam:artist") artists.push_back(kv.second); else if (kv.first == "xesam:title") meta.title = kv.second; else if (kv.first == "xesam:album") meta.album = kv.second; else if (kv.first == "mpris:artUrl") meta.artUrl = kv.second; else if (kv.first == "PlaybackStatus") meta.playing = (kv.second == "Playing"); } // XXX Spotify only sends one artist anyway for (auto p = artists.begin(); p != artists.end(); p++) { meta.artists += *p; if (p != artists.end() - 1) meta.artists += ", "; } } it = entries_map.find("PlaybackStatus"); if (it != entries_map.end()) { for (auto& kv : it->second) { if (kv.first == "PlaybackStatus") meta.playing = (kv.second == "Playing"); } } if (meta.artists.size() || !meta.title.empty()) meta.valid = meta.playing; meta.ticker.needs_recalc = true; meta.ticker.pos = 0; meta.ticker.longest = 0; meta.ticker.dir = -1; } bool dbus_get_name_owner(dbusmgr::dbus_manager& dbus_mgr, std::string& name_owner, const char *name) { auto& dbus = dbus_mgr.dbus(); DBusError error; dbus.error_init(&error); DBusMessage * dbus_reply = nullptr; DBusMessage * dbus_msg = nullptr; // dbus-send --session --dest=org.freedesktop.DBus --type=method_call --print-reply /org/freedesktop/DBus org.freedesktop.DBus.GetNameOwner string:"org.mpris.MediaPlayer2.spotify" if (nullptr == (dbus_msg = dbus.message_new_method_call("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "GetNameOwner"))) { throw std::runtime_error("unable to allocate memory for dbus message"); } if (!dbus.message_append_args (dbus_msg, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)) { dbus.message_unref(dbus_msg); std::cerr << "MANGOHUD: " << __func__ << ": dbus_message_append_args failed\n"; dbus.error_free(&error); return false; } if (nullptr == (dbus_reply = dbus.connection_send_with_reply_and_block(dbus_mgr.get_conn(), dbus_msg, DBUS_TIMEOUT_USE_DEFAULT, &error))) { dbus.message_unref(dbus_msg); std::cerr << "MANGOHUD: " << __func__ << ": "<< error.message << "\n"; dbus.error_free(&error); return false; } const char* val = nullptr; DBusMessageIter iter; dbus.message_iter_init (dbus_reply, &iter); if (dbus.message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) return false; dbus.message_iter_get_basic(&iter, &val); if (val) name_owner = val; dbus.message_unref(dbus_msg); dbus.message_unref(dbus_reply); dbus.error_free(&error); return true; } bool dbus_list_name_to_owner(dbusmgr::dbus_manager& dbus_mgr, string_map& name_owners) { auto& dbus = dbus_mgr.dbus(); DBusError error; dbus.error_init(&error); std::vector names; std::string owner; DBusMessageIter iter; DBusMessage * dbus_reply = nullptr; DBusMessage * dbus_msg = nullptr; // dbus-send --session --dest=org.freedesktop.DBus --type=method_call --print-reply /org/freedesktop/DBus org.freedesktop.DBus.GetNameOwner string:"org.mpris.MediaPlayer2.spotify" if (nullptr == (dbus_msg = dbus.message_new_method_call("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames"))) { throw std::runtime_error("unable to allocate memory for dbus message"); } if (nullptr == (dbus_reply = dbus.connection_send_with_reply_and_block(dbus_mgr.get_conn(), dbus_msg, DBUS_TIMEOUT_USE_DEFAULT, &error))) { dbus.message_unref(dbus_msg); std::cerr << "MANGOHUD: " << __func__ << ": "<< error.message << "\n"; dbus.error_free(&error); return false; } dbus.message_iter_init (dbus_reply, &iter); if (!get_string_array(dbus, &iter, names)) return false; for (auto& name : names) { if (!starts_with(name, "org.mpris.MediaPlayer2.")) continue; if (dbus_get_name_owner(dbus_mgr, owner, name.c_str())) { name_owners[name] = owner; } } dbus.message_unref(dbus_msg); dbus.message_unref(dbus_reply); dbus.error_free(&error); return true; } void dbus_get_player_property(dbusmgr::dbus_manager& dbus_mgr, string_pair_vec& entries, const char * dest, const char * prop) { auto& dbus = dbus_mgr.dbus(); DBusError error; dbus.error_init(&error); DBusMessage * dbus_reply = nullptr; DBusMessage * dbus_msg = nullptr; // dbus-send --print-reply --session --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:'org.mpris.MediaPlayer2.Player' string:'Metadata' if (nullptr == (dbus_msg = dbus.message_new_method_call(dest, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get"))) { throw std::runtime_error("unable to allocate memory for dbus message"); } const char *v_STRINGS[] = { "org.mpris.MediaPlayer2.Player", }; if (!dbus.message_append_args (dbus_msg, DBUS_TYPE_STRING, &v_STRINGS[0], DBUS_TYPE_STRING, &prop, DBUS_TYPE_INVALID)) { dbus.message_unref(dbus_msg); throw std::runtime_error("dbus_message_append_args failed"); } if (nullptr == (dbus_reply = dbus.connection_send_with_reply_and_block(dbus_mgr.get_conn(), dbus_msg, DBUS_TIMEOUT_USE_DEFAULT, &error))) { dbus.message_unref(dbus_msg); throw dbusmgr::dbus_error(dbus, &error); } std::string entry; if (get_dict_string_array(dbus, dbus_reply, entries)) { // nothing } else if (get_variant_string(dbus, dbus_reply, entry)) { entries.push_back({prop, entry}); } dbus.message_unref(dbus_msg); dbus.message_unref(dbus_reply); dbus.error_free(&error); } void get_spotify_metadata(dbusmgr::dbus_manager& dbus, metadata& meta) { meta.artists.clear(); string_pair_vec_map entries; dbus_get_player_property(dbus, entries["Metadata"], "org.mpris.MediaPlayer2.spotify", "Metadata"); dbus_get_player_property(dbus, entries["PlaybackStatus"], "org.mpris.MediaPlayer2.spotify", "PlaybackStatus"); assign_metadata(meta, entries); } namespace dbusmgr { void dbus_manager::init() { if (m_inited) return; if (!m_dbus_ldr.IsLoaded() && !m_dbus_ldr.Load("libdbus-1.so.3")) throw std::runtime_error("Could not load libdbus-1.so.3"); m_dbus_ldr.error_init(&m_error); m_dbus_ldr.threads_init_default(); if ( nullptr == (m_dbus_conn = m_dbus_ldr.bus_get(DBUS_BUS_SESSION, &m_error)) ) { throw dbus_error(m_dbus_ldr, &m_error); } std::cout << "Connected to D-Bus as \"" << m_dbus_ldr.bus_get_unique_name(m_dbus_conn) << "\"." << std::endl; dbus_list_name_to_owner(*this, m_name_owners); connect_to_signals(); m_inited = true; } void dbus_manager::deinit() { if (!m_inited) return; // unreference system bus connection instead of closing it if (m_dbus_conn) { disconnect_from_signals(); m_dbus_ldr.connection_unref(m_dbus_conn); m_dbus_conn = nullptr; } m_dbus_ldr.error_free(&m_error); m_inited = false; } dbus_manager::~dbus_manager() { deinit(); } void dbus_manager::connect_to_signals() { for (auto kv : m_signals) { auto signal = format_signal(kv); m_dbus_ldr.bus_add_match(m_dbus_conn, signal.c_str(), &m_error); if (m_dbus_ldr.error_is_set(&m_error)) { ::perror(m_error.name); ::perror(m_error.message); m_dbus_ldr.error_free(&m_error); //return; } } start_thread(); } void dbus_manager::disconnect_from_signals() { for (auto kv : m_signals) { auto signal = format_signal(kv); m_dbus_ldr.bus_remove_match(m_dbus_conn, signal.c_str(), &m_error); if (m_dbus_ldr.error_is_set(&m_error)) { ::perror(m_error.name); ::perror(m_error.message); m_dbus_ldr.error_free(&m_error); } } stop_thread(); } void dbus_manager::add_callback(CBENUM type, callback_func func) { m_callbacks[type] = func; } void dbus_manager::stop_thread() { m_quit = true; if (m_thread.joinable()) m_thread.join(); } void dbus_manager::start_thread() { stop_thread(); m_quit = false; m_thread = std::thread(dbus_thread, this); } void dbus_manager::dbus_thread(dbus_manager *pmgr) { (void)parse_property_changed; DBusMessage *msg = nullptr; auto& dbus = pmgr->dbus(); // loop listening for signals being emmitted while (!pmgr->m_quit) { // non blocking read of the next available message if (!dbus.connection_read_write(pmgr->m_dbus_conn, 0)) return; // connection closed msg = dbus.connection_pop_message(pmgr->m_dbus_conn); // loop again if we haven't read a message if (nullptr == msg) { std::this_thread::sleep_for(ms(10)); continue; } for (auto& sig : pmgr->m_signals) { if (dbus.message_is_signal(msg, sig.intf, sig.signal)) { const char *sender = dbus.message_get_sender(msg); #ifndef NDEBUG std::cerr << __func__ << ": " << sig.intf << "::" << sig.signal << "\n"; std::cerr << "Sender: " << sender << "\n"; #endif switch (sig.type) { case ST_PROPERTIESCHANGED: { std::string source; string_pair_vec_map entries_map; //parse_property_changed(msg, source, entries); parse_mpris_properties(dbus, msg, source, entries_map); #ifndef NDEBUG std::cerr << "Source: " << source << "\n"; #endif if (source != "org.mpris.MediaPlayer2.Player") break; if (pmgr->m_name_owners["org.mpris.MediaPlayer2.spotify"] == sender) { assign_metadata(spotify, entries_map); } else { assign_metadata(generic_mpris, entries_map); if (generic_mpris.playing && !generic_mpris.valid) { dbus_get_player_property(*pmgr, entries_map["Metadata"], sender, "Metadata"); assign_metadata(generic_mpris, entries_map); } } } break; case ST_NAMEOWNERCHANGED: { DBusMessageIter iter; dbus.message_iter_init (msg, &iter); std::vector str; const char *value = nullptr; while (dbus.message_iter_get_arg_type (&iter) == DBUS_TYPE_STRING) { dbus.message_iter_get_basic (&iter, &value); str.push_back(value); dbus.message_iter_next (&iter); } // register new name if (str.size() == 3 && starts_with(str[0], "org.mpris.MediaPlayer2.") && !str[2].empty() ) { pmgr->m_name_owners[str[0]] = str[2]; } // did a player quit? if (str[2].empty()) { if (str.size() == 3 && str[0] == "org.mpris.MediaPlayer2.spotify" ) { spotify.valid = false; } else { auto it = pmgr->m_name_owners.find(str[0]); if (it != pmgr->m_name_owners.end() && it->second == str[1]) { generic_mpris.artists.clear(); generic_mpris.title.clear(); generic_mpris.album.clear(); generic_mpris.valid = false; } } } } break; default: break; } } } // free the message dbus.message_unref(msg); } } dbus_manager dbus_mgr; }