diff options
author | Aylur <[email protected]> | 2024-10-27 20:35:37 +0000 |
---|---|---|
committer | Aylur <[email protected]> | 2024-10-27 20:35:45 +0000 |
commit | 197bffbf05d62aeefceb58011b0f031cd8836ca6 (patch) | |
tree | 84beb8f89b71dff0f04a71b3af4f12de9e28cbb1 /lib | |
parent | 17987c6fac21bb9f30c66cae93cd38bb4e8d2dd0 (diff) |
docs(mpris): doc comments
Diffstat (limited to 'lib')
l--------- | lib/mpris/gir.py | 1 | ||||
-rw-r--r-- | lib/mpris/ifaces.vala | 40 | ||||
-rw-r--r-- | lib/mpris/meson.build | 46 | ||||
-rw-r--r-- | lib/mpris/mpris.vala | 45 | ||||
-rw-r--r-- | lib/mpris/player.vala | 317 |
5 files changed, 357 insertions, 92 deletions
diff --git a/lib/mpris/gir.py b/lib/mpris/gir.py new file mode 120000 index 0000000..b5b4f1d --- /dev/null +++ b/lib/mpris/gir.py @@ -0,0 +1 @@ +../gir.py
\ No newline at end of file diff --git a/lib/mpris/ifaces.vala b/lib/mpris/ifaces.vala index 4a9d715..298a288 100644 --- a/lib/mpris/ifaces.vala +++ b/lib/mpris/ifaces.vala @@ -1,19 +1,18 @@ -namespace AstalMpris { [DBus (name="org.freedesktop.DBus")] -internal interface DBusImpl : DBusProxy { - public abstract string[] list_names () throws GLib.Error; - public signal void name_owner_changed (string name, string old_owner, string new_owner); +private interface AstalMpris.DBusImpl : DBusProxy { + public abstract string[] list_names() throws GLib.Error; + public signal void name_owner_changed(string name, string old_owner, string new_owner); } [DBus (name="org.freedesktop.DBus.Properties")] -internal interface PropsIface : DBusProxy { - public abstract HashTable<string, Variant> get_all (string iface); +private interface AstalMpris.PropsIface : DBusProxy { + public abstract HashTable<string, Variant> get_all(string iface); } [DBus (name="org.mpris.MediaPlayer2")] -internal interface IMpris : PropsIface { - public abstract void raise () throws GLib.Error; - public abstract void quit () throws GLib.Error; +private interface AstalMpris.IMpris : PropsIface { + public abstract void raise() throws GLib.Error; + public abstract void quit() throws GLib.Error; public abstract bool can_quit { get; } public abstract bool fullscreen { get; set; } @@ -27,18 +26,18 @@ internal interface IMpris : PropsIface { } [DBus (name="org.mpris.MediaPlayer2.Player")] -internal interface IPlayer : IMpris { - public abstract void next () throws GLib.Error; - public abstract void previous () throws GLib.Error; - public abstract void pause () throws GLib.Error; - public abstract void play_pause () throws GLib.Error; - public abstract void stop () throws GLib.Error; - public abstract void play () throws GLib.Error; - public abstract void seek (int64 offset) throws GLib.Error; - public abstract void set_position (ObjectPath track_id, int64 position) throws GLib.Error; - public abstract void open_uri (string uri) throws GLib.Error; +private interface AstalMpris.IPlayer : IMpris { + public abstract void next() throws GLib.Error; + public abstract void previous() throws GLib.Error; + public abstract void pause() throws GLib.Error; + public abstract void play_pause() throws GLib.Error; + public abstract void stop() throws GLib.Error; + public abstract void play() throws GLib.Error; + public abstract void seek(int64 offset) throws GLib.Error; + public abstract void set_position(ObjectPath track_id, int64 position) throws GLib.Error; + public abstract void open_uri(string uri) throws GLib.Error; - public signal void seeked (int64 position); + public signal void seeked(int64 position); public abstract string playback_status { owned get; } public abstract string loop_status { owned get; set; } @@ -57,4 +56,3 @@ internal interface IPlayer : IMpris { public abstract bool can_seek { get; } public abstract bool can_control { get; } } -} diff --git a/lib/mpris/meson.build b/lib/mpris/meson.build index c9a5c53..bf215c9 100644 --- a/lib/mpris/meson.build +++ b/lib/mpris/meson.build @@ -38,34 +38,40 @@ deps = [ dependency('json-glib-1.0'), ] -sources = [ - config, +sources = [config] + files( 'ifaces.vala', - 'player.vala', 'mpris.vala', -] + 'player.vala', +) if get_option('lib') lib = library( meson.project_name(), sources, dependencies: deps, + vala_args: ['--vapi-comments'], vala_header: meson.project_name() + '.h', vala_vapi: meson.project_name() + '-' + api_version + '.vapi', - vala_gir: gir, version: meson.project_version(), install: true, - install_dir: [true, true, true, true], + install_dir: [true, true, true], ) - import('pkgconfig').generate( - lib, - name: meson.project_name(), - filebase: meson.project_name() + '-' + api_version, - version: meson.project_version(), - subdirs: meson.project_name(), - requires: deps, - install_dir: get_option('libdir') / 'pkgconfig', + pkgs = [] + foreach dep : deps + pkgs += ['--pkg=' + dep.name()] + endforeach + + gir_tgt = custom_target( + gir, + command: [find_program('python3'), files('gir.py'), meson.project_name(), gir] + + pkgs + + sources, + input: sources, + depends: lib, + output: gir, + install: true, + install_dir: get_option('datadir') / 'gir-1.0', ) custom_target( @@ -78,10 +84,20 @@ if get_option('lib') ], input: lib, output: typelib, - depends: lib, + depends: [lib, gir_tgt], install: true, install_dir: get_option('libdir') / 'girepository-1.0', ) + + import('pkgconfig').generate( + lib, + name: meson.project_name(), + filebase: meson.project_name() + '-' + api_version, + version: meson.project_version(), + subdirs: meson.project_name(), + requires: deps, + install_dir: get_option('libdir') / 'pkgconfig', + ) endif if get_option('cli') diff --git a/lib/mpris/mpris.vala b/lib/mpris/mpris.vala index 0e55a2e..8eaffa5 100644 --- a/lib/mpris/mpris.vala +++ b/lib/mpris/mpris.vala @@ -1,12 +1,24 @@ namespace AstalMpris { -public Mpris get_default() { - return Mpris.get_default(); + /** + * Gets the default singleton Mpris instance. + */ + public Mpris get_default() { + return Mpris.get_default(); + } } -public class Mpris : Object { +/** + * Object that monitors dbus for players to appear and disappear. + */ +public class AstalMpris.Mpris : Object { internal static string PREFIX = "org.mpris.MediaPlayer2."; private static Mpris instance; + private DBusImpl proxy; + + /** + * Gets the default singleton Mpris instance. + */ public static Mpris get_default() { if (instance == null) instance = new Mpris(); @@ -14,15 +26,23 @@ public class Mpris : Object { return instance; } - private DBusImpl proxy; - private HashTable<string, Player> _players = new HashTable<string, Player> (str_hash, str_equal); + /** + * List of currently available players. + */ public List<weak Player> players { owned get { return _players.get_values(); } } - public signal void player_added (Player player); - public signal void player_closed (Player player); + /** + * Emitted when a new mpris Player appears. + */ + public signal void player_added(Player player); + + /** + * Emitted when a Player disappears. + */ + public signal void player_closed(Player player); construct { try { @@ -53,14 +73,15 @@ public class Mpris : Object { var p = new Player(busname); _players.set(busname, p); - p.closed.connect(() => { - player_closed(p); - _players.remove(busname); - notify_property("players"); + p.notify["available"].connect(() => { + if (!p.available) { + player_closed(p); + _players.remove(busname); + notify_property("players"); + } }); player_added(p); notify_property("players"); } } -} diff --git a/lib/mpris/player.vala b/lib/mpris/player.vala index 6764d2b..2776047 100644 --- a/lib/mpris/player.vala +++ b/lib/mpris/player.vala @@ -1,75 +1,185 @@ -namespace AstalMpris { -public class Player : Object { +/** + * Object which tracks players through their mpris dbus interface. + * The most simple way is to use [[email protected]] which tracks every player, + * but [[email protected]] can be constructed for a dedicated players too. + */ +public class AstalMpris.Player : Object { private static string COVER_CACHE = Environment.get_user_cache_dir() + "/astal/mpris"; private IPlayer proxy; - - public signal void appeared () { available = true; } - public signal void closed () { available = false; } + private uint pollid; // periodically notify position // identifiers - public string bus_name { owned get; construct set; } - public bool available { get; private set; } + public string bus_name { owned get; private set; } - // periodically notify position - private uint pollid; + /** + * Indicates if [[email protected]:bus_name] is available on dbus. + */ + public bool available { get; private set; } // mpris + + /** + * Brings the player's user interface to the front + * using any appropriate mechanism available. + * + * The media player may be unable to control how its user interface is displayed, + * or it may not have a graphical user interface at all. + * In this case, the [[email protected]:can_raise] is `false` and this method does nothing. + */ public void raise() { - try { proxy.raise(); } catch (Error error) { critical(error.message); } + try { proxy.raise(); } catch (Error err) { critical(err.message); } } - public void quit() { - try { proxy.quit(); } catch (Error error) { critical(error.message); } + /** + * Causes the media player to stop running. + * + * The media player may refuse to allow clients to shut it down. + * In this case, the [[email protected]:can_quit] property is false and this method does nothing. + */ + public void quit() throws Error { + try { proxy.quit(); } catch (Error err) { critical(err.message); } } + /** + * Indicates if [[email protected]] has any effect. + */ public bool can_quit { get; private set; } + + /** + * Indicates if the player is occupying the fullscreen. This is typically used for videos. + * Use [[email protected]_fullscreen] to toggle fullscreen state. + */ public bool fullscreen { get; private set; } + + /** + * Indicates if [[email protected]_fullscreen] has any effect. + */ public bool can_set_fullscreen { get; private set; } + + /** + * Indicates if [[email protected]] has any effect. + */ public bool can_raise { get; private set; } - public bool has_track_list { get; private set; } + + // TODO: Tracklist interface + // public bool has_track_list { get; private set; } + + /** + * A human friendly name to identify the player. + */ public string identity { owned get; private set; } + + /** + * The base name of a .desktop file + */ public string entry { owned get; private set; } + + /** + * The URI schemes supported by the media player. + * + * This can be viewed as protocols supported by the player in almost all cases. + * Almost every media player will include support for the "file" scheme. + * Other common schemes are "http" and "rtsp". + */ public string[] supported_uri_schemas { owned get; private set; } + + /** + * The mime-types supported by the player. + */ public string[] supported_mime_types { owned get; private set; } + /** + * Toggle [[email protected]:fullscreen] state. + */ public void toggle_fullscreen() { if (!can_set_fullscreen) - critical("can not set fullscreen on " + bus_name); + critical(@"can not set fullscreen on $bus_name"); proxy.fullscreen = !fullscreen; } - // player + /** + * Skips to the next track in the tracklist. + * + * If there is no next track (and endless playback and track repeat are both off), stop playback. + * If [[email protected]:can_go_next] is `false` this method has no effect. + */ public void next() { try { proxy.next(); } catch (Error error) { critical(error.message); } } + /** + * Skips to the previous track in the tracklist. + * + * If there is no previous track (and endless playback and track repeat are both off), stop playback. + * If [[email protected]:can_go_previous] is `false` this method has no effect. + */ public void previous() { try { proxy.previous(); } catch (Error error) { critical(error.message); } } + /** + * Pauses playback. + * + * If playback is already paused, this has no effect. + * If [[email protected]:can_pause] is `false` this method has no effect. + */ public void pause() { try { proxy.pause(); } catch (Error error) { critical(error.message); } } + /** + * Pauses playback. + * + * If playback is already paused, resumes playback. + * If playback is stopped, starts playback. + */ public void play_pause() { try { proxy.play_pause(); } catch (Error error) { critical(error.message); } } + /** + * Stops playback. + * + * If playback is already stopped, this has no effect. + * If [[email protected]:can_control] is `false` this method has no effect. + */ public void stop() { try { proxy.stop(); } catch (Error error) { critical(error.message); } } + /** + * Starts or resumes playback. + * + * If already playing, this has no effect. + * If paused, playback resumes from the current position. + * If [[email protected]:can_play] is `false` this method has no effect. + */ public void play() { try { proxy.play(); } catch (Error error) { critical(error.message); } } + /** + * uri scheme should be an element of [[email protected]:supported_uri_schemas] + * and the mime-type should match one of the elements of [[email protected]:supported_mime_types]. + * + * @param uri Uri of the track to load. + */ public void open_uri(string uri) { try { proxy.open_uri(uri); } catch (Error error) { critical(error.message); } } + /** + * Change [[email protected]:loop_status] from none to track, + * from track to playlist, from playlist to none. + */ public void loop() { + if (loop_status == Loop.UNSUPPORTED) { + critical(@"loop is unsupported by $bus_name"); + return; + } + switch (loop_status) { case Loop.NONE: loop_status = Loop.TRACK; @@ -85,15 +195,21 @@ public class Player : Object { } } + /** + * Toggle [[email protected]:shuffle_status]. + */ public void shuffle() { + if (shuffle_status == Shuffle.UNSUPPORTED) { + critical(@"shuffle is unsupported by $bus_name"); + return; + } + shuffle_status = shuffle_status == Shuffle.ON ? Shuffle.OFF : Shuffle.ON; } - public signal void seeked (int64 position); - - public double _get_position() { + private double _get_position() { try { var reply = proxy.call_sync( "org.freedesktop.DBus.Properties.Get", @@ -130,60 +246,172 @@ public class Player : Object { private Shuffle _shuffle_status = Shuffle.UNSUPPORTED; private double _volume = -1; + /** + * The current loop/repeat status. + */ public Loop loop_status { get { return _loop_status; } set { proxy.loop_status = value.to_string(); } } + /** + * The current playback rate. + */ public double rate { get { return _rate; } set { proxy.rate = value; } } + /** + * The current shuffle status. + */ public Shuffle shuffle_status { get { return _shuffle_status; } set { proxy.shuffle = value == Shuffle.ON; } } + /** + * The current volume level between 0 and 1. + */ public double volume { get { return _volume; } set { proxy.volume = value; } } + /** + * The current position of the track in seconds. + * To get a progress percentage simply divide this with [[email protected]:length]. + */ public double position { get { return _get_position(); } set { _set_position(value); } } + /** + * The current playback status. + */ public PlaybackStatus playback_status { get; private set; } + + /** + * The minimum value which the [[email protected]:rate] can take. + */ public double minimum_rate { get; private set; } + + /** + * The maximum value which the [[email protected]:rate] can take. + */ public double maximum_rate { get; private set; } + + /** + * Indicates if invoking [[email protected]] has effect. + */ public bool can_go_next { get; private set; } + + /** + * Indicates if invoking [[email protected]] has effect. + */ public bool can_go_previous { get; private set; } + + /** + * Indicates if invoking [[email protected]] has effect. + */ public bool can_play { get; private set; } + + /** + * Indicates if invoking [[email protected]] has effect. + */ public bool can_pause { get; private set; } + + /** + * Indicates if setting [[email protected]:position] has effect. + */ public bool can_seek { get; private set; } - public bool can_control { get; private set; } - // metadata - [CCode (notify = false)] - public HashTable<string,Variant> metadata { owned get; private set; } + /** + * Indicates if the player can be controlled with + * methods such as [[email protected]_pause]. + */ + public bool can_control { get; private set; } + /** + * Metadata hashtable of this player. + * In languages that cannot introspect this + * use [[email protected]_meta]. + */ + [CCode (notify = false)] // notified manually in sync + public HashTable<string, Variant> metadata { owned get; private set; } + + /** + * Currently playing track's id. + */ public string trackid { owned get; private set; } + + /** + * Length of the currently playing track in seconds. + */ public double length { get; private set; } + + /** + * The location of an image representing the track or album. + * You should always prefer to use [[email protected]:cover_art]. + */ public string art_url { owned get; private set; } + /** + * Title of the currently playing album. + */ public string album { owned get; private set; } + + /** + * Artists of the currently playing album. + */ public string album_artist { owned get; private set; } + + /** + * Artists of the currently playing track. + */ public string artist { owned get; private set; } + + /** + * Lyrics of the currently playing track. + */ public string lyrics { owned get; private set; } + + /** + * Title of the currently playing track. + */ public string title { owned get; private set; } + + /** + * Composers of the currently playing track. + */ public string composer { owned get; private set; } + + /** + * Comments of the currently playing track. + */ public string comments { owned get; private set; } - // cached cover art + /** + * Path of the cached [[email protected]:art_url]. + */ public string cover_art { owned get; private set; } + /** + * Lookup a key from [[email protected]:metadata]. + * This method is useful for languages that fail to introspect hashtables. + */ + public Variant? get_meta(string key) { + return metadata.lookup(key); + } + + /** + * Construct a Player that tracks a dbus name. For example "org.mpris.MediaPlayer2.spotify". + * The "org.mpris.MediaPlayer2." prefix can be leftout so simply "spotify" would mean the same. + * [[email protected]:available] indicates whether the player is actually running or not. + * + * @param name dbus name of the player. + */ public Player(string name) { Object(bus_name: name.has_prefix("org.mpris.MediaPlayer2.") ? name : "org.mpris.MediaPlayer2." + name); @@ -195,7 +423,7 @@ public class Player : Object { fullscreen = proxy.fullscreen; can_set_fullscreen = proxy.can_set_fullscreen; can_raise = proxy.can_raise; - has_track_list = proxy.has_track_list; + // has_track_list = proxy.has_track_list; identity = proxy.identity; entry = proxy.desktop_entry; supported_uri_schemas = proxy.supported_uri_schemas; @@ -310,10 +538,6 @@ public class Player : Object { } } - public Variant? get_meta(string key) { - return metadata.lookup(key); - } - private string get_str(string key) { if (metadata.get(key) == null) return ""; @@ -349,7 +573,7 @@ public class Player : Object { } } - public void try_proxy() throws Error { + private void try_proxy() throws Error { if (proxy != null) return; @@ -360,13 +584,14 @@ public class Player : Object { ); if (proxy.g_name_owner != null) - appeared(); + available = false; proxy.notify["g-name-owner"].connect(() => { - if (proxy.g_name_owner != null) - appeared(); - else - closed(); + if (proxy.g_name_owner != null) { + available = true; + } else { + available = false; + } }); proxy.g_properties_changed.connect(sync); @@ -387,12 +612,12 @@ public class Player : Object { } } -public enum PlaybackStatus { +public enum AstalMpris.PlaybackStatus { PLAYING, PAUSED, STOPPED; - public static PlaybackStatus from_string(string? str) { + internal static PlaybackStatus from_string(string? str) { switch (str) { case "Playing": return PLAYING; @@ -404,7 +629,7 @@ public enum PlaybackStatus { } } - public string to_string() { + internal string to_string() { switch (this) { case PLAYING: return "Playing"; @@ -417,13 +642,16 @@ public enum PlaybackStatus { } } -public enum Loop { +public enum AstalMpris.Loop { UNSUPPORTED, + /** The playback will stop when there are no more tracks to play. */ NONE, + /** The current track will start again from the begining once it has finished playing. */ TRACK, + /** The playback loops through a list of tracks. */ PLAYLIST; - public static Loop from_string(string? str) { + internal static Loop from_string(string? str) { switch (str) { case "None": return NONE; @@ -436,7 +664,7 @@ public enum Loop { } } - public string? to_string() { + internal string? to_string() { switch (this) { case NONE: return "None"; @@ -450,16 +678,18 @@ public enum Loop { } } -public enum Shuffle { +public enum AstalMpris.Shuffle { UNSUPPORTED, + /** Playback is progressing through a playlist in some other order. */ ON, + /** Playback is progressing linearly through a playlist. */ OFF; - public static Shuffle from_bool(bool b) { + internal static Shuffle from_bool(bool b) { return b ? Shuffle.ON : Shuffle.OFF; } - public string? to_string() { + internal string? to_string() { switch (this) { case OFF: return "Off"; @@ -470,4 +700,3 @@ public enum Shuffle { } } } -} |