/* * Copyright 2014 Canonical Ltd. * Copyright 2021 Robert Tari * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: * Ted Gould * Robert Tari */ public class MediaPlayerUser : MediaPlayer { Act.UserManager accounts_manager = Act.UserManager.get_default(); string username; Act.User? actuser = null; AccountsServiceSoundSettings? proxy = null; GreeterBroadcast? greeter = null; HashTable properties_queued = new HashTable(str_hash, str_equal); uint properties_timeout = 0; /* Grab the user from the Accounts service and, when it is loaded then set up a proxy to its sound settings */ public MediaPlayerUser(string user) { username = user; actuser = accounts_manager.get_user(user); actuser.notify["is-loaded"].connect(() => { debug("User loaded"); this.proxy = null; Bus.get_proxy.begin ( BusType.SYSTEM, "org.freedesktop.Accounts", actuser.get_object_path(), DBusProxyFlags.GET_INVALIDATED_PROPERTIES, null, new_proxy); }); Bus.get_proxy.begin ( BusType.SYSTEM, "org.ayatana.Desktop.Greeter.Broadcast", "/org/ayatana/Desktop/Greeter/Broadcast", DBusProxyFlags.NONE, null, greeter_proxy_new); } ~MediaPlayerUser () { if (properties_timeout != 0) { Source.remove(properties_timeout); properties_timeout = 0; } } /* Ensure that we've collected all the changes so that we only signal once for variables like 'track' */ bool properties_idle () { properties_timeout = 0; properties_queued.@foreach((key, value) => { debug("Notifying '%s' changed", key); this.notify_property(key); }); properties_queued.remove_all(); return Source.REMOVE; } /* Turns the DBus names into the object properties */ void queue_property_notification (string dbus_property_name) { if (properties_timeout == 0) { properties_timeout = Idle.add(properties_idle); } switch (dbus_property_name) { case "Timestamp": properties_queued.insert("name", true); properties_queued.insert("icon", true); properties_queued.insert("state", true); properties_queued.insert("current-track", true); properties_queued.insert("is-running", true); break; case "PlayerName": properties_queued.insert("name", true); break; case "PlayerIcon": properties_queued.insert("icon", true); break; case "State": properties_queued.insert("state", true); break; case "Title": case "Artist": case "Album": case "ArtUrl": properties_queued.insert("current-track", true); break; } } void new_proxy (GLib.Object? obj, AsyncResult res) { try { this.proxy = Bus.get_proxy.end (res); var gproxy = this.proxy as DBusProxy; gproxy.g_properties_changed.connect ((proxy, changed, invalidated) => { string key = ""; Variant value; VariantIter iter = new VariantIter(changed); while (iter.next("{sv}", &key, &value)) { queue_property_notification(key); } foreach (var invalid in invalidated) { queue_property_notification(invalid); } }); debug("Notifying player is ready for user: %s", this.username); this.notify_property("is-running"); } catch (Error e) { this.proxy = null; warning("Unable to get proxy to user '%s' sound settings: %s", username, e.message); } } bool proxy_is_valid () { if (this.proxy == null) { return false; } /* More than 10 minutes old */ if (this.proxy.timestamp < GLib.get_monotonic_time() - 10 * 60 * 1000 * 1000) { return false; } return true; } public override string id { get { return username; } } /* These values come from the proxy */ string name_cache; public override string name { get { if (proxy_is_valid()) { name_cache = this.proxy.player_name; debug("Player Name: %s", name_cache); return name_cache; } else { return ""; } } } string state_cache; public override string state { get { if (proxy_is_valid()) { state_cache = this.proxy.state; debug("State: %s", state_cache); return state_cache; } else { return ""; } } set { } } Icon icon_cache; public override Icon? icon { get { if (proxy_is_valid()) { icon_cache = Icon.deserialize(this.proxy.player_icon); return icon_cache; } else { return null; } } } /* Placeholder */ public override string dbus_name { get { return ""; } } /* If it's shown externally it's running */ public override bool is_running { get { return proxy_is_valid(); } } /* A bit weird. Not sure how we should handle this. */ public override bool can_raise { get { return true; } } /* Fill out the track based on the values in the proxy */ MediaPlayer.Track track_cache; public override MediaPlayer.Track? current_track { get { if (proxy_is_valid()) { track_cache = new MediaPlayer.Track( this.proxy.artist, this.proxy.title, this.proxy.album, this.proxy.art_url ); return track_cache; } else { return null; } } set { } } void greeter_proxy_new (GLib.Object? obj, AsyncResult res) { try { this.greeter = Bus.get_proxy.end (res); } catch (Error e) { this.greeter = null; warning("Unable to get greeter proxy: %s", e.message); } } /* Control functions through ayatana-greeter-session-broadcast */ public override void activate () { /* TODO: */ } public override void play_pause () { debug("Play Pause for user: %s", this.username); if (this.greeter != null) { this.greeter.RequestSoundPlayPause.begin(this.username, (obj, res) => { try { var broadcasts = (obj as GreeterBroadcast); if (broadcasts != null) { broadcasts.RequestSoundPlayPause.end(res); } } catch (Error e) { warning("Unable to send play pause: %s", e.message); } }); } else { warning("No ayatana-greeter-session-broadcast to send play-pause"); } } public override void next () { debug("Next for user: %s", this.username); if (this.greeter != null) { this.greeter.RequestSoundNext.begin(this.username, (obj, res) => { try { var broadcasts = (obj as GreeterBroadcast); if (broadcasts != null) { broadcasts.RequestSoundNext.end(res); } } catch (Error e) { warning("Unable to send next: %s", e.message); } }); } else { warning("No ayatana-greeter-session-broadcast to send next"); } } public override void previous () { debug("Previous for user: %s", this.username); if (this.greeter != null) { this.greeter.RequestSoundPrev.begin(this.username, (obj, res) => { try { var broadcasts = (obj as GreeterBroadcast); if (broadcasts != null) { broadcasts.RequestSoundPrev.end(res); } } catch (Error e) { warning("Unable to send previous: %s", e.message); } }); } else { warning("No ayatana-greeter-session-broadcast to send previous"); } } /* Play list functions are all null as we don't support the playlist feature on the greeter */ public override uint get_n_playlists() { return 0; } public override string get_playlist_id (int index) { return ""; } public override string get_playlist_name (int index) { return ""; } public override void activate_playlist_by_name (string playlist) { } }