/*
* 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) {
}
}