pax_global_header00006660000000000000000000000064134465531450014524gustar00rootroot0000000000000052 comment=c69059b3857b94edb40c31605e0624f08d095b4a playerctl-2.0.2/000077500000000000000000000000001344655314500135245ustar00rootroot00000000000000playerctl-2.0.2/.clang-format000066400000000000000000000005201344655314500160740ustar00rootroot00000000000000BasedOnStyle: google AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortBlocksOnASingleLine: false AlwaysBreakBeforeMultilineStrings: false IndentWidth: 4 PointerBindsToType: false ColumnLimit: 100 SpaceBeforeParens: ControlStatements IndentCaseLabels: false playerctl-2.0.2/.gitignore000066400000000000000000000001061344655314500155110ustar00rootroot00000000000000tags .clang_complete /build /mesonbuild /playerctl-fpm .swp .swo .swn playerctl-2.0.2/CHANGELOG.md000066400000000000000000000163741344655314500153500ustar00rootroot00000000000000# Changelog ## Version 2.0.2 Version 2.0.2 contains some minor bugfixes for the CLI and build system. The author would like to inform you as part of my effort to improve media player integration on the Linux Desktop, I have fixed many bugs in Electron based media players that should be available soon (see #40, #81, #35 which were closed recently). **CLI** * Regression: exit 1 when no players are found (#126, #119) * Regression: fix sort order for `--player` command (#112) * Handle nonfile uris in the `open` command (#122) **Build** * Fix documentation of the `--follow` flag (#117) * Update manpage release date at build time (#118) * fix gir build on cross compilation (#120) ## Version 2.0.1 Version 2.0.1 includes new major features and breaking changes to the library and CLI. **CLI** * Add `--ignore-player` flag to ignore specific players (#2) * Add `--follow` flag to block and print updated values when they change (#37, #98, #101) * The `--player` command acts on the first player without `--all-players` (breaking) (#54) * Accept multiple keys for `metadata [key]` command (#68) * `metadata` command has tabular output. (breaking) (#72) * Add `--format [fmt]` for metadata formatting (#73) * Add `duration()` template formatter for formatting durations (#75) * Print player name and instance with format strings (#90) * Add command to get and set `shuffle` status (#92) * Add a command to get and set `loop` status (#99) * Add the `open` command to open a URI with the player (#79) * Fix some errors with utf8 printing (#80) * Skip players from selection when they don't support a command (determined by the `can-*` properties) * Select all player instances with the `--player` and `--ignore-player` command * Print help information to stdout (not stderr) when no arguments are passed **Library** * add `playerctl_list_players()` to public api for listing players (#47) * Implement the "seeked" signal on the player (#94) * Add the "volume" signal on the player (#95) * Deprecate the "play", "pause", and "stopped" signal for a single "status" signal (#96) * Add the `PlayerctlPlayerManager()` class (#100) * Cache and compute the position property (#102) * Remove chaining abilities from the library (breaking) * Library query functions return `NULL` instead of empty string when properties aren't found (breaking) * Deprecate `status` property in favor of the `playback-status` property as an enum * Add library functions for `shuffle` and `loop` status (#92, #99) * Deprecate setting volume via the object properties interface * Fix the "exit" signal * Add properties "can-control", "can-play", "can-pause", "can-seek", "can-go-next", "can-go-previous" * Add the "source" property to determine the source of the player (session or system bus) * Change first keyword arg for `playerctl_player_new()` from `name` to `player_name` (breaking) * Add `playerctl_player_new_for_source()` to select players based on the source (session or system bus) * Add `playerctl_player_new_from_name()` to create a player from a PlayerManager name * `playerctl_player_new()` selects an instance of the `player_name` if found * Add documentation for the entire public library API **Build** * Remove autotools and switch to the meson build system (breaking) (#57) * Fix various compiler warnings (#97) * Remove library version from pkg-config name and add it to the so in the standard way (new pkg-config name is just `playerctl`). ## Version 0.6.1 Version 0.6.1 includes bug fixes and some minor features. * Bugfix: unref of a null player when no players are present * Playerctl now searches the system bus for players * Parse trackid as a string as a workaround for noncompliant players * Various meson fixes ## Version 0.6.0 Version 0.6.0 includes bug fixes and new features. * control multiple players at once by putting commas between the names * add the --all-players option to control all players at once * lib: better cache invalidation strategy for getting properties * bugfix: Set position in fractional seconds * Fix various memory leaks and errors NOTE: This will be the last minor release that uses autotools. Playerctl will switch to the meson build system as of the next minor release. Github releases will have a debian package and an rpm, but these will soon be deprecated as package maintainers create official packages for distros. ## Version 0.5.0 Version 0.5.0 includes some new features. New features: - Add workaround for Spotify to get metadata - Add `position` cli command to query and set position - Add `position` property to Player and method to set position to library ## Version 0.4.2 Version 0.4.2 includes several important bug fixes. - Send `Play` directly instead of a `PlayPause` message depending on player status. This was an exception for Spotify that is no longer needed. - Fix memory errors when an initialization error occurs. ## Version 0.4.1 This version includes a fix to support unicode characters when printing metadata. ## Version 0.4.0 This version adds the following features and bugfixes - List players with cli `-l` option. - Fix a bug in the build for some platforms - Remove claim of mplayer support ## Version 0.3.0 This release includes some major bugfixes and some new features mostly for the library for use in applications. - Add the "stop" library and cli command - Add the "exit" signal - emitted when the player exits - Implement player class memory management - Add version macros The following quirks have been corrected (should not be breaking) - Player "player_name" property getter returns the player name and not the DBus name - Player "stop" event correctly emits "stop" and not "pause" - Add include guards so only `` can be included directly Additional packages available by request ## Version 0.2.1 This minor release adds a pkg-config file and relicenses the code under the LGPL. ## Version 0.2.0 This release adds convenient metadata accessors and improves error handling - Add get_artist method to player - Add get_title method to player - Add get_album method to player - Add get_metadata_prop to player - Add [KEY] option to metadata cli - Bugfix: gracefully handle property access when connection to dbus fails by returning empty properties ## Version 0.1.0 This release adds some new player commands and improves error handling - Add the "next" CLI command and player method used to switch to the next track - Add the "previous" CLI command and player method used to switch to the previous track - Print an error message when no players are found in the CLI and propagate an error on initialization in this case in the library - Print an error message when a command fails in the CLI and propagate an error in this case in the library # Version 0.0.1 Playerctl is a command-line utility and library for controlling media players that implement the [MPRIS](http://specifications.freedesktop.org/mpris-spec/latest/) D-Bus Interface Specification. Playerctl makes it easy to bind player actions, such as play and pause, to media keys. For more advanced users, Playerctl provides an [introspectable](https://wiki.gnome.org/action/show/Projects/GObjectIntrospection) library available in your favorite scripting language that allows more detailed control like the ability to subscribe to media player events or get metadata such as artist and title for the playing track. playerctl-2.0.2/CONTRIBUTORS000066400000000000000000000004211344655314500154010ustar00rootroot00000000000000A list of contributors to playerctl, sorted alphabetically on last names. Please keep this list sorted when adding yourself. Tony Crisci Jente Hidskes Nick Morrott Rasmus Thomsen playerctl-2.0.2/COPYING000066400000000000000000000167431344655314500145720ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. playerctl-2.0.2/README.md000066400000000000000000000252131344655314500150060ustar00rootroot00000000000000# Playerctl For true players only: spotify, vlc, audacious, bmp, xmms2, and others. ## About Playerctl is a command-line utility and library for controlling media players that implement the [MPRIS](http://specifications.freedesktop.org/mpris-spec/latest/) D-Bus Interface Specification. Playerctl makes it easy to bind player actions, such as play and pause, to media keys. You can also get metadata about the playing track such as the artist and title for integration into statusline generators or other command-line tools. For more advanced users, Playerctl provides an [introspectable](https://wiki.gnome.org/action/show/Projects/GObjectIntrospection) library available in your favorite scripting language that allows more detailed control like the ability to subscribe to media player events or get metadata such as artist and title for the playing track. ## Using the CLI ``` playerctl [--version] [--list-all] [--all-players] [--player=NAME] [--ignore-player=IGNORE] [--format=FORMAT] COMMAND ``` Here is a list of available commands: | Command | Description | |:----------------------------:| ------------------------------------------------------------------------------------------------------ | | **`play`** | Command the player to play. | | **`pause`** | Command the player to pause | | **`play-pause`** | Command the player to toggle between play/pause. | | **`stop`** | Command the player to stop. | | **`next`** | Command the player to skip to the next track. | | **`previous`** | Command the player to skip to the previous track. | | **`position [OFFSET][+/-]`** | Command the player to go to the position or seek forward or backward OFFSET in seconds. | | **`volume [LEVEL][+/-]`** | Print or set the volume to LEVEL from 0.0 to 1.0. | | **`status`** | Get the play status of the player. Either "Playing", "Paused", or "Stopped". | | **`metadata [KEY...]`** | Print the metadata for the current track. If KEY is passed, print only those values from the metadata. | | **`open [URI]`** | Command for the player to open a given URI. Can be either a file path or a remote URL. | | **`loop [STATUS]`** | Print or set the loop status. Either "None", "Track", or "Playlist". | | **`shuffle [STATUS]`** | Print or set the shuffle status. Either "On", "Off". | ### Selecting Players to Control Without specifying any players to control, Playerctl will act on the first player it can find. You can list the names of players that are available to control that are running on the system with `playerctl --list-all`. If you'd only like to control certain players, you can pass the names of those players separated by commas with the `--player` flag. Playerctl will select the first instance of a player in that list that supports the command. To control all players in the list, you can use the `--all-players` flag. Similarly, you can ignore players by passing their names with the `--ignore-player` flag. Examples: ```bash # Command the first instance of VLC to play playerctl --player=vlc play # Command all players to stop playerctl --all-players stop # Command VLC to go to the next track if it's running. If it's not, send the # command to Spotify. playerctl --player=vlc,spotify next # Get the status of the first player that is not Gwenview. playerctl --ignore-player=Gwenview status ``` ### Printing Properties and Metadata You can pass a format string with the `--format` argument to print properties in a specific format. Pass the variable you want to print in the format string between double braces like `{{ VARIABLE }}`. The variables available are either the name of the query command, or anything in the metadata map which can be viewed with `playerctl metadata`. You can use this to integrate playerctl into a statusline generator. For a simple "now playing" banner: ```bash playerctl metadata --format "Now playing: {{ artist }} - {{ album }} - {{ title }}" # prints 'Now playing: Lana Del Rey - Born To Die - Video Games' ``` Included in the template language are some helper functions for common formatting that you can call on template variables. ```bash playerctl metadata --format "Total length: {{ duration(mpris:length) }}" # prints 'Total length: 3:23' playerctl position --format "At position: {{ duration(position) }}" # prints 'At position: 1:16' playerctl metadata --format "Artist in lowercase: {{ lc(artist) }}" # prints 'Artist in lowercase: lana del rey' playerctl status --format "STATUS: {{ uc(status) }}" # prints 'STATUS: PLAYING' ``` | Function | Argument | Description | | ---------- | --------------- | ---------------------------------------------------------------------- | | `lc` | string | Convert the string to lowercase. | | `uc` | string | Convert the string to uppercase. | | `duration` | int | Convert the duration to hh:mm:ss format. | | `emoji` | status, volume | (experimental) Try to convert the variable to an emoji representation. | ### Following changes You can pass the `--follow` flag to query commands to block, wait for players to connect, and print the query whenever it changes. If players are passed with `--player`, players earlier in the list will be preferred in the order they appear unless `--all-players` is passed. When no player can support the query, such as when all the players exit, a newline will be printed. For example, to be notified of information about the latest currently playing track for your media players, use: ```bash playerctl metadata --format '{{ playerName }}: {{ artist }} - {{ title }} {{ duration(position) }}|{{ duration(mpris:length) }}' --follow ``` ## Using the Library To use a scripting library, find your favorite language from [this list](https://wiki.gnome.org/Projects/GObjectIntrospection/Users) and install the bindings library. Documentation for the library is hosted [here](https://dubstepdish.com/playerctl). For examples on how to use the library, see the [examples](https://github.com/acrisci/playerctl/blob/master/examples) folder. ### Example Python Script This example uses the [Python bindings](https://wiki.gnome.org/action/show/Projects/PyGObject). ```python #!/usr/bin/env python3 from gi.repository import Playerctl, GLib player = Playerctl.Player('vlc') def on_metadata(player, metadata): if 'xesam:artist' in metadata.keys() and 'xesam:title' in metadata.keys(): print('Now playing:') print('{artist} - {title}'.format( artist=metadata['xesam:artist'][0], title=metadata['xesam:title'])) def on_play(player, status): print('Playing at volume {}'.format(player.props.volume)) def on_pause(player, status): print('Paused the song: {}'.format(player.get_title())) player.connect('status::playing', on_play) player.connect('status::paused', on_pause) player.connect('metadata', on_metadata) # start playing some music player.play() if player.get_artist() == 'Lana Del Rey': # I meant some good music! player.next() # wait for events main = GLib.MainLoop() main.run() ``` For a more complete example which is capable of listening to when players start and exit, see [player-manager.py](https://github.com/acrisci/playerctl/blob/master/examples/player-manager.py) from the official examples. ## Troubleshooting Some players like Spotify require certain DBus environment variables to be set which are normally set within the session manager. If you're not using a session manager or it does not set these variables automatically (like `xinit`), launch your desktop environment wrapped in a `dbus-launch` command. For example, in your `.xinitrc` file, use this to start your WM: ``` exec dbus-launch --autolaunch=$(cat /var/lib/dbus/machine-id) i3 ``` ## Installing First, check and see if the library is available from your package manager (if it is not, get someone to host a package for you) and also check the [releases](https://github.com/acrisci/playerctl/releases) page on github. Using the cli and library requires [GLib](https://developer.gnome.org/glib/) (which is a dependency of almost all of these players as well, so you probably already have it). You can use the library in almost any programming language with the associated [introspection binding library](https://wiki.gnome.org/Projects/GObjectIntrospection/Users). Additionally, you also need the following build dependencies: [gobject-introspection](https://wiki.gnome.org/action/show/Projects/GObjectIntrospection) for building introspection data (configurable with the `introspection` meson option) [gtk-doc](http://www.gtk.org/gtk-doc/) for building documentation (configurable with the `gtk-doc` meson option) Fedora users also need to install `redhat-rpm-config` To generate and build the project to contribute to development and install playerctl to `/`: ``` meson mesonbuild sudo ninja -C mesonbuild install ``` Note that you need `meson >= 0.46.0` installed. In case your distro only has an older version of meson in its repository you can install the newest version via pip: ``` pip3 install meson ``` Also keep in mind that gtk-doc and gobject-introspection are enabled by default, you can disable them with `-Dintrospection=false` and `-Dgtk-doc=false`. If you don't want to install playerctl to `/` you can install it elsewhere by exporting `DESTDIR` before invoking ninja, e.g.: ``` export PREFIX="/usr/local" meson --prefix="${PREFIX}" --libdir="${PREFIX}/lib" mesonbuild export DESTDIR="$(pwd)/install" ninja -C mesonbuild install ``` You can use it later on by exporting the following variables: ``` export LD_LIBRARY_PATH="$DESTDIR/${PREFIX}/lib/:$LD_LIBRARY_PATH" export GI_TYPELIB_PATH="$DESTDIR/${PREFIX}/lib/:$GI_TYPELIB_PATH" export PATH="$DESTDIR/${PREFIX}/bin:$PATH" ``` ## License This work is available under the GNU Lesser General Public License (See COPYING). Copyright © 2014, Tony Crisci playerctl-2.0.2/doc/000077500000000000000000000000001344655314500142715ustar00rootroot00000000000000playerctl-2.0.2/doc/meson.build000066400000000000000000000006331344655314500164350ustar00rootroot00000000000000man_page = configure_file( input: 'playerctl.1.in', output: 'playerctl.1', configuration: version_conf, ) install_man(man_page) if get_option('gtk-doc') gtkdoc = find_program('gtkdoc-scan', required: false) if not gtkdoc.found() error('You need to have gtk-doc installed to generate docs. Disable it with `-Dgtk-doc=false` if you don\'t want to build docs') endif subdir('reference') endif playerctl-2.0.2/doc/playerctl.1.in000066400000000000000000000120721344655314500167610ustar00rootroot00000000000000.TH PLAYERCTL "1" "@PLAYERCTL_RELEASE_MONTH@" "playerctl @PLAYERCTL_VERSION@" "User Commands" .SH NAME \fBplayerctl\fR \- utility to control media players via MPRIS .SH SYNOPSIS .TP \fBplayerctl\fR [\fIOPTION\fR] \fICOMMAND\fR .SH DESCRIPTION .RE \fBplayerctl\fR is a command line utility to control MPRIS-enabled media\& players. In addition to offering play/pause/stop control, \fBplayerctl\fR\& also offers previous/next track support, the ability to seek backward/forward\& in a track, and volume control. \fBplayerctl\fR also supports displaying metadata (e.g. artist/title/album) for the\& current track, and showing the status of the player. .PP Players that can be controlled using \fBplayerctl\fR include audacious, cmus,\& mopidy, mpd, quod libet, rhythmbox, vlc and xmms2. However, any player that implements\& the MPRIS interface specification should be able to be controlled using \fBplayerctl\fR. .SH OPTIONS .TP \fB\-p\fR, \fB\-\-player\fR=\fI\,NAME\/\fR The name or comma-separated list of the players to control (default: first available player) .TP \fB\-i\fR, \fB\-\-ignore\-player\fR=\fI\,NAME\/\fR The name or comma-separated list of the players to ignore .TP \fB\-f\fR, \fB\-\-format\fR=\fI\,FORMAT\/\fR A format string for printing properties and metadata .TP \fB\-F\fR, \fB\-\-follow\fR Block and output the updated query when it changes .TP \fB\-l\fR, \fB\-\-list\-all\fR List the names of running players that can be controlled .TP \fB\-a\fR, \fB\-\-all\-players\fR Apply command to all available players .TP \fB\-h\fR, \fB\-\-help\fR Print this help, then exit .TP \fB\-V\fR, \fB\-\-version\fR Print version number, then exit .SH COMMANDS .TP \fBstatus\fR Get the current status of the player .TP \fBplay\fR Command the player to play .TP \fBpause\fR Command the player to pause .TP \fBplay\-pause\fR Command the player to toggle between play/pause .TP \fBstop\fR Command the player to stop .TP \fBnext\fR Command the player to skip to the next track .TP \fBprevious\fR Command the player to skip to the previous track .TP \fBposition\fR [\fIOFFSET\fR][\fI+\fR|\fI\-\fR] Print the position of the current track in seconds. With \fIOFFSET\fR specified, seek to \fIOFFSET\fR seconds from the start of the current track. With the optional [\fI+\fR|\fI\-\fR] appended, seek forward/backward \fIOFFSET\fR seconds from the current position. .TP \fBvolume\fR [\fILEVEL\fR][\fI+\fR|\fI\-\fR] Print the player's volume scaled from 0.0 (0%) to 1.0 (100%). With \fILEVEL\fR specified, set the player's volume to \fILEVEL\fR. With the optional [\fI+\fR|\fI\-\fR] appended, increase/decrease the player's volume by \fILEVEL\fR. .TP \fBmetadata\fR [\fIKEY\fR] Print available metadata information for the current track. When \fIKEY\fR is specified, print the value of \fIKEY\fR. .TP \fBopen\fR [\fIURI\fR] Open the given \fIURI\fR in the player. The \fIURI\fR may be the name of a file or an external URL. .TP \fBshuffle\fR [{\fIOn\fR|\fIOff\fR}] Print the shuffle status of the player. With the shuffle status specified, set shuffle to either \fIOn\fR or \fIOff\fR. .TP \fBloop\fR [{\fINone\fR|\fITrack\fR|\fIPlaylist\fR}] Print the loop status of the player. With the loop status specified, set the loop status to either \fINone\fR to not loop, \fITrack\fR to loop the current track, or \fIPlaylist\fR to loop the current playlist. .SH FORMAT STRINGS A format string can be given with the \fB--format\fR argument to print properties and metadata in a particular format. Variable names between curly braces in the form of \fB{{\fR \fIVARIABLE\fR \fB}}\fR will be expanded to their values. The available variables are the names of the commands that print properties or any of the metadata keys that can be viewed with the \fBmetadata\fR command. The name of the player is also available with the \fBplayerName\fR variable. Several helper functions are available in the template language to transform expanded values which can be called in the form \fB{{\fR \fIHELPER\fR(\fIVARIABLE\fR) \fB}}\fR. The available helper functions are: .TP \fBlc\fR Convert the value to lowercase .TP \fBuc\fR Convert the value to uppercase .TP \fBduration\fR When called on a duration such as \fBposition\fR or \fBmpris:length\fR, convert the duration to \fBhh:mm:ss\fR form .SH EXAMPLES .TP To print the player name, playback status in lowercase, and position and length in human readable form: \fBplayerctl metadata --format '{{playerName}}: {{lc(status)}} {{duration(position)}}|{{duration(mpris:length)}}' .SH SEE ALSO .TP Online API documentation: https://dubstepdish.com/playerctl .TP GObject Introspection language bindings: https://wiki.gnome.org/Projects/GObjectIntrospection/Users .SH REPORTING BUGS .TP Please review and report bugs to https://github.com/acrisci/playerctl/issues .SH AVAILABILITY .TP The latest version of \fBplayerctl\fR is available at https://github.com/acrisci/playerctl .SH AUTHOR .TP This manual page was created by Nick Morrott for the Debian GNU/Linux system, but may be used by others. .SH COPYRIGHT .TP Copyright © 2014, Tony Crisci. .TP This work is made available under the GNU Lesser General Public License 3.0. playerctl-2.0.2/doc/reference/000077500000000000000000000000001344655314500162275ustar00rootroot00000000000000playerctl-2.0.2/doc/reference/meson.build000066400000000000000000000020101344655314500203620ustar00rootroot00000000000000glib_prefix = dependency('glib-2.0').get_pkgconfig_variable('prefix') glib_docpath = join_paths(glib_prefix, 'share', 'gtk-doc', 'html') configure_file( input : 'version.xml.in', output : 'version.xml', configuration : version_conf, ) gnome.gtkdoc( meson.project_name(), src_dir: join_paths(meson.source_root(), 'playerctl'), dependencies: [ glib_dep, playerctl_shared_link, ], mkdb_args: [ '--output-format=xml', '--name-space=' + meson.project_name(), ], scan_args: '--deprecated-guards="PLAYERCTL_DISABLE_DEPRECATED"', gobject_typesfile: join_paths(meson.current_build_dir(), meson.project_name() + '.types'), fixxref_args: [ '--extra-dir=@0@'.format(join_paths(glib_docpath, 'gobject')), '--extra-dir=@0@'.format(join_paths(glib_docpath, 'gio')), '--extra-dir=@0@'.format(glib_docpath), ], main_sgml: 'playerctl-docs.xml', ignore_headers: [ 'playerctl.h', 'playerctl-generated.h', 'playerctl-common.h', 'playerctl-formatter.h', ], install: true, ) playerctl-2.0.2/doc/reference/playerctl-docs.xml000066400000000000000000000026241344655314500217020ustar00rootroot00000000000000 ]> Playerctl Reference Manual for Playerctl &version;. The latest version of this documentation can be found on-line at http://dubstepdish.com/playerctl/. Playerctl Object Hierarchy API Index Index of deprecated API playerctl-2.0.2/doc/reference/version.xml.in000066400000000000000000000000241344655314500210370ustar00rootroot00000000000000@PLAYERCTL_VERSION@ playerctl-2.0.2/examples/000077500000000000000000000000001344655314500153425ustar00rootroot00000000000000playerctl-2.0.2/examples/basic-example.py000077500000000000000000000015231344655314500204320ustar00rootroot00000000000000#!/usr/bin/env python3 from gi.repository import Playerctl, GLib player = Playerctl.Player() def on_metadata(player, metadata): if 'xesam:artist' in metadata.keys() and 'xesam:title' in metadata.keys(): print('Now playing:') print('{artist} - {title}'.format( artist=metadata['xesam:artist'][0], title=metadata['xesam:title'])) def on_play(player, status): print('Playing at volume {}'.format(player.props.volume)) def on_pause(player, status): print('Paused the song: {}'.format(player.get_title())) player.connect('playback-status::playing', on_play) player.connect('playback-status::paused', on_pause) player.connect('metadata', on_metadata) # start playing some music player.play() if player.get_artist() == 'Lana Del Rey': player.next() # wait for events main = GLib.MainLoop() main.run() playerctl-2.0.2/examples/player-manager.py000077500000000000000000000021671344655314500206310ustar00rootroot00000000000000#!/usr/bin/env python3 from gi.repository import Playerctl, GLib manager = Playerctl.PlayerManager() def on_play(player, status, manager): print('player is playing: {}'.format(player.props.player_name)) def on_metadata(player, metadata, manager): keys = metadata.keys() if 'xesam:artist' in keys and 'xesam:title' in keys: print('{} - {}'.format(metadata['xesam:artist'][0], metadata['xesam:title'])) def init_player(name): # choose if you want to manage the player based on the name if name.name in ['vlc', 'cmus']: player = Playerctl.Player.new_from_name(name) player.connect('playback-status::playing', on_play, manager) player.connect('metadata', on_metadata, manager) manager.manage_player(player) def on_name_appeared(manager, name): init_player(name) def on_player_vanished(manager, player): print('player has exited: {}'.format(player.props.player_name)) manager.connect('name-appeared', on_name_appeared) manager.connect('player-vanished', on_player_vanished) for name in manager.props.player_names: init_player(name) main = GLib.MainLoop() main.run() playerctl-2.0.2/fpm-packages.sh000077500000000000000000000035761344655314500164340ustar00rootroot00000000000000#!/bin/sh set -e PROJECT_ROOT=${PWD} FPM_DIR=${PWD}/playerctl-fpm DEB_DIR=${FPM_DIR}/deb RPM_DIR=${FPM_DIR}/rpm MESON_DIR=${FPM_DIR}/build # sanity check if [[ ! -f playerctl/playerctl.h ]]; then echo 'You must run this from the playerctl project directory' exit 1 fi packages=(fpm rpm dpkg) for pkg in ${packages[@]}; do if ! hash ${pkg}; then echo "you need ${pkg} to package playerctl" exit 127 fi done rm -rf ${FPM_DIR} mkdir -p ${FPM_DIR} fpm_deb() { cd ${PROJECT_ROOT} meson ${DEB_DIR}/build --prefix=/usr --libdir=/usr/lib DESTDIR=${DEB_DIR}/install ninja -C ${DEB_DIR}/build install VERSION=`LD_LIBRARY_PATH=${DEB_DIR}/install/usr/lib ${DEB_DIR}/install/usr/bin/playerctl -v | sed s/^v// | sed s/-.*//` cd ${DEB_DIR}/install fpm -s dir -t deb -n playerctl -v ${VERSION} \ -p playerctl-VERSION_ARCH.deb \ -d "libglib2.0-0" \ usr/include usr/lib usr/bin usr/share echo -e "\nDEBIAN PACKAGE CONTENTS" echo -e "-----------------------" dpkg -c ${DEB_DIR}/install/playerctl-${VERSION}_amd64.deb mv ${DEB_DIR}/install/playerctl-${VERSION}_amd64.deb ${FPM_DIR} cd - &> /dev/null } function fpm_rpm() { cd ${PROJECT_ROOT} meson ${RPM_DIR}/build --prefix=/usr --libdir=/usr/lib64 DESTDIR=${RPM_DIR}/install ninja -C ${RPM_DIR}/build install VERSION=`LD_LIBRARY_PATH=${RPM_DIR}/install/usr/lib64 ${RPM_DIR}/install/usr/bin/playerctl -v | sed s/^v// | sed s/-.*//` cd ${RPM_DIR}/install fpm -s dir -t rpm -n playerctl -v ${VERSION} \ -p playerctl-VERSION_ARCH.rpm \ -d "glib2" \ usr/include usr/lib64 usr/bin usr/share echo -e "\nRPM PACKAGE CONTENTS" echo -e "--------------------" rpm -qlp ${RPM_DIR}/install/playerctl-${VERSION}_x86_64.rpm mv ${RPM_DIR}/install/playerctl-${VERSION}_x86_64.rpm ${FPM_DIR} cd - &> /dev/null } fpm_deb fpm_rpm playerctl-2.0.2/meson.build000066400000000000000000000016001344655314500156630ustar00rootroot00000000000000project( 'playerctl', 'c', version: '2.0.2', meson_version: '>=0.46.0' ) release_month = 'March 2019' gnome = import('gnome') pkgconfig = import('pkgconfig') version_conf = configuration_data() playerctl_version = meson.project_version().split('-')[0] version_array = playerctl_version.split('.') playerctl_major_version = version_array[0] version_conf.set( 'PLAYERCTL_VERSION', meson.project_version(), ) version_conf.set( 'PLAYERCTL_MAJOR_VERSION', playerctl_major_version.to_int(), ) version_conf.set( 'PLAYERCTL_MINOR_VERSION', version_array[1].to_int(), ) version_conf.set( 'PLAYERCTL_MICRO_VERSION', version_array[2].to_int(), ) version_conf.set( 'PLAYERCTL_RELEASE_MONTH', release_month, ) gobject_dep = dependency('gobject-2.0', version: '>=2.38') gio_dep = dependency('gio-unix-2.0') glib_dep = dependency('glib-2.0') subdir('playerctl') subdir('doc') playerctl-2.0.2/meson_options.txt000066400000000000000000000002401344655314500171550ustar00rootroot00000000000000option('gtk-doc', type: 'boolean', value: true, description: 'build docs') option('introspection', type: 'boolean', value: true, description: 'build gir data') playerctl-2.0.2/playerctl/000077500000000000000000000000001344655314500155235ustar00rootroot00000000000000playerctl-2.0.2/playerctl/meson.build000066400000000000000000000056571344655314500177020ustar00rootroot00000000000000playerctl_version_header = configure_file( input: 'playerctl-version.h.in', output: 'playerctl-version.h', configuration: version_conf, ) # Include the just generated playerctl_version header configuration_inc = include_directories('..') playerctl_generated = gnome.gdbus_codegen( 'playerctl-generated', 'mpris-dbus-interface.xml', ) headers = [ 'playerctl.h', 'playerctl-player.h', 'playerctl-player-manager.h', 'playerctl-player-name.h', playerctl_version_header, ] playerctl_sources = [ 'playerctl-player-name.c', 'playerctl-formatter.c', 'playerctl-player.c', 'playerctl-common.c', 'playerctl-player-manager.c', playerctl_generated, ] # Allow including playerctl.h during compilation add_global_arguments( '-DPLAYERCTL_COMPILATION', language: 'c', ) enums = gnome.mkenums_simple( 'playerctl-enum-types', sources: headers, install_header: true, install_dir: join_paths(get_option('includedir'), 'playerctl') ) deps = [ gobject_dep, gio_dep, ] playerctl_lib = both_libraries( 'playerctl', playerctl_sources, enums, dependencies: deps, include_directories: configuration_inc, version: playerctl_version, install: true, ) # Required for linking against the shared lib we just built playerctl_shared_link = declare_dependency( link_with: playerctl_lib.get_shared_lib(), dependencies: deps, ) playerctl_executable = executable( 'playerctl', 'playerctl-cli.c', enums, dependencies: playerctl_shared_link, include_directories: configuration_inc, install: true, ) install_headers( headers, install_dir: join_paths(get_option('includedir'), 'playerctl'), ) if get_option('introspection') # The below isn't strictly required, since meson checks for gobject-introspection anyway when # we call gnome.generate_gir. However, doing it this way we have a little nicer error reporting # in case the user enabled instropection but doesn't have gobject-introspection installed. introspection_dep = dependency('gobject-introspection-1.0', required: false) if not introspection_dep.found() error('You need to have gobject-introspection installed to generate Gir data. Disable it with `-Dintrospection=false` if you don\'t want to build it') endif gnome.generate_gir( playerctl_lib.get_shared_lib(), sources: [ enums, 'playerctl-player-name.c', 'playerctl-player-name.h', 'playerctl-player-manager.c', 'playerctl-player-manager.h', 'playerctl-player.c', 'playerctl-player.h', ], extra_args : [ '-DPLAYERCTL_COMPILATION' ], nsversion: playerctl_major_version + '.0', namespace: 'Playerctl', includes: ['GObject-2.0'], install: true, ) endif pkgconfig.generate( libraries: playerctl_lib.get_shared_lib(), subdirs: 'playerctl', version: meson.project_version(), name: 'Playerctl', filebase: 'playerctl', description: 'A C library for MPRIS players', requires: ['gobject-2.0'], requires_private: 'gio-2.0', ) playerctl-2.0.2/playerctl/mpris-dbus-interface.xml000066400000000000000000000062711344655314500222760ustar00rootroot00000000000000 playerctl-2.0.2/playerctl/playerctl-cli.c000066400000000000000000001140651344655314500204420ustar00rootroot00000000000000/* * This file is part of playerctl. * * playerctl is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * playerctl 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 Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License * along with playerctl If not, see . * * Copyright © 2014, Tony Crisci and contributors. */ #include #include #include #include #include #include #include #include #include "playerctl-common.h" #include "playerctl-formatter.h" #define LENGTH(array) (sizeof array / sizeof array[0]) G_DEFINE_QUARK(playerctl-cli-error-quark, playerctl_cli_error); /* The CLI will exit with this exit status */ static gint exit_status = 0; /* A comma separated list of players to control. */ static gchar *player_arg = NULL; /* A comma separated list of players to ignore. */ static gchar *ignore_player_arg = NULL; /* If true, control all available media players */ static gboolean select_all_players; /* If true, list all available players' names and exit. */ static gboolean list_all_players_and_exit; /* If true, print the version and exit. */ static gboolean print_version_and_exit; /* The commands passed on the command line, filled in via G_OPTION_REMAINING. */ static gchar **command_arg = NULL; /* A format string for printing properties and metadata */ static gchar *format_string_arg = NULL; /* The formatter for the format string argument if present */ static PlayerctlFormatter *formatter = NULL; /* Block and follow the command */ static gboolean follow = FALSE; /* The main loop for the follow command */ static GMainLoop *main_loop = NULL; /* The last output printed by the cli */ static gchar *last_output = NULL; /* The manager of all the players we connect to */ static PlayerctlPlayerManager *manager = NULL; /* List of player names parsed from the --player arg */ static GList *player_names = NULL; /* List of ignored player names passed from the --ignore-player arg*/ static GList *ignored_player_names = NULL; /* forward definitions */ static void managed_players_execute_command(GError **error); /* * Sometimes players may notify metadata when nothing we care about has * changed, so we have this to avoid printing duplicate lines in follow * mode. Prints a newline if output is NULL which denotes that the property has * been cleared. Only use this in follow mode. * * This consumes the output string. */ static void cli_print_output(gchar *output) { if (output == NULL && last_output == NULL) { return; } if (output == NULL) { output = g_strdup("\n"); } if (g_strcmp0(output, last_output) == 0) { g_free(output); return; } printf("%s", output); fflush(stdout); g_free(last_output); last_output = output; } struct playercmd_args { gchar **argv; gint argc; }; /* Arguments given to the player for the follow command */ static struct playercmd_args *playercmd_args = NULL; static struct playercmd_args *playercmd_args_create(gchar **argv, gint argc) { struct playercmd_args *user_data = calloc(1, sizeof(struct playercmd_args)); user_data->argc = argc; user_data->argv = g_strdupv(argv); return user_data; } static void playercmd_args_destroy(struct playercmd_args *data) { if (data == NULL) { return; } g_strfreev(data->argv); free(data); return; } static gchar *get_metadata_formatted(PlayerctlPlayer *player, GError **error) { GError *tmp_error = NULL; GVariant *metadata = NULL; g_return_val_if_fail(formatter != NULL, NULL); g_object_get(player, "metadata", &metadata, NULL); if (metadata == NULL) { return NULL; } if (g_variant_n_children(metadata) == 0) { g_variant_unref(metadata); return NULL; } GVariantDict *context = playerctl_formatter_default_template_context(formatter, player, metadata); gchar *result = playerctl_formatter_expand_format(formatter, context, &tmp_error); if (tmp_error) { g_variant_unref(metadata); g_variant_dict_unref(context); g_propagate_error(error, tmp_error); return NULL; } g_variant_unref(metadata); g_variant_dict_unref(context); return result; } static gboolean playercmd_play(PlayerctlPlayer *player, gchar **argv, gint argc, gchar **output, GError **error) { GError *tmp_error = NULL; gboolean can_play = FALSE; g_object_get(player, "can-play", &can_play, NULL); if (!can_play) { return FALSE; } playerctl_player_play(player, &tmp_error); if (tmp_error) { g_propagate_error(error, tmp_error); return FALSE; } return TRUE; } static gboolean playercmd_pause(PlayerctlPlayer *player, gchar **argv, gint argc, gchar **output, GError **error) { GError *tmp_error = NULL; gboolean can_pause = FALSE; g_object_get(player, "can-pause", &can_pause, NULL); if (!can_pause) { return FALSE; } playerctl_player_pause(player, &tmp_error); if (tmp_error) { g_propagate_error(error, tmp_error); return FALSE; } return TRUE; } static gboolean playercmd_play_pause(PlayerctlPlayer *player, gchar **argv, gint argc, gchar **output, GError **error) { GError *tmp_error = NULL; gboolean can_play = FALSE; g_object_get(player, "can-play", &can_play, NULL); if (!can_play) { return FALSE; } playerctl_player_play_pause(player, &tmp_error); if (tmp_error) { g_propagate_error(error, tmp_error); return FALSE; } return TRUE; } static gboolean playercmd_stop(PlayerctlPlayer *player, gchar **argv, gint argc, gchar **output, GError **error) { GError *tmp_error = NULL; // XXX there is no CanStop propery on the mpris player. CanPlay is supposed // to indicate whether there is a current track. If there is no current // track, then I assume the player cannot stop. gboolean can_play = FALSE; g_object_get(player, "can-play", &can_play, NULL); if (!can_play) { return FALSE; } playerctl_player_stop(player, &tmp_error); if (tmp_error) { g_propagate_error(error, tmp_error); return FALSE; } return TRUE; } static gboolean playercmd_next(PlayerctlPlayer *player, gchar **argv, gint argc, gchar **output, GError **error) { GError *tmp_error = NULL; gboolean can_go_next = FALSE; g_object_get(player, "can-go-next", &can_go_next, NULL); if (!can_go_next) { return FALSE; } playerctl_player_next(player, &tmp_error); if (tmp_error) { g_propagate_error(error, tmp_error); return FALSE; } return TRUE; } static gboolean playercmd_previous(PlayerctlPlayer *player, gchar **argv, gint argc, gchar **output, GError **error) { GError *tmp_error = NULL; gboolean can_go_previous = FALSE; g_object_get(player, "can-go-previous", &can_go_previous, NULL); if (!can_go_previous) { return FALSE; } playerctl_player_previous(player, &tmp_error); if (tmp_error) { g_propagate_error(error, tmp_error); return FALSE; } return TRUE; } static gboolean playercmd_open(PlayerctlPlayer *player, gchar **argv, gint argc, gchar **output, GError **error) { const gchar *uri = argv[1]; GError *tmp_error = NULL; gboolean can_control = FALSE; g_object_get(player, "can-control", &can_control, NULL); if (!can_control) { return FALSE; } if (uri) { GFile *file = g_file_new_for_commandline_arg(uri); gboolean exists = g_file_query_exists(file, NULL); gchar *full_uri = NULL; if (exists) { // it's a file, so pass the absolute path of the file full_uri = g_file_get_uri(file); } else { // it may be some other scheme, just pass the uri directly full_uri = g_strdup(uri); } playerctl_player_open(player, full_uri, &tmp_error); g_free(full_uri); g_object_unref(file); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); return FALSE; } } return TRUE; } static gboolean playercmd_position(PlayerctlPlayer *player, gchar **argv, gint argc, gchar **output, GError **error) { const gchar *position = argv[1]; gint64 offset; GError *tmp_error = NULL; if (position) { if (format_string_arg != NULL) { g_set_error(error, playerctl_cli_error_quark(), 1, "format strings are not supported on command functions."); return FALSE; } char *endptr = NULL; offset = 1000000.0 * strtod(position, &endptr); if (position == endptr) { g_set_error(error, playerctl_cli_error_quark(), 1, "Could not parse position as a number: %s\n", position); return FALSE; } gboolean can_seek = FALSE; g_object_get(player, "can-seek", &can_seek, NULL); if (!can_seek) { return FALSE; } size_t last = strlen(position) - 1; if (position[last] == '+' || position[last] == '-') { if (position[last] == '-') { offset *= -1; } playerctl_player_seek(player, offset, &tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); return FALSE; } } else { playerctl_player_set_position(player, offset, &tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); return FALSE; } } } else { if (formatter != NULL) { GVariantDict *context = playerctl_formatter_default_template_context(formatter, player, NULL); gchar *formatted = playerctl_formatter_expand_format(formatter, context, &tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); g_variant_dict_unref(context); return FALSE; } *output = g_strdup_printf("%s\n", formatted); g_free(formatted); g_variant_dict_unref(context); } else { g_object_get(player, "position", &offset, NULL); *output = g_strdup_printf("%f\n", (double)offset / 1000000.0); } } return TRUE; } static gboolean playercmd_volume(PlayerctlPlayer *player, gchar **argv, gint argc, gchar **output, GError **error) { GError *tmp_error = NULL; const gchar *volume = argv[1]; gdouble level; if (volume) { if (format_string_arg != NULL) { g_set_error(error, playerctl_cli_error_quark(), 1, "format strings are not supported on command functions."); return FALSE; } char *endptr = NULL; size_t last = strlen(volume) - 1; if (volume[last] == '+' || volume[last] == '-') { gdouble adjustment = strtod(volume, &endptr); if (volume == endptr) { g_set_error(error, playerctl_cli_error_quark(), 1, "Could not parse volume as a number: %s\n", volume); return FALSE; } if (volume[last] == '-') { adjustment *= -1; } g_object_get(player, "volume", &level, NULL); level += adjustment; } else { level = strtod(volume, &endptr); if (volume == endptr) { g_set_error(error, playerctl_cli_error_quark(), 1, "Could not parse volume as a number: %s\n", volume); return FALSE; } } gboolean can_control = FALSE; g_object_get(player, "can-control", &can_control, NULL); if (!can_control) { return FALSE; } playerctl_player_set_volume(player, level, &tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); return FALSE; } } else { g_object_get(player, "volume", &level, NULL); if (formatter != NULL) { GVariantDict *context = playerctl_formatter_default_template_context(formatter, player, NULL); gchar *formatted = playerctl_formatter_expand_format(formatter, context, &tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); return FALSE; } *output = g_strdup_printf("%s\n", formatted); g_free(formatted); } else { *output = g_strdup_printf("%f\n", level); } } return TRUE; } static gboolean playercmd_status(PlayerctlPlayer *player, gchar **argv, gint argc, gchar **output, GError **error) { GError *tmp_error = NULL; if (formatter != NULL) { GVariantDict *context = playerctl_formatter_default_template_context(formatter, player, NULL); gchar *formatted = playerctl_formatter_expand_format(formatter, context, &tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); g_variant_dict_unref(context); return FALSE; } *output = g_strdup_printf("%s\n", formatted); g_variant_dict_unref(context); g_free(formatted); } else { PlayerctlPlaybackStatus status = 0; g_object_get(player, "playback-status", &status, NULL); const gchar *status_str = pctl_playback_status_to_string(status); assert(status_str != NULL); *output = g_strdup_printf("%s\n", status_str); } return TRUE; } static gboolean playercmd_shuffle(PlayerctlPlayer *player, gchar **argv, gint argc, gchar **output, GError **error) { GError *tmp_error = NULL; if (argc > 1) { gchar *status_str = argv[1]; gboolean status = FALSE; if (strcasecmp(status_str, "on") == 0) { status = TRUE; } else if (strcasecmp(status_str, "off") == 0) { status = FALSE; } else { g_set_error(error, playerctl_cli_error_quark(), 1, "Got unknown loop status: '%s' (expected 'none', " "'playlist', or 'track').", argv[1]); return FALSE; } gboolean can_control = FALSE; g_object_get(player, "can-control", &can_control, NULL); if (!can_control) { return FALSE; } playerctl_player_set_shuffle(player, status, &tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); return FALSE; } } else { if (formatter != NULL) { GVariantDict *context = playerctl_formatter_default_template_context(formatter, player, NULL); gchar *formatted = playerctl_formatter_expand_format(formatter, context, &tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); g_variant_dict_unref(context); return FALSE; } *output = g_strdup_printf("%s\n", formatted); g_variant_dict_unref(context); g_free(formatted); } else { gboolean status = FALSE; g_object_get(player, "shuffle", &status, NULL); if (status) { *output = g_strdup("On\n"); } else { *output = g_strdup("Off\n"); } } } return TRUE; } static gboolean playercmd_loop(PlayerctlPlayer *player, gchar **argv, gint argc, gchar **output, GError **error) { GError *tmp_error = NULL; if (argc > 1) { gchar *status_str = argv[1]; PlayerctlLoopStatus status = 0; if (!pctl_parse_loop_status(status_str, &status)) { g_set_error(error, playerctl_cli_error_quark(), 1, "Got unknown loop status: '%s' (expected 'none', " "'playlist', or 'track').", argv[1]); return FALSE; } gboolean can_control = FALSE; g_object_get(player, "can-control", &can_control, NULL); if (!can_control) { return FALSE; } playerctl_player_set_loop_status(player, status, &tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); return FALSE; } } else { if (formatter != NULL) { GVariantDict *context = playerctl_formatter_default_template_context(formatter, player, NULL); gchar *formatted = playerctl_formatter_expand_format(formatter, context, &tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); g_variant_dict_unref(context); return FALSE; } *output = g_strdup_printf("%s\n", formatted); g_variant_dict_unref(context); g_free(formatted); } else { PlayerctlLoopStatus status = 0; g_object_get(player, "loop-status", &status, NULL); const gchar *status_str = pctl_loop_status_to_string(status); assert(status_str != NULL); *output = g_strdup_printf("%s\n", status_str); } } return TRUE; } static gboolean playercmd_metadata(PlayerctlPlayer *player, gchar **argv, gint argc, gchar **output, GError **error) { GError *tmp_error = NULL; gboolean can_play = FALSE; g_object_get(player, "can-play", &can_play, NULL); if (!can_play) { // skip if no current track return FALSE; } if (format_string_arg != NULL) { gchar *data = get_metadata_formatted(player, &tmp_error); if (tmp_error) { g_propagate_error(error, tmp_error); return FALSE; } if (data != NULL) { *output = g_strdup_printf("%s\n", data); g_free(data); } else { return FALSE; } } else if (argc == 1) { gchar *data = playerctl_player_print_metadata_prop(player, NULL, &tmp_error); if (tmp_error) { g_propagate_error(error, tmp_error); return FALSE; } if (data != NULL) { *output = g_strdup_printf("%s\n", data); g_free(data); } else { return FALSE; } } else { for (int i = 1; i < argc; ++i) { const gchar *type = argv[i]; gchar *data; if (g_strcmp0(type, "artist") == 0) { data = playerctl_player_get_artist(player, &tmp_error); } else if (g_strcmp0(type, "title") == 0) { data = playerctl_player_get_title(player, &tmp_error); } else if (g_strcmp0(type, "album") == 0) { data = playerctl_player_get_album(player, &tmp_error); } else { data = playerctl_player_print_metadata_prop(player, type, &tmp_error); } if (tmp_error) { g_propagate_error(error, tmp_error); return FALSE; } if (data != NULL) { *output = g_strdup_printf("%s\n", data); g_free(data); } else { return FALSE; } } } return TRUE; } static void managed_player_properties_callback(PlayerctlPlayer *player, gpointer *data) { playerctl_player_manager_move_player_to_top(manager, player); GError *error = NULL; managed_players_execute_command(&error); } static gboolean playercmd_tick_callback(gpointer data) { GError *tmp_error = NULL; managed_players_execute_command(&tmp_error); if (tmp_error != NULL) { exit_status = 1; g_printerr("Error while executing command: %s\n", tmp_error->message); g_clear_error(&tmp_error); g_main_loop_quit(main_loop); return FALSE; } return TRUE; } struct player_command { const gchar *name; gboolean (*func)(PlayerctlPlayer *player, gchar **argv, gint argc, gchar **output, GError **error); gboolean supports_format; const gchar *follow_signal; } player_commands[] = { {"open", &playercmd_open, FALSE, NULL}, {"play", &playercmd_play, FALSE, NULL}, {"pause", &playercmd_pause, FALSE, NULL}, {"play-pause", &playercmd_play_pause, FALSE, NULL}, {"stop", &playercmd_stop, FALSE, NULL}, {"next", &playercmd_next, FALSE, NULL}, {"previous", &playercmd_previous, FALSE, NULL}, {"position", &playercmd_position, TRUE, "seeked"}, {"volume", &playercmd_volume, TRUE, "volume"}, {"status", &playercmd_status, TRUE, "playback-status"}, {"loop", &playercmd_loop, TRUE, "loop-status"}, {"shuffle", &playercmd_shuffle, TRUE, "shuffle"}, {"metadata", &playercmd_metadata, TRUE, "metadata"}, }; static const struct player_command *get_player_command(gchar **argv, gint argc, GError **error) { for (gsize i = 0; i < LENGTH(player_commands); ++i) { const struct player_command command = player_commands[i]; if (g_strcmp0(command.name, argv[0]) == 0) { if (format_string_arg != NULL && !command.supports_format) { g_set_error(error, playerctl_cli_error_quark(), 1, "format strings are not supported on command: %s", argv[0]); return NULL; } if (follow && (command.follow_signal == NULL)) { g_set_error(error, playerctl_cli_error_quark(), 1, "follow is not supported on command: %s", argv[0]); return NULL; } return &player_commands[i]; } } g_set_error(error, playerctl_cli_error_quark(), 1, "Command not recognized: %s", argv[0]); return NULL; } static const GOptionEntry entries[] = { {"player", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &player_arg, "A comma separated list of names of players to control (default: the " "first available player)", "NAME"}, {"all-players", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &select_all_players, "Select all available players to be controlled", NULL}, {"ignore-player", 'i', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &ignore_player_arg, "A comma separated list of names of players to ignore.", "IGNORE"}, {"format", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &format_string_arg, "A format string for printing properties and metadata", NULL}, {"follow", 'F', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &follow, "Block and append the query to output when it changes for the most recently updated player.", NULL}, {"list-all", 'l', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &list_all_players_and_exit, "List the names of running players that can be controlled", NULL}, {"version", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &print_version_and_exit, "Print version information", NULL}, {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &command_arg, NULL, "COMMAND"}, {NULL}}; static gboolean parse_setup_options(int argc, char *argv[], GError **error) { static const gchar *description = "Available Commands:" "\n play Command the player to play" "\n pause Command the player to pause" "\n play-pause Command the player to toggle between " "play/pause" "\n stop Command the player to stop" "\n next Command the player to skip to the next track" "\n previous Command the player to skip to the previous " "track" "\n position [OFFSET][+/-] Command the player to go to the position or " "seek forward/backward OFFSET in seconds" "\n volume [LEVEL][+/-] Print or set the volume to LEVEL from 0.0 " "to 1.0" "\n status Get the play status of the player" "\n metadata [KEY...] Print metadata information for the current " "track. If KEY is passed," "\n print only those values. KEY may be artist," "title, album, or any key found in the metadata." "\n open [URI] Command for the player to open given URI." "\n URI can be either file path or remote URL." "\n loop [STATUS] Print or set the loop status." "\n Can be \"None\", \"Track\", or \"Playlist\"." "\n shuffle [STATUS] Print or set the shuffle status." "\n Can be \"On\" or \"Off\"."; static const gchar *summary = " For players supporting the MPRIS D-Bus specification"; GOptionContext *context = NULL; gboolean success; context = g_option_context_new("- Controller for media players"); g_option_context_add_main_entries(context, entries, NULL); g_option_context_set_description(context, description); g_option_context_set_summary(context, summary); success = g_option_context_parse(context, &argc, &argv, error); if (!success) { g_option_context_free(context); return FALSE; } if (command_arg == NULL && !print_version_and_exit && !list_all_players_and_exit) { gchar *help = g_option_context_get_help(context, TRUE, NULL); printf("%s\n", help); g_option_context_free(context); g_free(help); exit(1); } g_option_context_free(context); return TRUE; } static GList *parse_player_list(gchar *player_list_arg) { GList *players = NULL; if (player_list_arg == NULL) { return NULL; } const gchar *delim = ","; gchar *token = strtok(player_list_arg, delim); while (token != NULL) { players = g_list_append(players, g_strdup(g_strstrip(token))); token = strtok(NULL, ","); } return players; } static int handle_version_flag() { g_print("v%s\n", PLAYERCTL_VERSION_S); return 0; } static int handle_list_all_flag() { GError *tmp_error = NULL; GList *player_names = playerctl_list_players(&tmp_error); if (tmp_error != NULL) { g_printerr("%s\n", tmp_error->message); return 1; } if (player_names == NULL) { g_printerr("No players were found\n"); return 0; } GList *l = NULL; for (l = player_names; l != NULL; l = l->next) { PlayerctlPlayerName *name = l->data; printf("%s\n", name->instance); } pctl_player_name_list_destroy(player_names); return 0; } static void managed_players_execute_command(GError **error) { GError *tmp_error = NULL; const struct player_command *player_cmd = get_player_command(playercmd_args->argv, playercmd_args->argc, &tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); return; } assert(player_cmd->func != NULL); gboolean did_command = FALSE; GList *players = NULL; g_object_get(manager, "players", &players, NULL); GList *l = NULL; for (l = players; l != NULL; l = l->next) { PlayerctlPlayer *player = PLAYERCTL_PLAYER(l->data); assert(player != NULL); gchar *output = NULL; gboolean result = player_cmd->func(player, playercmd_args->argv, playercmd_args->argc, &output, &tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); g_free(output); return; } if (output != NULL) { cli_print_output(output); } did_command = did_command || result; if (result) { break; } } if (!did_command) { cli_print_output(NULL); } } static gboolean name_is_selected(gchar *name) { if (ignored_player_names != NULL) { gboolean ignored = (g_list_find_custom(ignored_player_names, name, (GCompareFunc)pctl_player_name_string_instance_compare) != NULL); if (ignored) { return FALSE; } } if (player_names != NULL) { gboolean selected = (g_list_find_custom(player_names, name, (GCompareFunc)pctl_player_name_string_instance_compare) != NULL); if (!selected) { return FALSE; } } return TRUE; } static void name_appeared_callback(PlayerctlPlayerManager *manager, PlayerctlPlayerName *name, gpointer *data) { if (!name_is_selected(name->instance)) { return; } GError *error = NULL; PlayerctlPlayer *player = playerctl_player_new_from_name(name, &error); if (error != NULL) { exit_status = 1; g_printerr("Could not connect to player: %s\n", error->message); g_clear_error(&error); g_main_loop_quit(main_loop); return; } playerctl_player_manager_manage_player(manager, player); g_object_unref(player); } static void init_managed_player(PlayerctlPlayer *player, const struct player_command *player_cmd) { assert(player_cmd->follow_signal != NULL); g_signal_connect(G_OBJECT(player), player_cmd->follow_signal, G_CALLBACK(managed_player_properties_callback), playercmd_args); if (formatter != NULL) { for (gsize i = 0; i < LENGTH(player_commands); ++i) { const struct player_command cmd = player_commands[i]; if (&cmd != player_cmd && cmd.follow_signal != NULL && g_strcmp0(cmd.name, "metadata") != 0 && playerctl_formatter_contains_key(formatter, cmd.name)) { g_signal_connect(G_OBJECT(player), cmd.follow_signal, G_CALLBACK(managed_player_properties_callback), playercmd_args); } } } } static void player_appeared_callback(PlayerctlPlayerManager *manager, PlayerctlPlayer *player, gpointer *data) { GError *error = NULL; const struct player_command *player_cmd = get_player_command(playercmd_args->argv, playercmd_args->argc, &error); if (error != NULL) { exit_status = 1; g_printerr("Could not get player command: %s\n", error->message); g_clear_error(&error); g_main_loop_quit(main_loop); return; } init_managed_player(player, player_cmd); managed_players_execute_command(&error); if (error != NULL) { exit_status = 1; g_printerr("Could not execute command: %s\n", error->message); g_clear_error(&error); g_main_loop_quit(main_loop); return; } } static void player_vanished_callback(PlayerctlPlayerManager *manager, PlayerctlPlayer *player, gpointer *data) { GError *error = NULL; managed_players_execute_command(&error); if (error != NULL) { exit_status = 1; g_printerr("Could not execute command: %s\n", error->message); g_clear_error(&error); g_main_loop_quit(main_loop); return; } } gint player_name_string_compare_func(gconstpointer a, gconstpointer b) { const gchar *name_a = a; const gchar *name_b = b; if (g_strcmp0(name_a, name_b) == 0) { return 0; } GList *l = NULL; for (l = player_names; l != NULL; l = l->next) { gchar *name = l->data; if (g_strcmp0(name_a, name) == 0) { return -1; } else if (g_strcmp0(name_b, name) == 0) { return 1; } else if (pctl_player_name_string_instance_compare(name, name_a) == 0) { return -1; } else if (pctl_player_name_string_instance_compare(name, name_b) == 0) { return 1; } } return 0; } gint player_name_compare_func(gconstpointer a, gconstpointer b) { const PlayerctlPlayerName *name_a = a; const PlayerctlPlayerName *name_b = b; return player_name_string_compare_func(name_a->instance, name_b->instance); } gint player_compare_func(gconstpointer a, gconstpointer b) { PlayerctlPlayer *player_a = PLAYERCTL_PLAYER(a); PlayerctlPlayer *player_b = PLAYERCTL_PLAYER(b); gchar *name_a = NULL; gchar *name_b = NULL; g_object_get(player_a, "player-name", &name_a, NULL); g_object_get(player_b, "player-name", &name_b, NULL); gint result = player_name_string_compare_func(name_a, name_b); g_free(name_a); g_free(name_b); return result; } int main(int argc, char *argv[]) { GError *error = NULL; guint num_commands = 0; // seems to be required to print unicode (see #8) setlocale(LC_CTYPE, ""); if (!parse_setup_options(argc, argv, &error)) { g_printerr("%s\n", error->message); g_clear_error(&error); exit(0); } if (print_version_and_exit) { int result = handle_version_flag(); exit(result); } if (list_all_players_and_exit) { int result = handle_list_all_flag(); exit(result); } num_commands = g_strv_length(command_arg); const struct player_command *player_cmd = get_player_command(command_arg, num_commands, &error); if (error != NULL) { g_printerr("Could not execute command: %s\n", error->message); g_clear_error(&error); exit(1); } if (format_string_arg != NULL) { formatter = playerctl_formatter_new(format_string_arg, &error); if (error != NULL) { g_printerr("Could not execute command: %s\n", error->message); g_clear_error(&error); exit(1); } } player_names = parse_player_list(player_arg); ignored_player_names = parse_player_list(ignore_player_arg); playercmd_args = playercmd_args_create(command_arg, num_commands); manager = playerctl_player_manager_new(&error); if (error != NULL) { g_printerr("Could not connect to players: %s\n", error->message); exit_status = 1; goto end; } if (player_names != NULL && !select_all_players) { playerctl_player_manager_set_sort_func(manager, (GCompareDataFunc)player_compare_func, NULL, NULL); } GList *available_players = NULL; g_object_get(manager, "player-names", &available_players, NULL); available_players = g_list_copy(available_players); available_players = g_list_sort(available_players, (GCompareFunc)player_name_compare_func); gboolean has_selected = FALSE; GList *l = NULL; for (l = available_players; l != NULL; l = l->next) { PlayerctlPlayerName *name = l->data; if (!name_is_selected(name->instance)) { continue; } has_selected = TRUE; PlayerctlPlayer *player = playerctl_player_new_from_name(name, &error); if (error != NULL) { g_printerr("Could not connect to player: %s\n", error->message); exit_status = 1; goto end; } if (follow) { playerctl_player_manager_manage_player(manager, player); init_managed_player(player, player_cmd); } else { gchar *output = NULL; gboolean result = player_cmd->func(player, command_arg, num_commands, &output, &error); if (error != NULL) { g_printerr("Could not execute command: %s\n", error->message); exit_status = 1; g_object_unref(player); goto end; } if (result) { if (output != NULL) { printf("%s", output); fflush(stdout); g_free(output); } if (!select_all_players) { g_object_unref(player); goto end; } } } g_object_unref(player); } if (!follow && !has_selected) { g_printerr("No players found\n"); exit_status = 1; goto end; } if (follow) { managed_players_execute_command(&error); if (error != NULL) { g_printerr("Connection to player failed: %s\n", error->message); exit_status = 1; goto end; } g_signal_connect(PLAYERCTL_PLAYER_MANAGER(manager), "name-appeared", G_CALLBACK(name_appeared_callback), NULL); g_signal_connect(PLAYERCTL_PLAYER_MANAGER(manager), "player-appeared", G_CALLBACK(player_appeared_callback), NULL); g_signal_connect(PLAYERCTL_PLAYER_MANAGER(manager), "player-vanished", G_CALLBACK(player_vanished_callback), NULL); if (formatter != NULL && playerctl_formatter_contains_key(formatter, "position")) { g_timeout_add(1000, playercmd_tick_callback, NULL); } main_loop = g_main_loop_new(NULL, FALSE); g_main_loop_run(main_loop); g_main_loop_unref(main_loop); } end: if (available_players != NULL) { g_list_free(available_players); } playercmd_args_destroy(playercmd_args); if (manager != NULL) { g_object_unref(manager); } playerctl_formatter_destroy(formatter); g_free(last_output); g_list_free_full(player_names, g_free); g_list_free_full(ignored_player_names, g_free); exit(exit_status); } playerctl-2.0.2/playerctl/playerctl-common.c000066400000000000000000000140441344655314500211570ustar00rootroot00000000000000/* * This file is part of playerctl. * * playerctl is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * playerctl 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 Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License * along with playerctl If not, see . * * Copyright © 2014, Tony Crisci and contributors. */ #include #include #include #include "playerctl-common.h" gboolean pctl_parse_playback_status(const gchar *status_str, PlayerctlPlaybackStatus *status) { if (status_str == NULL) { return FALSE; } if (strcasecmp(status_str, "Playing") == 0) { *status = PLAYERCTL_PLAYBACK_STATUS_PLAYING; return TRUE; } else if (strcasecmp(status_str, "Paused") == 0) { *status = PLAYERCTL_PLAYBACK_STATUS_PAUSED; return TRUE; } else if (strcasecmp(status_str, "Stopped") == 0) { *status = PLAYERCTL_PLAYBACK_STATUS_STOPPED; return TRUE; } return FALSE; } const gchar *pctl_playback_status_to_string(PlayerctlPlaybackStatus status) { switch (status) { case PLAYERCTL_PLAYBACK_STATUS_PLAYING: return "Playing"; case PLAYERCTL_PLAYBACK_STATUS_PAUSED: return "Paused"; case PLAYERCTL_PLAYBACK_STATUS_STOPPED: return "Stopped"; } return NULL; } const gchar *pctl_loop_status_to_string(PlayerctlLoopStatus status) { switch (status) { case PLAYERCTL_LOOP_STATUS_NONE: return "None"; case PLAYERCTL_LOOP_STATUS_TRACK: return "Track"; case PLAYERCTL_LOOP_STATUS_PLAYLIST: return "Playlist"; } return NULL; } gboolean pctl_parse_loop_status(const gchar *status_str, PlayerctlLoopStatus *status) { if (status_str == NULL) { return FALSE; } if (strcasecmp(status_str, "None") == 0) { *status = PLAYERCTL_LOOP_STATUS_NONE; return TRUE; } else if (strcasecmp(status_str, "Track") == 0) { *status = PLAYERCTL_LOOP_STATUS_TRACK; return TRUE; } else if (strcasecmp(status_str, "Playlist") == 0) { *status = PLAYERCTL_LOOP_STATUS_PLAYLIST; return TRUE; } return FALSE; } gchar *pctl_print_gvariant(GVariant *value) { GString *printed = g_string_new(""); if (g_variant_is_of_type(value, G_VARIANT_TYPE_STRING_ARRAY)) { gsize prop_count; const gchar **prop_strv = g_variant_get_strv(value, &prop_count); for (gsize i = 0; i < prop_count; i += 1) { g_string_append(printed, prop_strv[i]); if (i != prop_count - 1) { g_string_append(printed, ", "); } } g_free(prop_strv); } else if (g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) { g_string_append(printed, g_variant_get_string(value, NULL)); } else { printed = g_variant_print_string(value, printed, FALSE); } return g_string_free(printed, FALSE); } GBusType pctl_source_to_bus_type(PlayerctlSource source) { switch (source) { case PLAYERCTL_SOURCE_DBUS_SESSION: return G_BUS_TYPE_SESSION; case PLAYERCTL_SOURCE_DBUS_SYSTEM: return G_BUS_TYPE_SYSTEM; default: return G_BUS_TYPE_NONE; } } PlayerctlSource pctl_bus_type_to_source(GBusType bus_type) { switch (bus_type) { case G_BUS_TYPE_SESSION: return PLAYERCTL_SOURCE_DBUS_SESSION; case G_BUS_TYPE_SYSTEM: return PLAYERCTL_SOURCE_DBUS_SYSTEM; default: g_warning("could not convert bus type to source: %d\n", bus_type); return PLAYERCTL_SOURCE_NONE; } } PlayerctlPlayerName *pctl_player_name_new(const gchar *instance, PlayerctlSource source) { PlayerctlPlayerName *player_name = g_slice_new(PlayerctlPlayerName); gchar **split = g_strsplit(instance, ".instance", 2); player_name->name = g_strdup(split[0]); g_strfreev(split); player_name->instance = g_strdup(instance); player_name->source = source; return player_name; } gint pctl_player_name_compare(PlayerctlPlayerName *name_a, PlayerctlPlayerName *name_b) { if (name_a->source != name_b->source) { return 1; } return g_strcmp0(name_a->instance, name_b->instance); } gint pctl_player_name_instance_compare(PlayerctlPlayerName *name, PlayerctlPlayerName *instance) { if (name->source != instance->source) { return 1; } return pctl_player_name_string_instance_compare(name->instance, instance->instance); } gint pctl_player_name_string_instance_compare(const gchar *name, const gchar *instance) { gboolean exact_match = (g_strcmp0(name, instance) == 0); gboolean instance_match = !exact_match && (g_str_has_prefix(instance, name) && g_str_has_prefix(instance + strlen(name), ".instance")); if (exact_match || instance_match) { return 0; } else { return 1; } } GList *pctl_player_name_find(GList *list, gchar *player_id, PlayerctlSource source) { PlayerctlPlayerName player_name = { .instance = player_id, .source = source, }; return g_list_find_custom(list, &player_name, (GCompareFunc)pctl_player_name_compare); } GList *pctl_player_name_find_instance(GList *list, gchar *player_id, PlayerctlSource source) { PlayerctlPlayerName player_name = { .instance = player_id, .source = source, }; return g_list_find_custom(list, &player_name, (GCompareFunc)pctl_player_name_instance_compare); } void pctl_player_name_list_destroy(GList *list) { g_list_free_full(list, (GDestroyNotify)playerctl_player_name_free); } playerctl-2.0.2/playerctl/playerctl-common.h000066400000000000000000000041461344655314500211660ustar00rootroot00000000000000/* * This file is part of playerctl. * * playerctl is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * playerctl 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 Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License * along with playerctl If not, see . * * Copyright © 2014, Tony Crisci and contributors */ #ifndef __PLAYERCTL_COMMON_H__ #define __PLAYERCTL_COMMON_H__ #include #include #include #define MPRIS_PREFIX "org.mpris.MediaPlayer2." gboolean pctl_parse_playback_status(const gchar *playback_status, PlayerctlPlaybackStatus *status); const gchar *pctl_playback_status_to_string(PlayerctlPlaybackStatus status); gboolean pctl_parse_loop_status(const gchar *loop_status, PlayerctlLoopStatus *status); const gchar *pctl_loop_status_to_string(PlayerctlLoopStatus status); gchar *pctl_print_gvariant(GVariant *value); GBusType pctl_source_to_bus_type(PlayerctlSource source); PlayerctlSource pctl_bus_type_to_source(GBusType bus_type); PlayerctlPlayerName *pctl_player_name_new(const gchar *name, PlayerctlSource source); gint pctl_player_name_compare(PlayerctlPlayerName *name_a, PlayerctlPlayerName *name_b); gint pctl_player_name_instance_compare(PlayerctlPlayerName *name, PlayerctlPlayerName *instance); gint pctl_player_name_string_instance_compare(const gchar *name, const gchar *instance); GList *pctl_player_name_find(GList *list, gchar *player_id, PlayerctlSource source); GList *pctl_player_name_find_instance(GList *list, gchar *player_id, PlayerctlSource source); void pctl_player_name_list_destroy(GList *list); #undef __PLAYERCTL_COMMON_H__ #endif playerctl-2.0.2/playerctl/playerctl-enum-types.c.in000066400000000000000000000016051344655314500224010ustar00rootroot00000000000000/*** BEGIN file-header ***/ #include "config.h" #include "enum-types.h" /*** END file-header ***/ /*** BEGIN file-production ***/ /* enumerations from "@filename@" */ /*** END file-production ***/ /*** BEGIN value-header ***/ GType @enum_name@_get_type (void) { static volatile gsize g_@type@_type_id__volatile; if (g_once_init_enter (&g_define_type_id__volatile)) { static const G@Type@Value values[] = { /*** END value-header ***/ /*** BEGIN value-production ***/ { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, /*** END value-production ***/ /*** BEGIN value-tail ***/ { 0, NULL, NULL } }; GType g_@type@_type_id = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); g_once_init_leave (&g_@type@_type_id__volatile, g_@type@_type_id); } return g_@type@_type_id__volatile; } /*** END value-tail ***/ playerctl-2.0.2/playerctl/playerctl-enum-types.h.in000066400000000000000000000007331344655314500224070ustar00rootroot00000000000000/*** BEGIN file-header ***/ #pragma once /* Include the main project header */ #include "project.h" G_BEGIN_DECLS /*** END file-header ***/ /*** BEGIN file-production ***/ /* enumerations from "@filename@" */ /*** END file-production ***/ /*** BEGIN value-header ***/ GType @enum_name@_get_type (void) G_GNUC_CONST; #define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) /*** END value-header ***/ /*** BEGIN file-tail ***/ G_END_DECLS /*** END file-tail ***/ playerctl-2.0.2/playerctl/playerctl-formatter.c000066400000000000000000000424541344655314500217000ustar00rootroot00000000000000#include #include #include "playerctl/playerctl-formatter.h" #include "playerctl/playerctl-common.h" #include #include #define LENGTH(array) (sizeof array / sizeof array[0]) G_DEFINE_QUARK(playerctl-formatter-error-quark, playerctl_formatter_error); enum token_type { TOKEN_PASSTHROUGH, TOKEN_VARIABLE, TOKEN_FUNCTION, }; struct token { enum token_type type; gchar *data; struct token *arg; }; enum parser_state { STATE_INSIDE = 0, STATE_PARAMS_OPEN, STATE_PARAMS_CLOSED, STATE_PASSTHROUGH, }; struct _PlayerctlFormatterPrivate { GList *tokens; }; static struct token *token_create(enum token_type type) { struct token *token = calloc(1, sizeof(struct token)); token->type = type; return token; } static void token_destroy(struct token *token) { if (token == NULL) { return; } token_destroy(token->arg); g_free(token->data); free(token); } static void token_list_destroy(GList *tokens) { if (tokens == NULL) { return; } g_list_free_full(tokens, (GDestroyNotify)token_destroy); } static gboolean token_list_contains_key(GList *tokens, const gchar *key) { GList *t = NULL; for (t = tokens; t != NULL; t = t->next) { struct token *token = t->data; switch (token->type) { case TOKEN_VARIABLE: if (g_strcmp0(token->data, key) == 0) { return TRUE; } break; case TOKEN_FUNCTION: if (token->arg != NULL && token->arg->type == TOKEN_VARIABLE && g_strcmp0(token->arg->data, key) == 0) { return TRUE; } break; default: break; } } return FALSE; } static GList *tokenize_format(const char *format, GError **error) { GList *tokens = NULL; if (format == NULL) { return NULL; } int len = strlen(format); char buf[1028]; int buf_len = 0; if (len >= 1028) { g_set_error(error, playerctl_formatter_error_quark(), 1, "the maximum format string length is 1028"); return NULL; } enum parser_state state = STATE_PASSTHROUGH; for (int i = 0; i < len; ++i) { if (format[i] == '{' && i < len + 1 && format[i+1] == '{') { if (state == STATE_INSIDE) { g_set_error(error, playerctl_formatter_error_quark(), 1, "unexpected token: \"{{\" (position %d)", i); token_list_destroy(tokens); return NULL; } if (buf_len != 0) { struct token *token = token_create(TOKEN_PASSTHROUGH); buf[buf_len] = '\0'; token->data = g_strdup(buf); tokens = g_list_append(tokens, token); } i += 1; buf_len = 0; state = STATE_INSIDE; } else if (format[i] == '}' && i < len + 1 && format[i+1] == '}' && state != STATE_PASSTHROUGH) { if (state == STATE_PARAMS_OPEN) { g_set_error(error, playerctl_formatter_error_quark(), 1, "unexpected token: \"}}\" (expected closing parens: \")\" at position %d)", i); token_list_destroy(tokens); return NULL; } if (state != STATE_PARAMS_CLOSED) { buf[buf_len] = '\0'; gchar *name = g_strstrip(g_strdup(buf)); if (strlen(name) == 0) { g_set_error(error, playerctl_formatter_error_quark(), 1, "got empty template expression at position %d", i); token_list_destroy(tokens); g_free(name); return NULL; } struct token *token = token_create(TOKEN_VARIABLE); token->data = name; tokens = g_list_append(tokens, token); } else if (buf_len > 0) { for (int k = 0; k < buf_len; ++k) { if (buf[k] != ' ') { g_set_error(error, playerctl_formatter_error_quark(), 1, "got unexpected input after closing parens at position %d", i - buf_len + k); token_list_destroy(tokens); return NULL; } } } i += 1; buf_len = 0; state = STATE_PASSTHROUGH; } else if (format[i] == '(' && state != STATE_PASSTHROUGH) { if (state == STATE_PARAMS_OPEN) { g_set_error(error, playerctl_formatter_error_quark(), 1, "unexpected token: \"(\" at position %d", i); token_list_destroy(tokens); return NULL; } if (state == STATE_PARAMS_CLOSED) { g_set_error(error, playerctl_formatter_error_quark(), 1, "unexpected token: \"(\" at position %d", i); token_list_destroy(tokens); return NULL; } buf[buf_len] = '\0'; gchar *name = g_strstrip(g_strdup(buf)); if (strlen(name) == 0) { g_set_error(error, playerctl_formatter_error_quark(), 1, "expected a function name to call at position %d", i); token_list_destroy(tokens); g_free(name); return NULL; } struct token *token = token_create(TOKEN_FUNCTION); token->data = name; tokens = g_list_append(tokens, token); buf_len = 0; state = STATE_PARAMS_OPEN; } else if (format[i] == ')' && state != STATE_PASSTHROUGH) { if (state != STATE_PARAMS_OPEN) { g_set_error(error, playerctl_formatter_error_quark(), 1, "unexpected token: \")\" at position %d", i); token_list_destroy(tokens); return NULL; } buf[buf_len] = '\0'; gchar *name = g_strstrip(g_strdup(buf)); if (strlen(name) == 0) { g_set_error(error, playerctl_formatter_error_quark(), 1, "expected a function parameter at position %d", i); token_list_destroy(tokens); g_free(name); return NULL; } struct token *token = token_create(TOKEN_VARIABLE); token->data = name; struct token *fn_token = g_list_last(tokens)->data; assert(fn_token != NULL); assert(fn_token->type == TOKEN_FUNCTION); assert(fn_token->arg == NULL); fn_token->arg = token; buf_len = 0; state = STATE_PARAMS_CLOSED; } else { buf[buf_len++] = format[i]; } } if (state == STATE_INSIDE || state == STATE_PARAMS_CLOSED) { g_set_error(error, playerctl_formatter_error_quark(), 1, "unmatched opener \"{{\" (expected a matching \"}}\" at the end)"); token_list_destroy(tokens); return NULL; } else if (state == STATE_PARAMS_OPEN) { g_set_error(error, playerctl_formatter_error_quark(), 1, "unmatched opener \"(\" (expected a matching \")\")"); token_list_destroy(tokens); return NULL; } if (buf_len > 0) { buf[buf_len] = '\0'; struct token *token = token_create(TOKEN_PASSTHROUGH); token->data = g_strdup(buf); tokens = g_list_append(tokens, token); } return tokens; } static gchar *helperfn_lc(gchar *key, GVariant *value) { gchar *printed = pctl_print_gvariant(value); gchar *printed_lc = g_utf8_strdown(printed, -1); g_free(printed); return printed_lc; } static gchar *helperfn_uc(gchar *key, GVariant *value) { gchar *printed = pctl_print_gvariant(value); gchar *printed_uc = g_utf8_strup(printed, -1); g_free(printed); return printed_uc; } static gchar *helperfn_duration(gchar *key, GVariant *value) { // mpris durations are represented as int64 in microseconds if (!g_variant_type_equal(g_variant_get_type(value), G_VARIANT_TYPE_INT64)) { return NULL; } gint64 duration = g_variant_get_int64(value); gint64 seconds = (duration / 1000000) % 60; gint64 minutes = (duration / 1000000 / 60) % 60; gint64 hours = (duration / 1000000 / 60 / 60); GString *formatted = g_string_new(""); if (hours != 0) { g_string_append_printf(formatted, "%" PRId64 ":%02" PRId64 ":%02" PRId64, hours, minutes, seconds); } else { g_string_append_printf(formatted, "%" PRId64 ":%02" PRId64, minutes, seconds); } return g_string_free(formatted, FALSE); } static gchar *helperfn_emoji(gchar *key, GVariant *value) { g_warning("The emoji() helper function is undocumented and experimental and will change in a future release."); if (g_strcmp0(key, "status") == 0 && g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) { const gchar *status_str = g_variant_get_string(value, NULL); PlayerctlPlaybackStatus status = 0; if (pctl_parse_playback_status(status_str, &status)) { switch (status) { case PLAYERCTL_PLAYBACK_STATUS_PLAYING: return g_strdup("▶️"); case PLAYERCTL_PLAYBACK_STATUS_STOPPED: return g_strdup("⏹️"); case PLAYERCTL_PLAYBACK_STATUS_PAUSED: return g_strdup("⏸️"); } } } else if (g_strcmp0(key, "volume") == 0 && g_variant_is_of_type(value, G_VARIANT_TYPE_DOUBLE)) { const gdouble volume = g_variant_get_double(value); if (volume < 0.3333) { return g_strdup("🔈"); } else if (volume < 0.6666) { return g_strdup("🔉"); } else { return g_strdup("🔊"); } } return pctl_print_gvariant(value); } struct template_helper { const gchar *name; gchar *(*func)(gchar *key, GVariant *value); } helpers[] = { {"lc", &helperfn_lc}, {"uc", &helperfn_uc}, {"duration", &helperfn_duration}, // EXPERIMENTAL {"emoji", &helperfn_emoji}, }; static gchar *expand_format(GList *tokens, GVariantDict *context, GError **error) { GString *expanded; expanded = g_string_new(""); GList *next = tokens; while (next != NULL) { struct token *token = next->data; switch (token->type) { case TOKEN_PASSTHROUGH: expanded = g_string_append(expanded, token->data); break; case TOKEN_VARIABLE: { gchar *name = token->data; if (g_variant_dict_contains(context, name)) { GVariant *value = g_variant_dict_lookup_value(context, name, NULL); if (value != NULL) { gchar *value_str = pctl_print_gvariant(value); expanded = g_string_append(expanded, value_str); g_variant_unref(value); g_free(value_str); } } break; } case TOKEN_FUNCTION: { // XXX: functions must have an argument and that argument must be a // variable (enforced in the tokenization step) assert(token->arg != NULL); assert(token->arg->type == TOKEN_VARIABLE); gboolean found = FALSE; gchar *fn_name = token->data; gchar *arg_name = token->arg->data; for (gsize i = 0; i < LENGTH(helpers); ++i) { if (g_strcmp0(helpers[i].name, fn_name) == 0) { GVariant *value = g_variant_dict_lookup_value(context, arg_name, NULL); if (value != NULL) { gchar *result = helpers[i].func(arg_name, value); if (result != NULL) { expanded = g_string_append(expanded, result); g_free(result); } g_variant_unref(value); } found = TRUE; break; } } if (!found) { g_set_error(error, playerctl_formatter_error_quark(), 1, "unknown template function: %s", fn_name); token_list_destroy(tokens); g_string_free(expanded, TRUE); return NULL; } break; } } next = next->next; } return g_string_free(expanded, FALSE); } static GVariantDict *get_default_template_context(PlayerctlPlayer *player, GVariant *base) { GVariantDict *context = g_variant_dict_new(base); if (!g_variant_dict_contains(context, "artist") && g_variant_dict_contains(context, "xesam:artist")) { GVariant *artist = g_variant_dict_lookup_value(context, "xesam:artist", NULL); g_variant_dict_insert_value(context, "artist", artist); g_variant_unref(artist); } if (!g_variant_dict_contains(context, "album") && g_variant_dict_contains(context, "xesam:album")) { GVariant *album = g_variant_dict_lookup_value(context, "xesam:album", NULL); g_variant_dict_insert_value(context, "album", album); g_variant_unref(album); } if (!g_variant_dict_contains(context, "title") && g_variant_dict_contains(context, "xesam:title")) { GVariant *title = g_variant_dict_lookup_value(context, "xesam:title", NULL); g_variant_dict_insert_value(context, "title", title); g_variant_unref(title); } if (!g_variant_dict_contains(context, "playerName")) { gchar *player_name = NULL; g_object_get(player, "player-name", &player_name, NULL); GVariant *player_name_variant = g_variant_new_string(player_name); g_variant_dict_insert_value(context, "playerName", player_name_variant); g_free(player_name); } if (!g_variant_dict_contains(context, "playerInstance")) { gchar *instance = NULL; g_object_get(player, "player-instance", &instance, NULL); GVariant *player_instance_variant = g_variant_new_string(instance); g_variant_dict_insert_value(context, "playerInstance", player_instance_variant); g_free(instance); } if (!g_variant_dict_contains(context, "shuffle")) { gboolean shuffle = FALSE; g_object_get(player, "shuffle", &shuffle, NULL); GVariant *shuffle_variant = g_variant_new_boolean(shuffle); g_variant_dict_insert_value(context, "shuffle", shuffle_variant); } if (!g_variant_dict_contains(context, "status")) { PlayerctlPlaybackStatus status = 0; g_object_get(player, "playback-status", &status, NULL); const gchar *status_str = pctl_playback_status_to_string(status); GVariant *status_variant = g_variant_new_string(status_str); g_variant_dict_insert_value(context, "status", status_variant); } if (!g_variant_dict_contains(context, "loop")) { PlayerctlLoopStatus status = 0; g_object_get(player, "loop-status", &status, NULL); const gchar *status_str = pctl_loop_status_to_string(status); GVariant *status_variant = g_variant_new_string(status_str); g_variant_dict_insert_value(context, "loop", status_variant); } if (!g_variant_dict_contains(context, "volume")) { gdouble level = 0.0; g_object_get(player, "volume", &level, NULL); GVariant *volume_variant = g_variant_new_double(level); g_variant_dict_insert_value(context, "volume", volume_variant); } if (!g_variant_dict_contains(context, "position")) { gint64 position = 0; g_object_get(player, "position", &position, NULL); GVariant *position_variant = g_variant_new_int64(position); g_variant_dict_insert_value(context, "position", position_variant); } return context; } PlayerctlFormatter *playerctl_formatter_new(const gchar *format, GError **error) { GError *tmp_error = NULL; GList *tokens = tokenize_format(format, &tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); return NULL; } PlayerctlFormatter *formatter = calloc(1, sizeof(PlayerctlFormatter)); formatter->priv = calloc(1, sizeof(PlayerctlFormatterPrivate)); formatter->priv->tokens = tokens; return formatter; } void playerctl_formatter_destroy(PlayerctlFormatter *formatter) { if (formatter == NULL) { return; } token_list_destroy(formatter->priv->tokens); free(formatter->priv); free(formatter); } gboolean playerctl_formatter_contains_key(PlayerctlFormatter *formatter, const gchar *key) { return token_list_contains_key(formatter->priv->tokens, key); } GVariantDict *playerctl_formatter_default_template_context( PlayerctlFormatter *formatter, PlayerctlPlayer *player, GVariant *base) { return get_default_template_context(player, base); } gchar *playerctl_formatter_expand_format(PlayerctlFormatter *formatter, GVariantDict *context, GError **error) { GError *tmp_error = NULL; gchar *expanded = expand_format(formatter->priv->tokens, context, &tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); return NULL; } return expanded; } playerctl-2.0.2/playerctl/playerctl-formatter.h000066400000000000000000000031101344655314500216670ustar00rootroot00000000000000/* * This file is part of playerctl. * * playerctl is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * playerctl 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 Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License * along with playerctl If not, see . * * Copyright © 2014, Tony Crisci and contributors */ #ifndef __PLAYERCTL_FORMATTER_H__ #define __PLAYERCTL_FORMATTER_H__ #include #include typedef struct _PlayerctlFormatter PlayerctlFormatter; typedef struct _PlayerctlFormatterPrivate PlayerctlFormatterPrivate; struct _PlayerctlFormatter { PlayerctlFormatterPrivate *priv; }; PlayerctlFormatter *playerctl_formatter_new(const gchar *format, GError **error); void playerctl_formatter_destroy(PlayerctlFormatter *formatter); gboolean playerctl_formatter_contains_key(PlayerctlFormatter *formatter, const gchar *key); GVariantDict *playerctl_formatter_default_template_context( PlayerctlFormatter *formatter, PlayerctlPlayer *player, GVariant *base); gchar *playerctl_formatter_expand_format(PlayerctlFormatter *formatter, GVariantDict *context, GError **error); #endif /* __PLAYERCTL_FORMATTER_H__ */ playerctl-2.0.2/playerctl/playerctl-player-manager.c000066400000000000000000000473471344655314500226070ustar00rootroot00000000000000/* * This file is part of playerctl. * * playerctl is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * playerctl 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 Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License * along with playerctl If not, see . * * Copyright © 2014, Tony Crisci and contributors. */ #include #include #include "playerctl/playerctl-player-name.h" #include "playerctl/playerctl-player-manager.h" #include "playerctl/playerctl-common.h" #include "playerctl/playerctl-player.h" enum { PROP_0, PROP_PLAYERS, PROP_PLAYER_NAMES, N_PROPERTIES, }; enum { NAME_APPEARED, NAME_VANISHED, PLAYER_APPEARED, PLAYER_VANISHED, LAST_SIGNAL, }; static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, }; static guint connection_signals[LAST_SIGNAL] = {0}; struct _PlayerctlPlayerManagerPrivate { gboolean initted; GError *init_error; GDBusProxy *session_proxy; GDBusProxy *system_proxy; GList *player_names; GList *players; GCompareDataFunc sort_func; gpointer *sort_data; GDestroyNotify sort_notify; }; static void playerctl_player_manager_initable_iface_init(GInitableIface *iface); G_DEFINE_TYPE_WITH_CODE(PlayerctlPlayerManager, playerctl_player_manager, G_TYPE_OBJECT, G_ADD_PRIVATE(PlayerctlPlayerManager) G_IMPLEMENT_INTERFACE( G_TYPE_INITABLE, playerctl_player_manager_initable_iface_init)); static void playerctl_player_manager_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { //PlayerctlPlayerManager *manager = PLAYERCTL_PLAYER_MANAGER(object); switch (property_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void playerctl_player_manager_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PlayerctlPlayerManager *manager = PLAYERCTL_PLAYER_MANAGER(object); switch (property_id) { case PROP_PLAYERS: g_value_set_pointer(value, manager->priv->players); break; case PROP_PLAYER_NAMES: g_value_set_pointer(value, manager->priv->player_names); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void playerctl_player_manager_constructed(GObject *gobject) { PlayerctlPlayerManager *manager = PLAYERCTL_PLAYER_MANAGER(gobject); g_initable_init(G_INITABLE(manager), NULL, &manager->priv->init_error); G_OBJECT_CLASS(playerctl_player_manager_parent_class)->constructed(gobject); } static void playerctl_player_manager_dispose(GObject *gobject) { PlayerctlPlayerManager *manager = PLAYERCTL_PLAYER_MANAGER(gobject); g_clear_error(&manager->priv->init_error); g_clear_object(&manager->priv->session_proxy); g_clear_object(&manager->priv->system_proxy); G_OBJECT_CLASS(playerctl_player_manager_parent_class)->dispose(gobject); } static void playerctl_player_manager_finalize(GObject *gobject) { PlayerctlPlayerManager *manager = PLAYERCTL_PLAYER_MANAGER(gobject); g_list_free_full(manager->priv->player_names, (GDestroyNotify)playerctl_player_name_free); g_list_free_full(manager->priv->players, g_object_unref); G_OBJECT_CLASS(playerctl_player_manager_parent_class)->finalize(gobject); } static void playerctl_player_manager_class_init(PlayerctlPlayerManagerClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS(klass); gobject_class->set_property = playerctl_player_manager_set_property; gobject_class->get_property = playerctl_player_manager_get_property; gobject_class->constructed = playerctl_player_manager_constructed; gobject_class->dispose = playerctl_player_manager_dispose; gobject_class->finalize = playerctl_player_manager_finalize; /** * PlayerctlPlayerManager:players: (transfer none) (type GList(PlayerctlPlayer)) * * A list of players that are currently connected and managed by this class. */ obj_properties[PROP_PLAYERS] = g_param_spec_pointer("players", "players", "A list of player objects managed by this manager", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * PlayerctlPlayerManager:player-names: (transfer none) (type GList(PlayerctlPlayerName)) * * A list of fully qualified player names that are currently available to control. */ obj_properties[PROP_PLAYER_NAMES] = g_param_spec_pointer("player-names", "player names", "A list of player names that are currently available to control.", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(gobject_class, N_PROPERTIES, obj_properties); /** * PlayerctlPlayerManager::name-appeared: * @self: the #PlayerctlPlayerManager on which the signal was emitted * @name: A #PlayerctlPlayerName containing information about the name that * has appeared. * * Emitted when a new name has appeared and is available to connect to. Use * playerctl_player_new_from_name() to connect to the player and * playerctl_player_manager_manage_player() to add it to the managed list of * players. */ connection_signals[NAME_APPEARED] = g_signal_new("name-appeared", PLAYERCTL_TYPE_PLAYER_MANAGER, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, PLAYERCTL_TYPE_PLAYER_NAME); /** * PlayerctlPlayerManager::name-vanished: * @self: the #PlayerctlPlayerManager on which this signal was emitted. * @name: The #PlayerctlPlayerName containing connection information about * the name that is going away. * * Emitted when the name has vanished and is no longer available to be * controlled by playerctl. If the player is managed, it will automatically * be removed from the list of players and the * #PlayerctlPlayerManager::player-vanished signal will be emitted * automatically. */ connection_signals[NAME_VANISHED] = g_signal_new("name-vanished", PLAYERCTL_TYPE_PLAYER_MANAGER, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, PLAYERCTL_TYPE_PLAYER_NAME); /** * PlayerctlPlayerManager::player-appeared: * @self: The #PlayerctlPlayerManager on which this event was emitted. * @player: The #PlayerctlPlayer that will be managed by this manager * * Emitted when a new player will be managed by this manager through a call * to playerctl_player_manager_manage_player(). */ connection_signals[PLAYER_APPEARED] = g_signal_new("player-appeared", PLAYERCTL_TYPE_PLAYER_MANAGER, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, PLAYERCTL_TYPE_PLAYER); /** * PlayerctlPlayerManager::player-vanished: * @self: The #PlayerctlPlayerManager on which this event was emitted. * @player: The #PlayerctlPlayer that will no longer be managed by this * manager * * Emitted when a player has disconnected and will no longer be managed by * this manager. The player is removed from the list of players * automatically. */ connection_signals[PLAYER_VANISHED] = g_signal_new("player-vanished", PLAYERCTL_TYPE_PLAYER_MANAGER, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, PLAYERCTL_TYPE_PLAYER); } static void playerctl_player_manager_init(PlayerctlPlayerManager *manager) { manager->priv = playerctl_player_manager_get_instance_private(manager); } static gchar *player_id_from_bus_name(const gchar *bus_name) { const size_t prefix_len = strlen(MPRIS_PREFIX); if (bus_name == NULL || !g_str_has_prefix(bus_name, MPRIS_PREFIX) || strlen(bus_name) <= prefix_len) { return NULL; } return g_strdup(bus_name + prefix_len); } static void manager_remove_managed_player_by_name(PlayerctlPlayerManager *manager, PlayerctlPlayerName *player_name) { GList *l = NULL; for (l = manager->priv->players; l != NULL; l = l->next) { PlayerctlPlayer *player = PLAYERCTL_PLAYER(l->data); gchar *instance = NULL; g_object_get(player, "player-instance", &instance, NULL); // TODO match bus type if (g_strcmp0(instance, player_name->instance) == 0) { manager->priv->players = g_list_remove_link(manager->priv->players, l); g_signal_emit(manager, connection_signals[PLAYER_VANISHED], 0, player); g_list_free_full(l, g_object_unref); g_free(instance); break; } g_free(instance); } } static void dbus_name_owner_changed_callback(GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, gpointer *data) { PlayerctlPlayerManager *manager = PLAYERCTL_PLAYER_MANAGER(data); if (g_strcmp0(signal_name, "NameOwnerChanged") != 0) { return; } if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) { g_warning("Got unknown parameters on org.freedesktop.DBus " "NameOwnerChange signal: %s", g_variant_get_type_string(parameters)); return; } GVariant *name_variant = g_variant_get_child_value(parameters, 0); const gchar *name = g_variant_get_string(name_variant, NULL); gchar *player_id = player_id_from_bus_name(name); if (player_id == NULL) { g_variant_unref(name_variant); return; } GBusType bus_type = 0; if (proxy == manager->priv->session_proxy) { bus_type = G_BUS_TYPE_SESSION; } else if (proxy == manager->priv->system_proxy) { bus_type = G_BUS_TYPE_SYSTEM; } else { g_error("got unknown proxy in callback (this is a bug in playerctl)"); g_variant_unref(name_variant); return; } GVariant *previous_owner_variant = g_variant_get_child_value(parameters, 1); const gchar *previous_owner = g_variant_get_string(previous_owner_variant, NULL); GVariant *new_owner_variant = g_variant_get_child_value(parameters, 2); const gchar *new_owner = g_variant_get_string(new_owner_variant, NULL); GList *player_entry = NULL; if (strlen(new_owner) == 0 && strlen(previous_owner) != 0) { // the name has vanished player_entry = pctl_player_name_find(manager->priv->player_names, player_id, pctl_bus_type_to_source(bus_type)); if (player_entry != NULL) { PlayerctlPlayerName *player_name = player_entry->data; manager->priv->player_names = g_list_remove_link(manager->priv->player_names, player_entry); manager_remove_managed_player_by_name(manager, player_name); g_signal_emit(manager, connection_signals[NAME_VANISHED], 0, player_name); pctl_player_name_list_destroy(player_entry); } } else if (strlen(previous_owner) == 0 && strlen(new_owner) != 0) { // the name has appeared player_entry = pctl_player_name_find(manager->priv->players, player_id, pctl_bus_type_to_source(bus_type)); if (player_entry == NULL) { PlayerctlPlayerName *player_name = pctl_player_name_new(player_id, pctl_bus_type_to_source(bus_type)); manager->priv->player_names = g_list_prepend(manager->priv->player_names, player_name); g_signal_emit(manager, connection_signals[NAME_APPEARED], 0, player_name); } } g_free(player_id); g_variant_unref(name_variant); g_variant_unref(previous_owner_variant); g_variant_unref(new_owner_variant); } static gboolean playerctl_player_manager_initable_init(GInitable *initable, GCancellable *cancellable, GError **error) { GError *tmp_error = NULL; PlayerctlPlayerManager *manager = PLAYERCTL_PLAYER_MANAGER(initable); if (manager->priv->initted) { return TRUE; } manager->priv->session_proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", NULL, &tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); return FALSE; } manager->priv->system_proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", NULL, &tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); return FALSE; } manager->priv->player_names = playerctl_list_players(&tmp_error); if (tmp_error != NULL) { g_propagate_error(error, tmp_error); return FALSE; } g_signal_connect(G_DBUS_PROXY(manager->priv->session_proxy), "g-signal", G_CALLBACK(dbus_name_owner_changed_callback), manager); g_signal_connect(G_DBUS_PROXY(manager->priv->system_proxy), "g-signal", G_CALLBACK(dbus_name_owner_changed_callback), manager); manager->priv->initted = TRUE; return TRUE; } static void playerctl_player_manager_initable_iface_init(GInitableIface *iface) { iface->init = playerctl_player_manager_initable_init; } /** * playerctl_player_manager_new: * @err:(allow-none): The location of a GError or NULL. * * Create a new player manager that contains a list of player names available * in the #PlayerctlPlayerManager:player-names property. You can create new * players from the names with the playerctl_player_new_from_name() function * and then start managing them with the * playerctl_player_manager_manage_player() function. * * Returns:(transfer full): A new #PlayerctlPlayerManager. */ PlayerctlPlayerManager *playerctl_player_manager_new(GError **err) { GError *tmp_error = NULL; PlayerctlPlayerManager *manager = g_initable_new(PLAYERCTL_TYPE_PLAYER_MANAGER, NULL, &tmp_error, NULL); if (tmp_error != NULL) { g_propagate_error(err, tmp_error); return NULL; } return manager; } /** * playerctl_player_manager_set_sort_func: * @manager: A #PlayerctlPlayerManager. * @sort_func: The compare function to be used to sort the * #PlayerctlPlayerManager:players. * @sort_data:(allow-none): User data for the sort function. * @notify:(allow-none): A function to notify when the sort function will no * longer be used. * * Keeps the #PlayerctlPlayerManager:players list of this manager in sorted order which is useful for * using this list as a priority queue. */ void playerctl_player_manager_set_sort_func(PlayerctlPlayerManager *manager, GCompareDataFunc sort_func, gpointer *sort_data, GDestroyNotify notify) { // TODO figure out how to make this work with the bindings manager->priv->sort_func = sort_func; manager->priv->sort_data = sort_data; manager->priv->sort_notify = notify; manager->priv->players = g_list_sort_with_data(manager->priv->players, sort_func, sort_data); } /** * playerctl_player_manager_move_player_to_top: * @manager: A #PlayerctlPlayerManager * @player: A #PlayerctlPlayer in the list of #PlayerctlPlayerManager:players * * Moves the player to the top of the list of #PlayerctlPlayerManager:players. If this manager has a * sort function set with playerctl_player_manager_set_sort_func(), the list of * players will be sorted afterward, but will be on top of equal players in the * sorted order. */ void playerctl_player_manager_move_player_to_top(PlayerctlPlayerManager *manager, PlayerctlPlayer *player) { GList *l; for (l = manager->priv->players; l != NULL; l = l->next) { PlayerctlPlayer *current = PLAYERCTL_PLAYER(l->data); if (current == player) { manager->priv->players = g_list_remove_link(manager->priv->players, l); manager->priv->players = g_list_concat(l, manager->priv->players); if (manager->priv->sort_func) { manager->priv->players = g_list_sort_with_data(manager->priv->players, manager->priv->sort_func, manager->priv->sort_data); } break; } } } /** * playerctl_player_manager_manage_player: * @manager: A #PlayerctlPlayerManager * @player: A #PlayerctlPlayer to manage * * Add the given player to the list of managed players. Takes a reference to * the player (so you can unref it after you call this function). The player * will automatically be unreffed and removed from the list of * #PlayerctlPlayerManager:players when * it disconnects and the #PlayerctlPlayerManager::player-vanished signal will * be emitted on the manager. */ void playerctl_player_manager_manage_player(PlayerctlPlayerManager *manager, PlayerctlPlayer *player) { if (player == NULL) { return; } GList *l = NULL; for (l = manager->priv->players; l != NULL; l = l->next) { PlayerctlPlayer *current = PLAYERCTL_PLAYER(l->data); if (player == current) { return; } } if (manager->priv->sort_func) { manager->priv->players = g_list_insert_sorted_with_data(manager->priv->players, player, manager->priv->sort_func, manager->priv->sort_data); } else { manager->priv->players = g_list_prepend(manager->priv->players, player); } g_object_ref(player); g_signal_emit(manager, connection_signals[PLAYER_APPEARED], 0, player); } playerctl-2.0.2/playerctl/playerctl-player-manager.h000066400000000000000000000111021344655314500225700ustar00rootroot00000000000000/* * This file is part of playerctl. * * playerctl is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * playerctl 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 Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License * along with playerctl If not, see . * * Copyright © 2014, Tony Crisci */ #ifndef __PLAYERCTL_PLAYER_MANAGER_H__ #define __PLAYERCTL_PLAYER_MANAGER_H__ #if !defined(__PLAYERCTL_INSIDE__) && !defined(PLAYERCTL_COMPILATION) #error "Only can be included directly." #endif #include #include /** * SECTION: playerctl-player-manager * @short_description: A class to watch for players appearing and vanishing. * * The #PlayerctlPlayerManager is a class to watch for players appearing and * vanishing. When a player opens and is available to control by `playerctl`, * the #PlayerctlPlayerManager::name-appeared event will be emitted on the * manager during the main loop. You can inspect this #PlayerctlPlayerName to * see if you want to manage it. If you do, create a #PlayerctlPlayer from it * with the playerctl_player_new_from_name() function. The manager is also * capable of keeping an up-to-date list of players you want it to manage in * the #PlayerctlPlayerManager:players list. These players are connected and * should be able to be controlled. Managing players is optional, and you can * do so manually if you like. * * When the player disconnects, the #PlayerctlPlayerManager::name-vanished * event will be emitted. If the player is managed and is going to be removed * from the list, the #PlayerctlPlayerManager::player-vanished event will also * be emitted. After this event, the player will be cleaned up and removed from * the manager. * * The manager has other features such as being able to keep the players in a * sorted order and moving a player to the top of the list. The * #PlayerctlPlayerManager:player-names will always be in the order that they * were known to appear after the manager was created. * * For examples on how to use the manager, see the `examples` folder in the git * repository. */ #define PLAYERCTL_TYPE_PLAYER_MANAGER (playerctl_player_manager_get_type()) #define PLAYERCTL_PLAYER_MANAGER(obj) \ (G_TYPE_CHECK_INSTANCE_CAST((obj), PLAYERCTL_TYPE_PLAYER_MANAGER, PlayerctlPlayerManager)) #define PLAYERCTL_IS_PLAYER_MANAGER(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLAYERCTL_TYPE_PLAYER_MANAGER)) #define PLAYERCTL_PLAYER_MANAGER_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST((klass), PLAYERCTL_TYPE_PLAYER_MANAGER, \ PlayerctlPlayerManagerClass)) #define PLAYERCTL_IS_PLAYER_MANAGER_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE((klass), PLAYERCTL_TYPE_PLAYER_MANAGER)) #define PLAYERCTL_PLAYER_MANAGER_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS((obj), PLAYERCTL_TYPE_PLAYER_MANAGER, \ PlayerctlPlayerManagerClass)) typedef struct _PlayerctlPlayerManager PlayerctlPlayerManager; typedef struct _PlayerctlPlayerManagerClass PlayerctlPlayerManagerClass; typedef struct _PlayerctlPlayerManagerPrivate PlayerctlPlayerManagerPrivate; struct _PlayerctlPlayerManager { /* Parent instance structure */ GObject parent_instance; /* Private members */ PlayerctlPlayerManagerPrivate *priv; }; struct _PlayerctlPlayerManagerClass { /* Parent class structure */ GObjectClass parent_class; }; GType playerctl_player_manager_get_type(void); PlayerctlPlayerManager *playerctl_player_manager_new(GError **err); void playerctl_player_manager_manage_player(PlayerctlPlayerManager *manager, PlayerctlPlayer *player); void playerctl_player_manager_set_sort_func(PlayerctlPlayerManager *manager, GCompareDataFunc sort_func, gpointer *sort_data, GDestroyNotify notify); void playerctl_player_manager_move_player_to_top(PlayerctlPlayerManager *manager, PlayerctlPlayer *player); #endif /* __PLAYERCTL_PLAYER_MANAGER_H__ */ playerctl-2.0.2/playerctl/playerctl-player-name.c000066400000000000000000000035051344655314500221010ustar00rootroot00000000000000/* * This file is part of playerctl. * * playerctl is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * playerctl 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 Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License * along with playerctl If not, see . * * Copyright © 2014, Tony Crisci and contributors. */ #include "playerctl-player-name.h" /** * playerctl_player_name_copy: * @name: a #PlayerctlPlayerName * * Creates a dynamically allocated name name container as a copy of * @name. * * Returns: (transfer full): a newly-allocated copy of @name */ PlayerctlPlayerName *playerctl_player_name_copy(PlayerctlPlayerName *name) { PlayerctlPlayerName *retval; g_return_val_if_fail(name != NULL, NULL); retval = g_slice_new0(PlayerctlPlayerName); *retval = *name; retval->source = name->source; retval->instance = g_strdup(name->instance); retval->name = g_strdup(name->name); return retval; } /** * playerctl_player_name_free: * @name:(allow-none): a #PlayerctlPlayerName * * Frees @name. If @name is %NULL, it simply returns. */ void playerctl_player_name_free(PlayerctlPlayerName *name) { if (name == NULL) { return; } g_free(name->instance); g_free(name->name); g_slice_free(PlayerctlPlayerName, name); } G_DEFINE_BOXED_TYPE(PlayerctlPlayerName, playerctl_player_name, playerctl_player_name_copy, playerctl_player_name_free); playerctl-2.0.2/playerctl/playerctl-player-name.h000066400000000000000000000056031344655314500221070ustar00rootroot00000000000000/* * This file is part of playerctl. * * playerctl is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * playerctl 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 Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License * along with playerctl If not, see . * * Copyright © 2014, Tony Crisci and contributors */ #ifndef __PLAYERCTL_PLAYER_NAME_H__ #define __PLAYERCTL_PLAYER_NAME_H__ #include #include /** * SECTION: playerctl-player-name * @short_description: Contains connection information that fully qualifies a * potential connection to a player. * * Contains connection information that fully qualifies a potential connection * to a player. You should not have to construct one of these directly. You can * list the names that are available to control from the * playerctl_list_players() function or use the * #PlayerctlPlayerManager:player-names property from a * #PlayerctlPlayerManager. * * Once you have gotten a player name like this, you can check the type of * player with the "name" property to see if you are interested in connecting * to it. If you are, you can pass it directly to the * playerctl_player_new_from_name() function to get a #PlayerctlPlayer that is * connected to this name and ready to command and query. */ /** * PlayerctlSource * @PLAYERCTL_SOURCE_NONE: Only for unitialized players. Source will be chosen automatically. * @PLAYERCTL_SOURCE_DBUS_SESSION: The player is on the DBus session bus. * @PLAYERCTL_SOURCE_DBUS_SYSTEM: The player is on the DBus system bus. * * The source of the name used to control the player. * */ typedef enum { PLAYERCTL_SOURCE_NONE, PLAYERCTL_SOURCE_DBUS_SESSION, PLAYERCTL_SOURCE_DBUS_SYSTEM, } PlayerctlSource; typedef struct _PlayerctlPlayerName PlayerctlPlayerName; #define PLAYERCTL_TYPE_PLAYER_NAME (playerctl_player_name_get_type()) void playerctl_player_name_free(PlayerctlPlayerName *name); PlayerctlPlayerName *playerctl_player_name_copy(PlayerctlPlayerName *name); GType playerctl_player_name_get_type(void); /** * PlayerctlPlayerName: * @name: the name of the type of player. * @instance: the complete name and instance of the player. * @source: the source of the player name. * * Event container for when names of players appear or disapear as the * controllable media player applications open and close. */ struct _PlayerctlPlayerName { gchar *name; gchar *instance; PlayerctlSource source; }; #endif /* __PLAYERCTL_PLAYER_NAME_H__ */ playerctl-2.0.2/playerctl/playerctl-player.c000066400000000000000000001664441344655314500211770ustar00rootroot00000000000000/* * This file is part of playerctl. * * playerctl is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * playerctl 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 Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License * along with playerctl If not, see . * * Copyright © 2014, Tony Crisci and contributors. */ #include #include #include #include "playerctl-common.h" #include "playerctl-generated.h" #include "playerctl-player.h" #include #include #include #define LENGTH(array) (sizeof array / sizeof array[0]) enum { PROP_0, PROP_PLAYER_NAME, PROP_PLAYER_INSTANCE, PROP_SOURCE, PROP_PLAYBACK_STATUS, PROP_LOOP_STATUS, PROP_SHUFFLE, PROP_STATUS, // deprecated PROP_VOLUME, PROP_METADATA, PROP_POSITION, PROP_CAN_CONTROL, PROP_CAN_PLAY, PROP_CAN_PAUSE, PROP_CAN_SEEK, PROP_CAN_GO_NEXT, PROP_CAN_GO_PREVIOUS, N_PROPERTIES }; enum { // PROPERTIES_CHANGED, PLAYBACK_STATUS, LOOP_STATUS, SHUFFLE, PLAY, // deprecated PAUSE, // deprecated STOP, // deprecated METADATA, VOLUME, SEEKED, EXIT, LAST_SIGNAL }; static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, }; static guint connection_signals[LAST_SIGNAL] = {0}; struct _PlayerctlPlayerPrivate { OrgMprisMediaPlayer2Player *proxy; gchar *player_name; gchar *instance; PlayerctlSource source; GError *init_error; gboolean initted; PlayerctlPlaybackStatus cached_status; gint64 cached_position; gchar *cached_track_id; struct timespec cached_position_monotonic; }; static inline int64_t timespec_to_usec(const struct timespec *a) { return (int64_t)a->tv_sec * 1e+6 + a->tv_nsec / 1000; } static gint64 calculate_cached_position(PlayerctlPlaybackStatus status, struct timespec *position_monotonic, gint64 position) { gint64 offset = 0; struct timespec current_time; switch (status) { case PLAYERCTL_PLAYBACK_STATUS_PLAYING: clock_gettime(CLOCK_MONOTONIC, ¤t_time); offset = timespec_to_usec(¤t_time) - timespec_to_usec(position_monotonic); return position + offset; case PLAYERCTL_PLAYBACK_STATUS_PAUSED: return position; default: return 0; } } static gchar *metadata_get_track_id(GVariant *metadata) { GVariant *track_id_variant = g_variant_lookup_value( metadata, "mpris:trackid", G_VARIANT_TYPE_OBJECT_PATH); if (track_id_variant == NULL) { // XXX some players set this as a string, which is against the protocol, // but a lot of them do it and I don't feel like fixing it on all the // players in the world. track_id_variant = g_variant_lookup_value(metadata, "mpris:trackid", G_VARIANT_TYPE_STRING); } if (track_id_variant != NULL) { const gchar *track_id = g_variant_get_string(track_id_variant, NULL); g_variant_unref(track_id_variant); return g_strdup(track_id); } return NULL; } static void playerctl_player_properties_changed_callback( GDBusProxy *_proxy, GVariant *changed_properties, const gchar *const *invalidated_properties, gpointer user_data) { PlayerctlPlayer *self = user_data; // TODO probably need to replace this with an iterator GVariant *metadata = g_variant_lookup_value(changed_properties, "Metadata", NULL); GVariant *playback_status = g_variant_lookup_value(changed_properties, "PlaybackStatus", NULL); GVariant *loop_status = g_variant_lookup_value(changed_properties, "LoopStatus", NULL); GVariant *volume = g_variant_lookup_value(changed_properties, "Volume", NULL); GVariant *shuffle = g_variant_lookup_value(changed_properties, "Shuffle", NULL); if (shuffle != NULL) { gboolean shuffle_value = g_variant_get_boolean(shuffle); g_signal_emit(self, connection_signals[SHUFFLE], 0, shuffle_value); g_variant_unref(shuffle); } if (volume != NULL) { gdouble volume_value = g_variant_get_double(volume); g_signal_emit(self, connection_signals[VOLUME], 0, volume_value); g_variant_unref(volume); } gboolean track_id_invalidated = FALSE; if (metadata != NULL) { // update the cached track id gchar *track_id = metadata_get_track_id(metadata); if ((track_id == NULL && self->priv->cached_track_id != NULL) || (track_id != NULL && self->priv->cached_track_id == NULL) || (g_strcmp0(track_id, self->priv->cached_track_id) != 0)) { g_free(self->priv->cached_track_id); self->priv->cached_track_id = track_id; track_id_invalidated = TRUE; } else { g_free(track_id); } g_signal_emit(self, connection_signals[METADATA], 0, metadata); g_variant_unref(metadata); } if (track_id_invalidated) { self->priv->cached_position = 0; clock_gettime(CLOCK_MONOTONIC, &self->priv->cached_position_monotonic); } if (playback_status == NULL && track_id_invalidated) { // XXX: Lots of player aren't setting status correctly when the track // changes so we have to get it from the interface. We should // definitely go fix this bug on the players. GVariant *call_reply = g_dbus_proxy_call_sync( G_DBUS_PROXY(self->priv->proxy), "org.freedesktop.DBus.Properties.Get", g_variant_new("(ss)", "org.mpris.MediaPlayer2.Player", "PlaybackStatus"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL); if (call_reply != NULL) { GVariant *call_reply_box = g_variant_get_child_value(call_reply, 0); playback_status = g_variant_get_child_value(call_reply_box, 0); g_variant_unref(call_reply); g_variant_unref(call_reply_box); } } if (loop_status != NULL) { const gchar *status_str = g_variant_get_string(loop_status, NULL); PlayerctlLoopStatus status = 0; GQuark quark = 0; if (pctl_parse_loop_status(status_str, &status)) { switch (status) { case PLAYERCTL_LOOP_STATUS_TRACK: quark = g_quark_from_string("track"); break; case PLAYERCTL_LOOP_STATUS_PLAYLIST: quark = g_quark_from_string("playlist"); break; case PLAYERCTL_LOOP_STATUS_NONE: quark = g_quark_from_string("none"); break; } g_signal_emit(self, connection_signals[LOOP_STATUS], quark, status); } g_variant_unref(loop_status); } if (playback_status != NULL) { const gchar *status_str = g_variant_get_string(playback_status, NULL); PlayerctlPlaybackStatus status = 0; GQuark quark = 0; if (pctl_parse_playback_status(status_str, &status)) { switch (status) { case PLAYERCTL_PLAYBACK_STATUS_PLAYING: quark = g_quark_from_string("playing"); if (self->priv->cached_status != PLAYERCTL_PLAYBACK_STATUS_PLAYING) { clock_gettime(CLOCK_MONOTONIC, &self->priv->cached_position_monotonic); } g_signal_emit(self, connection_signals[PLAY], 0); break; case PLAYERCTL_PLAYBACK_STATUS_PAUSED: quark = g_quark_from_string("paused"); self->priv->cached_position = calculate_cached_position(self->priv->cached_status, &self->priv->cached_position_monotonic, self->priv->cached_position); // DEPRECATED g_signal_emit(self, connection_signals[PAUSE], 0); break; case PLAYERCTL_PLAYBACK_STATUS_STOPPED: self->priv->cached_position = 0; quark = g_quark_from_string("stopped"); // DEPRECATED g_signal_emit(self, connection_signals[STOP], 0); break; } if (self->priv->cached_status != status) { self->priv->cached_status = status; g_signal_emit(self, connection_signals[PLAYBACK_STATUS], quark, status); } } else { g_warning("got unknown playback state: %s", status_str); } g_variant_unref(playback_status); } } static void playerctl_player_seeked_callback(GDBusProxy *_proxy, gint64 position, gpointer *user_data) { PlayerctlPlayer *player = PLAYERCTL_PLAYER(user_data); player->priv->cached_position = position; clock_gettime(CLOCK_MONOTONIC, &player->priv->cached_position_monotonic); g_signal_emit(player, connection_signals[SEEKED], 0, position); } static void playerctl_player_initable_iface_init(GInitableIface *iface); G_DEFINE_TYPE_WITH_CODE(PlayerctlPlayer, playerctl_player, G_TYPE_OBJECT, G_ADD_PRIVATE(PlayerctlPlayer) G_IMPLEMENT_INTERFACE( G_TYPE_INITABLE, playerctl_player_initable_iface_init)); G_DEFINE_QUARK(playerctl-player-error-quark, playerctl_player_error); static GVariant *playerctl_player_get_metadata(PlayerctlPlayer *self, GError **err) { GVariant *metadata; GError *tmp_error = NULL; g_main_context_iteration(NULL, FALSE); metadata = org_mpris_media_player2_player_dup_metadata(self->priv->proxy); if (!metadata) { // XXX: Ugly spotify workaround. Spotify does not seem to use the property // cache. We have to get the properties directly. GVariant *call_reply = g_dbus_proxy_call_sync( G_DBUS_PROXY(self->priv->proxy), "org.freedesktop.DBus.Properties.Get", g_variant_new("(ss)", "org.mpris.MediaPlayer2.Player", "Metadata"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &tmp_error); if (tmp_error != NULL) { g_propagate_error(err, tmp_error); return NULL; } GVariant *call_reply_properties = g_variant_get_child_value(call_reply, 0); metadata = g_variant_get_child_value(call_reply_properties, 0); g_variant_unref(call_reply); g_variant_unref(call_reply_properties); } return metadata; } static void playerctl_player_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PlayerctlPlayer *self = PLAYERCTL_PLAYER(object); switch (property_id) { case PROP_PLAYER_NAME: g_free(self->priv->player_name); self->priv->player_name = g_strdup(g_value_get_string(value)); break; case PROP_PLAYER_INSTANCE: g_free(self->priv->instance); self->priv->instance = g_strdup(g_value_get_string(value)); break; case PROP_SOURCE: self->priv->source = g_value_get_enum(value); break; case PROP_VOLUME: g_warning("setting the volume property directly is deprecated and will " "be removed in a future version. Use " "playerctl_player_set_volume() instead."); org_mpris_media_player2_player_set_volume(self->priv->proxy, g_value_get_double(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void playerctl_player_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PlayerctlPlayer *self = PLAYERCTL_PLAYER(object); switch (property_id) { case PROP_PLAYER_NAME: g_value_set_string(value, self->priv->player_name); break; case PROP_PLAYER_INSTANCE: g_value_set_string(value, self->priv->instance); break; case PROP_SOURCE: g_value_set_enum(value,self->priv->source); break; case PROP_PLAYBACK_STATUS: g_value_set_enum(value, self->priv->cached_status); break; case PROP_LOOP_STATUS: { const gchar *status_str = org_mpris_media_player2_player_get_loop_status(self->priv->proxy); PlayerctlLoopStatus status = 0; if (pctl_parse_loop_status(status_str, &status)) { g_value_set_enum(value, status); } else { if (status_str != NULL) { g_warning("got unknown loop status: %s", status_str); } g_value_set_enum(value, PLAYERCTL_LOOP_STATUS_NONE); } break; } case PROP_SHUFFLE: { if (self->priv->proxy == NULL) { g_value_set_boolean(value, FALSE); break; } g_main_context_iteration(NULL, FALSE); g_value_set_boolean(value, org_mpris_media_player2_player_get_shuffle(self->priv->proxy)); break; } case PROP_STATUS: // DEPRECATED if (self->priv->proxy) { g_value_set_string(value, pctl_playback_status_to_string(self->priv->cached_status)); } else { g_value_set_string(value, ""); } break; case PROP_METADATA: { GError *error = NULL; GVariant *metadata = NULL; metadata = playerctl_player_get_metadata(self, &error); if (error != NULL) { g_error("could not get metadata: %s", error->message); g_clear_error(&error); } g_value_set_variant(value, metadata); break; } case PROP_VOLUME: if (self->priv->proxy) { g_main_context_iteration(NULL, FALSE); g_value_set_double(value, org_mpris_media_player2_player_get_volume( self->priv->proxy)); } else { g_value_set_double(value, 0); } break; case PROP_POSITION: { gint64 position = calculate_cached_position(self->priv->cached_status, &self->priv->cached_position_monotonic, self->priv->cached_position); g_value_set_int64(value, position); break; } case PROP_CAN_CONTROL: if (self->priv->proxy == NULL) { g_value_set_boolean(value, FALSE); break; } g_main_context_iteration(NULL, FALSE); g_value_set_boolean(value, org_mpris_media_player2_player_get_can_control(self->priv->proxy)); break; case PROP_CAN_PLAY: if (self->priv->proxy == NULL) { g_value_set_boolean(value, FALSE); break; } g_main_context_iteration(NULL, FALSE); g_value_set_boolean(value, org_mpris_media_player2_player_get_can_play(self->priv->proxy)); break; case PROP_CAN_PAUSE: if (self->priv->proxy == NULL) { g_value_set_boolean(value, FALSE); break; } g_main_context_iteration(NULL, FALSE); g_value_set_boolean(value, org_mpris_media_player2_player_get_can_pause(self->priv->proxy)); break; case PROP_CAN_SEEK: if (self->priv->proxy == NULL) { g_value_set_boolean(value, FALSE); break; } g_main_context_iteration(NULL, FALSE); g_value_set_boolean(value, org_mpris_media_player2_player_get_can_seek(self->priv->proxy)); break; case PROP_CAN_GO_NEXT: if (self->priv->proxy == NULL) { g_value_set_boolean(value, FALSE); break; } g_main_context_iteration(NULL, FALSE); g_value_set_boolean(value, org_mpris_media_player2_player_get_can_go_next(self->priv->proxy)); break; case PROP_CAN_GO_PREVIOUS: if (self->priv->proxy == NULL) { g_value_set_boolean(value, FALSE); break; } g_main_context_iteration(NULL, FALSE); g_value_set_boolean(value, org_mpris_media_player2_player_get_can_go_previous(self->priv->proxy)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void playerctl_player_constructed(GObject *gobject) { PlayerctlPlayer *self = PLAYERCTL_PLAYER(gobject); self->priv->init_error = NULL; g_initable_init((GInitable *)self, NULL, &self->priv->init_error); G_OBJECT_CLASS(playerctl_player_parent_class)->constructed(gobject); } static void playerctl_player_dispose(GObject *gobject) { PlayerctlPlayer *self = PLAYERCTL_PLAYER(gobject); g_clear_error(&self->priv->init_error); g_clear_object(&self->priv->proxy); G_OBJECT_CLASS(playerctl_player_parent_class)->dispose(gobject); } static void playerctl_player_finalize(GObject *gobject) { PlayerctlPlayer *self = PLAYERCTL_PLAYER(gobject); g_free(self->priv->player_name); g_free(self->priv->instance); g_free(self->priv->cached_track_id); G_OBJECT_CLASS(playerctl_player_parent_class)->finalize(gobject); } static void playerctl_player_class_init(PlayerctlPlayerClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS(klass); gobject_class->set_property = playerctl_player_set_property; gobject_class->get_property = playerctl_player_get_property; gobject_class->constructed = playerctl_player_constructed; gobject_class->dispose = playerctl_player_dispose; gobject_class->finalize = playerctl_player_finalize; obj_properties[PROP_PLAYER_NAME] = g_param_spec_string( "player-name", "Player name", "The name of the type of player this is. " "The instance is fully qualified with the player-instance and the " "source.", NULL, /* default */ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); obj_properties[PROP_PLAYER_INSTANCE] = g_param_spec_string( "player-instance", "Player instance", "An instance name that identifies " "this player on the source", NULL, /* default */ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); obj_properties[PROP_SOURCE] = g_param_spec_enum("source", "Player source", "The source of this player. Currently supported " "sources are the DBus session bus and DBus system bus.", playerctl_source_get_type(), G_BUS_TYPE_NONE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_PLAYBACK_STATUS] = g_param_spec_enum("playback-status", "Player playback status", "Whether the player is playing, paused, or stopped", playerctl_playback_status_get_type(), PLAYERCTL_PLAYBACK_STATUS_STOPPED, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_LOOP_STATUS] = g_param_spec_enum("loop-status", "Player loop status", "The loop status of the player", playerctl_loop_status_get_type(), PLAYERCTL_LOOP_STATUS_NONE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_SHUFFLE] = g_param_spec_boolean( "shuffle", "Shuffle", "A value of false indicates that playback is " "progressing linearly through a playlist, while true means playback is " "progressing through a playlist in some other order.", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * PlayerctlPlayer:status: * * The playback status of the player as a string * * Deprecated:2.0.0: Use the "playback-status" signal instead. */ obj_properties[PROP_STATUS] = g_param_spec_string("status", "Player status", "The play status of the player (deprecated: use " "playback-status)", NULL, /* default */ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED); obj_properties[PROP_VOLUME] = g_param_spec_double( "volume", "Player volume", "The volume level of the player. Setting " "this property directly is deprecated and this property will become read " "only in a future version. Use playerctl_player_set_volume() to set the " "volume.", 0, 100, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_POSITION] = g_param_spec_int64( "position", "Player position", "The position in the current track of the player in microseconds", 0, INT64_MAX, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_METADATA] = g_param_spec_variant( "metadata", "Player metadata", "The metadata of the currently playing track as an array of key-value " "pairs. The metadata available depends on the track, but may include the " "artist, title, length, art url, and other metadata.", g_variant_type_new("a{sv}"), NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_CAN_CONTROL] = g_param_spec_boolean( "can-control", "Can control", "Whether the player can be controlled by playerctl", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_CAN_PLAY] = g_param_spec_boolean( "can-play", "Can play", "Whether the player can start playing and has a " "current track.", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_CAN_PAUSE] = g_param_spec_boolean( "can-pause", "Can pause", "Whether the player can pause", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_CAN_SEEK] = g_param_spec_boolean( "can-seek", "Can seek", "Whether the position of the player can be controlled", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_CAN_GO_NEXT] = g_param_spec_boolean( "can-go-next", "Can go next", "Whether the player can go to the next track", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_CAN_GO_PREVIOUS] = g_param_spec_boolean( "can-go-previous", "Can go previous", "Whether the player can go to the previous track", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(gobject_class, N_PROPERTIES, obj_properties); /** * PlayerctlPlayer::playback-status: * @player: the player this event was emitted on * @playback_status: the playback status of the player * * Emitted when the playback status changes. Detail will be "playing", * "paused", or "stopped" which you can listen to by connecting to the * "playback-status::[STATUS]" signal. */ connection_signals[PLAYBACK_STATUS] = g_signal_new("playback-status", /* signal_name */ PLAYERCTL_TYPE_PLAYER, /* itype */ G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED, /* signal_flags */ 0, /* class_offset */ NULL, /* accumulator */ NULL, /* accu_data */ g_cclosure_marshal_VOID__ENUM, /* c_marshaller */ G_TYPE_NONE, /* return_type */ 1, /* n_params */ playerctl_playback_status_get_type()); /** * PlayerctlPlayer::loop-status: * @player: the player this event was emitted on * @loop_status: the loop status of the player * * Emitted when the loop status changes. */ connection_signals[LOOP_STATUS] = g_signal_new("loop-status", /* signal_name */ PLAYERCTL_TYPE_PLAYER, /* itype */ G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED, /* signal_flags */ 0, /* class_offset */ NULL, /* accumulator */ NULL, /* accu_data */ g_cclosure_marshal_VOID__ENUM, /* c_marshaller */ G_TYPE_NONE, /* return_type */ 1, /* n_params */ playerctl_loop_status_get_type()); /** * PlayerctlPlayer::shuffle: * @player: the player this event was emitted on * @shuffle_status: the shuffle status of the player * * Emitted when the shuffle status changes. */ connection_signals[SHUFFLE] = g_signal_new("shuffle", /* signal_name */ PLAYERCTL_TYPE_PLAYER, /* itype */ G_SIGNAL_RUN_FIRST, /* signal_flags */ 0, /* class_offset */ NULL, /* accumulator */ NULL, /* accu_data */ g_cclosure_marshal_VOID__BOOLEAN,/* c_marshaller */ G_TYPE_NONE, /* return_type */ 1, /* n_params */ G_TYPE_BOOLEAN); /** * PlayerctlPlayer::play: * @player: the player this event was emitted on * * Emitted when the player begins to play. * * Deprecated:2.0.0: Use the "playback-status::playing" signal instead. */ connection_signals[PLAY] = g_signal_new("play", /* signal_name */ PLAYERCTL_TYPE_PLAYER, /* itype */ G_SIGNAL_RUN_FIRST | G_SIGNAL_DEPRECATED, /* signal_flags */ 0, /* class_offset */ NULL, /* accumulator */ NULL, /* accu_data */ g_cclosure_marshal_VOID__VOID, /* c_marshaller */ G_TYPE_NONE, /* return_type */ 0); /* n_params */ /** * PlayerctlPlayer::pause: * @player: the player this event was emitted on * * Emitted when the player pauses. * * Deprecated:2.0.0: Use the "playback-status::paused" signal instead. */ connection_signals[PAUSE] = g_signal_new("pause", /* signal_name */ PLAYERCTL_TYPE_PLAYER, /* itype */ G_SIGNAL_RUN_FIRST | G_SIGNAL_DEPRECATED, /* signal_flags */ 0, /* class_offset */ NULL, /* accumulator */ NULL, /* accu_data */ g_cclosure_marshal_VOID__VOID, /* c_marshaller */ G_TYPE_NONE, /* return_type */ 0); /* n_params */ /** * PlayerctlPlayer::stop: * @player: the player this event was emitted on * * Emitted when the player stops. * * Deprecated:2.0.0: Use the "playback-status::stopped" signal instead. */ connection_signals[STOP] = g_signal_new("stop", /* signal_name */ PLAYERCTL_TYPE_PLAYER, /* itype */ G_SIGNAL_RUN_FIRST | G_SIGNAL_DEPRECATED, /* signal_flags */ 0, /* class_offset */ NULL, /* accumulator */ NULL, /* accu_data */ g_cclosure_marshal_VOID__VOID, /* c_marshaller */ G_TYPE_NONE, /* return_type */ 0); /* n_params */ /** * PlayerctlPlayer::metadata: * @player: the player this event was emitted on * @metadata: the metadata for the currently playing track. * * Emitted when the metadata for the currently playing track changes. */ connection_signals[METADATA] = g_signal_new("metadata", /* signal_name */ PLAYERCTL_TYPE_PLAYER, /* itype */ G_SIGNAL_RUN_FIRST, /* signal_flags */ 0, /* class_offset */ NULL, /* accumulator */ NULL, /* accu_data */ g_cclosure_marshal_VOID__VARIANT, /* c_marshaller */ G_TYPE_NONE, /* return_type */ 1, /* n_params */ G_TYPE_VARIANT); /** * PlayerctlPlayer::volume: * @player: the player this event was emitted on * @volume: the volume of the player from 0 to 100. * * Emitted when the volume of the player changes. */ connection_signals[VOLUME] = g_signal_new("volume", /* signal_name */ PLAYERCTL_TYPE_PLAYER, /* itype */ G_SIGNAL_RUN_FIRST, /* signal_flags */ 0, /* class_offset */ NULL, /* accumulator */ NULL, /* accu_data */ g_cclosure_marshal_VOID__DOUBLE, /* c_marshaller */ G_TYPE_NONE, /* return_type */ 1, /* n_params */ G_TYPE_DOUBLE); /** * PlayerctlPlayer::seeked: * @player: the player this event was emitted on. * @position: the new position in the track in microseconds. * * Emitted when the track changes position unexpectedly or begins in a * position other than the beginning. Otherwise, position is assumed to * progress normally. */ connection_signals[SEEKED] = g_signal_new("seeked", /* signal_name */ PLAYERCTL_TYPE_PLAYER, /* itype */ G_SIGNAL_RUN_FIRST, /* signal_flags */ 0, /* class_offset */ NULL, /* accumulator */ NULL, /* accu_data */ g_cclosure_marshal_VOID__LONG, /* c_marshaller */ G_TYPE_NONE, /* return_type */ 1, /* n_params */ G_TYPE_INT64); /** * PlayerctlPlayer::exit: * @player: the player this event was emitted on. * * Emitted when the player has disconnected and will no longer respond to * queries and commands. */ connection_signals[EXIT] = g_signal_new("exit", /* signal_name */ PLAYERCTL_TYPE_PLAYER, /* itype */ G_SIGNAL_RUN_FIRST, /* signal_flags */ 0, /* class_offset */ NULL, /* accumulator */ NULL, /* accu_data */ g_cclosure_marshal_VOID__VOID, /* c_marshaller */ G_TYPE_NONE, /* return_type */ 0); /* n_params */ } static void playerctl_player_init(PlayerctlPlayer *self) { self->priv = playerctl_player_get_instance_private(self); } static GList *list_player_names_on_bus(GBusType bus_type, GError **err) { GError *tmp_error = NULL; GList *players = NULL; GDBusProxy *proxy = g_dbus_proxy_new_for_bus_sync( bus_type, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", NULL, &tmp_error); if (tmp_error != NULL) { g_propagate_error(err, tmp_error); return NULL; } GVariant *reply = g_dbus_proxy_call_sync( proxy, "ListNames", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &tmp_error); if (tmp_error != NULL) { g_propagate_error(err, tmp_error); g_object_unref(proxy); return NULL; } GVariant *reply_child = g_variant_get_child_value(reply, 0); gsize reply_count; const gchar **names = g_variant_get_strv(reply_child, &reply_count); size_t offset = strlen(MPRIS_PREFIX); for (gsize i = 0; i < reply_count; i += 1) { if (g_str_has_prefix(names[i], MPRIS_PREFIX)) { PlayerctlPlayerName *player_name = pctl_player_name_new(names[i] + offset, pctl_bus_type_to_source(bus_type)); players = g_list_append(players, player_name); } } g_object_unref(proxy); g_variant_unref(reply); g_variant_unref(reply_child); g_free(names); return players; } /* * Get the matching bus name for this player name. Bus name will be like: * "org.mpris.MediaPlayer2.{PLAYER_NAME}[.instance{NUM}]" * Pass a NULL player_name to get the first name on the bus * Returns NULL if no matching bus name is found on the bus. * Returns an error if there was a problem listing the names on the bus. */ static gchar *bus_name_for_player_name(gchar *name, GBusType bus_type, GError **err) { gchar *bus_name = NULL; GError *tmp_error = NULL; g_return_val_if_fail(err == NULL || *err == NULL, FALSE); GList *names = list_player_names_on_bus(bus_type, &tmp_error); if (tmp_error != NULL) { g_propagate_error(err, tmp_error); return NULL; } if (names == NULL) { return NULL; } if (name == NULL) { PlayerctlPlayerName *name = names->data; bus_name = g_strdup_printf(MPRIS_PREFIX "%s", name->instance); pctl_player_name_list_destroy(names); return bus_name; } GList *exact_match = pctl_player_name_find(names, name, pctl_bus_type_to_source(bus_type)); if (exact_match != NULL) { PlayerctlPlayerName *name = exact_match->data; bus_name = g_strdup_printf(MPRIS_PREFIX "%s", name->instance); g_list_free_full(names, (GDestroyNotify)playerctl_player_name_free); return bus_name; } GList *instance_match = pctl_player_name_find_instance(names, name, pctl_bus_type_to_source(bus_type)); if (instance_match != NULL) { gchar *name = instance_match->data; bus_name = g_strdup_printf(MPRIS_PREFIX "%s", name); pctl_player_name_list_destroy(names); return bus_name; } return NULL; } static void playerctl_player_name_owner_changed_callback(GObject *object, GParamSpec *pspec, gpointer *user_data) { PlayerctlPlayer *player = PLAYERCTL_PLAYER(user_data); GDBusProxy *proxy = G_DBUS_PROXY (object); char *name_owner = g_dbus_proxy_get_name_owner(proxy); if (name_owner == NULL) { g_signal_emit(player, connection_signals[EXIT], 0); } g_free(name_owner); } static gboolean playerctl_player_initable_init(GInitable *initable, GCancellable *cancellable, GError **err) { GError *tmp_error = NULL; PlayerctlPlayer *player = PLAYERCTL_PLAYER(initable); if (player->priv->initted) { return TRUE; } g_return_val_if_fail(err == NULL || *err == NULL, FALSE); if (player->priv->instance != NULL && player->priv->player_name != NULL) { g_set_error(err, playerctl_player_error_quark(), 3, "A player cannot be constructed with both name and instance"); return FALSE; } if (player->priv->instance != NULL && player->priv->source == PLAYERCTL_SOURCE_NONE) { g_set_error(err, playerctl_player_error_quark(), 3, "A player cannot be constructed with an instance and no source"); return FALSE; } gchar *bus_name = NULL; if (player->priv->instance != NULL) { bus_name = g_strdup_printf(MPRIS_PREFIX "%s", player->priv->instance); } else if (player->priv->source != PLAYERCTL_SOURCE_NONE) { // the source was specified bus_name = bus_name_for_player_name(player->priv->player_name, pctl_source_to_bus_type(player->priv->source), &tmp_error); if (tmp_error) { g_propagate_error(err, tmp_error); return FALSE; } } else { // the source was not specified const GBusType bus_types[] = { G_BUS_TYPE_SESSION, G_BUS_TYPE_SYSTEM }; for (int i = 0; i < LENGTH(bus_types); ++i) { bus_name = bus_name_for_player_name(player->priv->player_name, bus_types[i], &tmp_error); if (tmp_error != NULL) { g_propagate_error(err, tmp_error); return FALSE; } if (bus_name != NULL) { player->priv->source = pctl_bus_type_to_source(bus_types[i]); break; } } } if (bus_name == NULL) { g_set_error(err, playerctl_player_error_quark(), 1, "Player not found"); return FALSE; } /* org.mpris.MediaPlayer2.{NAME}[.instance{NUM}] */ int offset = strlen(MPRIS_PREFIX); gchar **split = g_strsplit(bus_name + offset, ".instance", 2); g_free(player->priv->player_name); player->priv->player_name = g_strdup(split[0]); g_strfreev(split); player->priv->proxy = org_mpris_media_player2_player_proxy_new_for_bus_sync( pctl_source_to_bus_type(player->priv->source), G_DBUS_PROXY_FLAGS_NONE, bus_name, "/org/mpris/MediaPlayer2", NULL, &tmp_error); if (tmp_error != NULL) { g_free(bus_name); g_propagate_error(err, tmp_error); return FALSE; } g_free(bus_name); // init the cache player->priv->cached_position = org_mpris_media_player2_player_get_position(player->priv->proxy); clock_gettime(CLOCK_MONOTONIC, &player->priv->cached_position_monotonic); const gchar *playback_status_str = org_mpris_media_player2_player_get_playback_status(player->priv->proxy); PlayerctlPlaybackStatus status = 0; if (pctl_parse_playback_status(playback_status_str, &status)) { player->priv->cached_status = status; } g_signal_connect(player->priv->proxy, "g-properties-changed", G_CALLBACK(playerctl_player_properties_changed_callback), player); g_signal_connect(player->priv->proxy, "seeked", G_CALLBACK(playerctl_player_seeked_callback), player); g_signal_connect(player->priv->proxy, "notify::g-name-owner", G_CALLBACK(playerctl_player_name_owner_changed_callback), player); player->priv->initted = TRUE; return TRUE; } static void playerctl_player_initable_iface_init(GInitableIface *iface) { iface->init = playerctl_player_initable_init; } /** * playerctl_list_players: * @err: The location of a GError or NULL * * Lists all the players that can be controlled by Playerctl. * * Returns:(transfer full) (element-type PlayerctlPlayerName): A list of player names. */ GList *playerctl_list_players(GError **err) { GError *tmp_error = NULL; g_return_val_if_fail(err == NULL || *err == NULL, NULL); GList *session_players = list_player_names_on_bus(G_BUS_TYPE_SESSION, &tmp_error); if (tmp_error != NULL) { g_propagate_error(err, tmp_error); return NULL; } GList *system_players = list_player_names_on_bus(G_BUS_TYPE_SYSTEM, &tmp_error); if (tmp_error != NULL) { g_propagate_error(err, tmp_error); return NULL; } GList *players = g_list_concat(session_players, system_players); g_list_free(system_players); return players; } /** * playerctl_player_new: * @player_name:(allow-none): The name to use to find the bus name of the player * @err: The location of a GError or NULL * * Allocates a new #PlayerctlPlayer and tries to connect to an instance of the * player with the given name. * * Returns:(transfer full): A new #PlayerctlPlayer connected to an instance of * the player or NULL if an error occurred */ PlayerctlPlayer *playerctl_player_new(const gchar *player_name, GError **err) { GError *tmp_error = NULL; PlayerctlPlayer *player; player = g_initable_new(PLAYERCTL_TYPE_PLAYER, NULL, &tmp_error, "player-name", player_name, NULL); if (tmp_error != NULL) { g_propagate_error(err, tmp_error); return NULL; } return player; } /** * playerctl_player_new_for_source: * @player_name:(allow-none): The name to use to find the bus name of the player * @source: The source where the player name is. * @err: The location of a GError or NULL * * Allocates a new #PlayerctlPlayer and tries to connect to an instance of the * player with the given name from the given source. * * Returns:(transfer full): A new #PlayerctlPlayer connected to an instance of * the player or NULL if an error occurred */ PlayerctlPlayer *playerctl_player_new_for_source(const gchar *player_name, PlayerctlSource source, GError **err) { GError *tmp_error = NULL; PlayerctlPlayer *player; player = g_initable_new(PLAYERCTL_TYPE_PLAYER, NULL, &tmp_error, "player-name", player_name, "source", source, NULL); if (tmp_error != NULL) { g_propagate_error(err, tmp_error); return NULL; } return player; } /** * playerctl_player_new_from_name: * @player_name: The name type to use to find the player * @err:(allow-none): The location of a GError or NULL * * Allocates a new #PlayerctlPlayer and tries to connect to the player * identified by the #PlayerctlPlayerName. * * Returns:(transfer full): A new #PlayerctlPlayer connected to the player or * NULL if an error occurred */ PlayerctlPlayer *playerctl_player_new_from_name(PlayerctlPlayerName *player_name, GError **err) { GError *tmp_error = NULL; PlayerctlPlayer *player; player = g_initable_new(PLAYERCTL_TYPE_PLAYER, NULL, &tmp_error, "player-instance", player_name->instance, "source", player_name->source, NULL); if (tmp_error != NULL) { g_propagate_error(err, tmp_error); return NULL; } return player; } /** * playerctl_player_on: * @self: a #PlayerctlPlayer * @event: the event to subscribe to * @callback: the callback to run on the event * @err:(allow-none): the location of a GError or NULL * * A convenience function for bindings to subscribe to an event with a callback * * Deprecated:2.0.0: Use g_object_connect() to listen to events. */ void playerctl_player_on(PlayerctlPlayer *self, const gchar *event, GClosure *callback, GError **err) { g_return_if_fail(self != NULL); g_return_if_fail(event != NULL); g_return_if_fail(callback != NULL); g_return_if_fail(err == NULL || *err == NULL); if (self->priv->init_error != NULL) { g_propagate_error(err, g_error_copy(self->priv->init_error)); return; } g_closure_ref(callback); g_closure_sink(callback); g_signal_connect_closure(self, event, callback, TRUE); return; } #define PLAYER_COMMAND_FUNC(COMMAND) \ GError *tmp_error = NULL; \ \ g_return_if_fail(self != NULL); \ g_return_if_fail(err == NULL || *err == NULL); \ \ if (self->priv->init_error != NULL) { \ g_propagate_error(err, g_error_copy(self->priv->init_error)); \ return; \ } \ \ org_mpris_media_player2_player_call_##COMMAND##_sync(self->priv->proxy, \ NULL, &tmp_error); \ \ if (tmp_error != NULL) { \ g_propagate_error(err, tmp_error); \ } /** * playerctl_player_play_pause: * @self: a #PlayerctlPlayer * @err:(allow-none): the location of a GError or NULL * * Command the player to play if it is paused or pause if it is playing */ void playerctl_player_play_pause(PlayerctlPlayer *self, GError **err) { PLAYER_COMMAND_FUNC(play_pause); } /** * playerctl_player_open: * @self: a #PlayerctlPlayer * @uri: the URI to open, either a file name or an external URL * @err:(allow-none): the location of a GError or NULL * * Command the player to open given URI */ void playerctl_player_open(PlayerctlPlayer *self, gchar *uri, GError **err) { GError *tmp_error = NULL; g_return_if_fail(self != NULL); g_return_if_fail(err == NULL || *err == NULL); if (self->priv->init_error != NULL) { g_propagate_error(err, g_error_copy(self->priv->init_error)); return; } org_mpris_media_player2_player_call_open_uri_sync(self->priv->proxy, uri, NULL, &tmp_error); if (tmp_error != NULL) { g_propagate_error(err, tmp_error); return; } return; } /** * playerctl_player_play: * @self: a #PlayerctlPlayer * @err:(allow-none): the location of a GError or NULL * * Command the player to play */ void playerctl_player_play(PlayerctlPlayer *self, GError **err) { PLAYER_COMMAND_FUNC(play); } /** * playerctl_player_pause: * @self: a #PlayerctlPlayer * @err:(allow-none): the location of a GError or NULL * * Command the player to pause */ void playerctl_player_pause(PlayerctlPlayer *self, GError **err) { PLAYER_COMMAND_FUNC(pause); } /** * playerctl_player_stop: * @self: a #PlayerctlPlayer * @err:(allow-none): the location of a GError or NULL * * Command the player to stop */ void playerctl_player_stop(PlayerctlPlayer *self, GError **err) { PLAYER_COMMAND_FUNC(stop); } /** * playerctl_player_seek: * @self: a #PlayerctlPlayer * @offset: the offset to seek forward to in microseconds * @err:(allow-none): the location of a GError or NULL * * Command the player to seek forward by offset given in microseconds. */ void playerctl_player_seek(PlayerctlPlayer *self, gint64 offset, GError **err) { GError *tmp_error = NULL; g_return_if_fail(self != NULL); g_return_if_fail(err == NULL || *err == NULL); if (self->priv->init_error != NULL) { g_propagate_error(err, g_error_copy(self->priv->init_error)); return; } org_mpris_media_player2_player_call_seek_sync(self->priv->proxy, offset, NULL, &tmp_error); if (tmp_error != NULL) { g_propagate_error(err, tmp_error); return; } return; } /** * playerctl_player_next: * @self: a #PlayerctlPlayer * @err:(allow-none): the location of a GError or NULL * * Command the player to go to the next track */ void playerctl_player_next(PlayerctlPlayer *self, GError **err) { PLAYER_COMMAND_FUNC(next); } /** * playerctl_player_previous: * @self: a #PlayerctlPlayer * @err:(allow-none): the location of a GError or NULL * * Command the player to go to the previous track */ void playerctl_player_previous(PlayerctlPlayer *self, GError **err) { PLAYER_COMMAND_FUNC(previous); } static gchar *print_metadata_table(GVariant *metadata, gchar *player_name) { GVariantIter iter; GVariant *child; GString *table = g_string_new(""); const gchar *fmt = "%-5s %-25s %s\n"; if (g_strcmp0(g_variant_get_type_string(metadata), "a{sv}") != 0) { return NULL; } g_variant_iter_init(&iter, metadata); while ((child = g_variant_iter_next_value(&iter))) { GVariant *key_variant = g_variant_get_child_value(child, 0); const gchar *key = g_variant_get_string(key_variant, 0); GVariant *value_variant = g_variant_lookup_value(metadata, key, NULL); if (g_variant_is_container(value_variant)) { // only go depth 1 int len = g_variant_n_children(value_variant); for (int i = 0; i < len; ++i) { GVariant *child_value = g_variant_get_child_value(value_variant, i); gchar *child_value_str = pctl_print_gvariant(child_value); g_string_append_printf(table, fmt, player_name, key, child_value_str); g_free(child_value_str); g_variant_unref(child_value); } } else { gchar *value = pctl_print_gvariant(value_variant); g_string_append_printf(table, fmt, player_name, key, value); g_free(value); } g_variant_unref(child); g_variant_unref(key_variant); g_variant_unref(value_variant); } if (table->len == 0) { g_string_free(table, TRUE); return NULL; } // cut off the last newline table = g_string_truncate(table, table->len - 1); return g_string_free(table, FALSE); } /** * playerctl_player_print_metadata_prop: * @self: a #PlayerctlPlayer * @property:(allow-none): the property from the metadata to print * @err:(allow-none): the location of a GError or NULL * * Gets the given property from the metadata of the current track. If property * is null, prints all the metadata properties. Returns NULL if no track is * playing. * * Returns:(transfer full): The artist from the metadata of the current track */ gchar *playerctl_player_print_metadata_prop(PlayerctlPlayer *self, const gchar *property, GError **err) { GError *tmp_error = NULL; g_return_val_if_fail(self != NULL, NULL); g_return_val_if_fail(err == NULL || *err == NULL, NULL); if (self->priv->init_error != NULL) { g_propagate_error(err, g_error_copy(self->priv->init_error)); return NULL; } GVariant *metadata = playerctl_player_get_metadata(self, &tmp_error); if (tmp_error != NULL) { g_propagate_error(err, tmp_error); return NULL; } if (!metadata) { return NULL; } if (!property) { gchar *res = print_metadata_table(metadata, self->priv->player_name); g_variant_unref(metadata); return res; } GVariant *prop_variant = g_variant_lookup_value(metadata, property, NULL); g_variant_unref(metadata); if (!prop_variant) { return NULL; } gchar *prop = pctl_print_gvariant(prop_variant); g_variant_unref(prop_variant); return prop; } /** * playerctl_player_get_artist: * @self: a #PlayerctlPlayer * @err:(allow-none): the location of a GError or NULL * * Gets the artist from the metadata of the current track, or NULL if no * track is playing. * * Returns:(transfer full): The artist from the metadata of the current track */ gchar *playerctl_player_get_artist(PlayerctlPlayer *self, GError **err) { g_return_val_if_fail(self != NULL, NULL); g_return_val_if_fail(err == NULL || *err == NULL, NULL); if (self->priv->init_error != NULL) { g_propagate_error(err, g_error_copy(self->priv->init_error)); return NULL; } return playerctl_player_print_metadata_prop(self, "xesam:artist", NULL); } /** * playerctl_player_get_title: * @self: a #PlayerctlPlayer * @err:(allow-none): the location of a GError or NULL * * Gets the title from the metadata of the current track, or NULL if * no track is playing. * * Returns:(transfer full): The title from the metadata of the current track */ gchar *playerctl_player_get_title(PlayerctlPlayer *self, GError **err) { g_return_val_if_fail(self != NULL, NULL); g_return_val_if_fail(err == NULL || *err == NULL, NULL); if (self->priv->init_error != NULL) { g_propagate_error(err, g_error_copy(self->priv->init_error)); return NULL; } return playerctl_player_print_metadata_prop(self, "xesam:title", NULL); } /** * playerctl_player_get_album: * @self: a #PlayerctlPlayer * @err:(allow-none): the location of a GError or NULL * * Gets the album from the metadata of the current track, or NULL if * no track is playing. * * Returns:(transfer full): The album from the metadata of the current track */ gchar *playerctl_player_get_album(PlayerctlPlayer *self, GError **err) { g_return_val_if_fail(self != NULL, NULL); g_return_val_if_fail(err == NULL || *err == NULL, NULL); if (self->priv->init_error != NULL) { g_propagate_error(err, g_error_copy(self->priv->init_error)); return NULL; } return playerctl_player_print_metadata_prop(self, "xesam:album", NULL); } /** * playerctl_player_set_volume * @self: a #PlayerctlPlayer * @volume: the volume level from 0.0 to 1.0 * @err:(allow-none): the location of a GError or NULL * * Sets the volume level for the player from 0.0 for no volume to 1.0 for * maximum volume. Passing negative numbers should set the volume to 0.0. */ void playerctl_player_set_volume(PlayerctlPlayer *self, gdouble volume, GError **err) { // TODO better error handling //GError *tmp_error = NULL; g_return_if_fail(self != NULL); g_return_if_fail(err == NULL || *err == NULL); if (self->priv->init_error != NULL) { g_propagate_error(err, g_error_copy(self->priv->init_error)); return; } org_mpris_media_player2_player_set_volume(self->priv->proxy, volume); } /** * playerctl_player_get_position * @self: a #PlayerctlPlayer * @err:(allow-none): the location of a GError or NULL * * Gets the position of the current track in microseconds ignoring the property * cache. */ gint64 playerctl_player_get_position(PlayerctlPlayer *self, GError **err) { GError *tmp_error = NULL; g_return_val_if_fail(self != NULL, 0); g_return_val_if_fail(err == NULL || *err == NULL, 0); if (self->priv->init_error != NULL) { g_propagate_error(err, g_error_copy(self->priv->init_error)); return 0; } GVariant *call_reply = g_dbus_proxy_call_sync( G_DBUS_PROXY(self->priv->proxy), "org.freedesktop.DBus.Properties.Get", g_variant_new("(ss)", "org.mpris.MediaPlayer2.Player", "Position"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &tmp_error); if (tmp_error) { g_propagate_error(err, tmp_error); return 0; } GVariant *call_reply_properties = g_variant_get_child_value(call_reply, 0); GVariant *call_reply_unboxed = g_variant_get_variant(call_reply_properties); gint64 position = g_variant_get_int64(call_reply_unboxed); g_variant_unref(call_reply); g_variant_unref(call_reply_properties); g_variant_unref(call_reply_unboxed); return position; } /** * playerctl_player_set_position * @self: a #PlayerctlPlayer * @position: The absolute position in the track to set as the position * @err:(allow-none): the location of a GError or NULL * * Sets the absolute position of the current track to the given position in microseconds. */ void playerctl_player_set_position(PlayerctlPlayer *self, gint64 position, GError **err) { GError *tmp_error = NULL; g_return_if_fail(self != NULL); g_return_if_fail(err == NULL || *err == NULL); if (self->priv->init_error != NULL) { g_propagate_error(err, g_error_copy(self->priv->init_error)); return; } // calling the function requires the track id GVariant *metadata = playerctl_player_get_metadata(self, &tmp_error); if (tmp_error != NULL) { g_propagate_error(err, tmp_error); return; } gchar *track_id = metadata_get_track_id(metadata); g_variant_unref(metadata); if (track_id == NULL) { tmp_error = g_error_new(playerctl_player_error_quark(), 2, "Could not get track id to set position"); g_propagate_error(err, tmp_error); return; } org_mpris_media_player2_player_call_set_position_sync( self->priv->proxy, track_id, position, NULL, &tmp_error); if (tmp_error != NULL) { g_propagate_error(err, tmp_error); } } /** * playerctl_player_set_loop_status: * @self: a #PlayerctlPlayer * @status: the requested #PlayerctlLoopStatus to set the player to * @err:(allow-none): the location of a GError or NULL * * Set the loop status of the player. Can be set to either None, Track, or Playlist. */ void playerctl_player_set_loop_status(PlayerctlPlayer *self, PlayerctlLoopStatus status, GError **err) { g_return_if_fail(self != NULL); g_return_if_fail(err == NULL || *err == NULL); if (self->priv->init_error != NULL) { g_propagate_error(err, g_error_copy(self->priv->init_error)); return; } const gchar *status_str = pctl_loop_status_to_string(status); g_return_if_fail(status_str != NULL); // TODO better error handling org_mpris_media_player2_player_set_loop_status(self->priv->proxy, status_str); } /** * playerctl_player_set_shuffle: * @self: a #PlayerctlPlayer * @shuffle: whether to enable shuffle * @err:(allow-none): the location of a GError or NULL * * Request to set the shuffle state of the player, either on or off. */ void playerctl_player_set_shuffle(PlayerctlPlayer *self, gboolean shuffle, GError **err) { g_return_if_fail(self != NULL); g_return_if_fail(err == NULL || *err == NULL); if (self->priv->init_error != NULL) { g_propagate_error(err, g_error_copy(self->priv->init_error)); return; } // TODO better error handling org_mpris_media_player2_player_set_shuffle(self->priv->proxy, shuffle); } playerctl-2.0.2/playerctl/playerctl-player.h000066400000000000000000000164501344655314500211730ustar00rootroot00000000000000/* * This file is part of playerctl. * * playerctl is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * playerctl 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 Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License * along with playerctl If not, see . * * Copyright © 2014, Tony Crisci and contributors */ #ifndef __PLAYERCTL_PLAYER_H__ #define __PLAYERCTL_PLAYER_H__ #if !defined(__PLAYERCTL_INSIDE__) && !defined(PLAYERCTL_COMPILATION) #error "Only can be included directly." #endif #include #include #include /** * SECTION: playerctl-player * @short_description: A class to control a media player. * * The #PlayerctlPlayer represents a proxy connection to a media player through * an IPC interface that is capable of performing commands and executing * queries on the player for properties and metadata. * * If you know the name of your player and that it is running, you can use * playerctl_player_new() giving the player name to connect to it. The player * names given are the same as you can get with the binary `playerctl * --list-all` command. Using this function will get you the first instance of * the player it can find, or the exact instance if you pass the instance as * the player name. * * If you would like to connect to a player dynamically, you can list players * to be controlled with playerctl_list_players() or use the * #PlayerctlPlayerManager class and read the list of player name containers in * the #PlayerctlPlayerManager:player-names property or listen to the * #PlayerctlPlayerManager::name-appeared event. If you have a * #PlayerctlPlayerName, you can use the playerctl_player_new_from_name() * function to create a #PlayerctlPlayer from this name. * * Once you have a player, you can give it commands to play, pause, stop, open * a file, etc with the provided functions listed below. You can also query for * properties such as the playback status, position, and shuffle status. Each * of these has an event that will be emitted when these properties change * during a main loop. * * For examples on how to use the #PlayerctlPlayer, see the `examples` * directory in the git repository. */ #define PLAYERCTL_TYPE_PLAYER (playerctl_player_get_type()) #define PLAYERCTL_PLAYER(obj) \ (G_TYPE_CHECK_INSTANCE_CAST((obj), PLAYERCTL_TYPE_PLAYER, PlayerctlPlayer)) #define PLAYERCTL_IS_PLAYER(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE((obj), PLAYERCTL_TYPE_PLAYER)) #define PLAYERCTL_PLAYER_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST((klass), PLAYERCTL_TYPE_PLAYER, \ PlayerctlPlayerClass)) #define PLAYERCTL_IS_PLAYER_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE((klass), PLAYERCTL_TYPE_PLAYER)) #define PLAYERCTL_PLAYER_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS((obj), PLAYERCTL_TYPE_PLAYER, \ PlayerctlPlayerClass)) typedef struct _PlayerctlPlayer PlayerctlPlayer; typedef struct _PlayerctlPlayerClass PlayerctlPlayerClass; typedef struct _PlayerctlPlayerPrivate PlayerctlPlayerPrivate; struct _PlayerctlPlayer { /* Parent instance structure */ GObject parent_instance; /* Private members */ PlayerctlPlayerPrivate *priv; }; struct _PlayerctlPlayerClass { /* Parent class structure */ GObjectClass parent_class; }; GType playerctl_player_get_type(void); PlayerctlPlayer *playerctl_player_new(const gchar *player_name, GError **err); PlayerctlPlayer *playerctl_player_new_for_source(const gchar *player_name, PlayerctlSource source, GError **err); PlayerctlPlayer *playerctl_player_new_from_name(PlayerctlPlayerName *player_name, GError **err); /** * PlayerctlPlaybackStatus: * @PLAYERCTL_PLAYBACK_STATUS_PLAYING: A track is currently playing. * @PLAYERCTL_PLAYBACK_STATUS_PAUSED: A track is currently paused. * @PLAYERCTL_PLAYBACK_STATUS_STOPPED: There is no track currently playing. * * Playback status enumeration for a #PlayerctlPlayer * */ typedef enum { PLAYERCTL_PLAYBACK_STATUS_PLAYING, /*< nick=Playing >*/ PLAYERCTL_PLAYBACK_STATUS_PAUSED, /*< nick=Paused >*/ PLAYERCTL_PLAYBACK_STATUS_STOPPED, /*< nick=Stopped >*/ } PlayerctlPlaybackStatus; /** * PlayerctlLoopStatus: * @PLAYERCTL_LOOP_STATUS_NONE: The playback will stop when there are no more tracks to play. * @PLAYERCTL_LOOP_STATUS_TRACK: The current track will start again from the beginning once it has finished playing. * @PLAYERCTL_LOOP_STATUS_PLAYLIST: The playback loops through a list of tracks. * * Loop status enumeration for a #PlayerctlPlayer * */ typedef enum { PLAYERCTL_LOOP_STATUS_NONE, /*< nick=None >*/ PLAYERCTL_LOOP_STATUS_TRACK, /*< nick=Track >*/ PLAYERCTL_LOOP_STATUS_PLAYLIST, /* nick=Playlist >*/ } PlayerctlLoopStatus; /* * Static methods */ GList *playerctl_list_players(GError **err); /* * Method definitions. */ void playerctl_player_on(PlayerctlPlayer *self, const gchar *event, GClosure *callback, GError **err); void playerctl_player_open(PlayerctlPlayer *self, gchar *uri, GError **err); void playerctl_player_play_pause(PlayerctlPlayer *self, GError **err); void playerctl_player_play(PlayerctlPlayer *self, GError **err); void playerctl_player_stop(PlayerctlPlayer *self, GError **err); void playerctl_player_seek(PlayerctlPlayer *self, gint64 offset, GError **err); void playerctl_player_pause(PlayerctlPlayer *self, GError **err); void playerctl_player_next(PlayerctlPlayer *self, GError **err); void playerctl_player_previous(PlayerctlPlayer *self, GError **err); gchar *playerctl_player_print_metadata_prop(PlayerctlPlayer *self, const gchar *property, GError **err); gchar *playerctl_player_get_artist(PlayerctlPlayer *self, GError **err); gchar *playerctl_player_get_title(PlayerctlPlayer *self, GError **err); gchar *playerctl_player_get_album(PlayerctlPlayer *self, GError **err); void playerctl_player_set_volume(PlayerctlPlayer *self, gdouble volume, GError **err); gint64 playerctl_player_get_position(PlayerctlPlayer *self, GError **err); void playerctl_player_set_position(PlayerctlPlayer *self, gint64 position, GError **err); void playerctl_player_set_loop_status(PlayerctlPlayer *self, PlayerctlLoopStatus status, GError **err); void playerctl_player_set_shuffle(PlayerctlPlayer *self, gboolean shuffle, GError **err); #endif /* __PLAYERCTL_PLAYER_H__ */ playerctl-2.0.2/playerctl/playerctl-version.h.in000066400000000000000000000047531344655314500217740ustar00rootroot00000000000000/* * This file is part of playerctl. * * playerctl is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * playerctl 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 Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License * along with playerctl If not, see . * * Copyright © 2014, Tony Crisci */ #ifndef __PLAYERCTL_VERSION_H__ #define __PLAYERCTL_VERSION_H__ #if !defined(__PLAYERCTL_INSIDE__) && !defined(PLAYERCTL_COMPILATION) #error "Only can be included directly." #endif /** * SECTION:playerctl-version * @short_description: Playerctl version checking * * Playerctl provides macros to check the version of the library at * compile-time */ /** * PLAYERCTL_MAJOR_VERSION: * * Playerctl major version component */ #define PLAYERCTL_MAJOR_VERSION (@PLAYERCTL_MAJOR_VERSION@) /** * PLAYERCTL_MINOR_VERSION: * * Playerctl minor version component */ #define PLAYERCTL_MINOR_VERSION (@PLAYERCTL_MINOR_VERSION@) /** * PLAYERCTL_MICRO_VERSION: * * Playerctl micro version component */ #define PLAYERCTL_MICRO_VERSION (@PLAYERCTL_MICRO_VERSION@) /** * PLAYERCTL_VERSION: * * Playerctl version */ #define PLAYERCTL_VERSION (@PLAYERCTL_VERSION@) /** * PLAYERCTL_VERSION_S: * * Playerctl version, encoded as a string */ #define PLAYERCTL_VERSION_S "@PLAYERCTL_VERSION@" #define PLAYERCTL_ENCODE_VERSION(major,minor,micro) \ ((major) << 24 | (minor) << 16 | (micro) << 8) /** * PLAYERCTL_VERSION_HEX: * * Playerctl version, encoded as an hexadecimal number, useful for integer * comparisons. */ #define PLAYERCTL_VERSION_HEX \ (PLAYERCTL_ENCODE_VERSION (PLAYERCTL_MAJOR_VERSION, PLAYERCTL_MINOR_VERSION, PLAYERCTL_MICRO_VERSION)) #define PLAYERCTL_CHECK_VERSION(major, minor, micro) \ (PLAYERCTL_MAJOR_VERSION > (major) || \ (PLAYERCTL_MAJOR_VERSION == (major) && PLAYERCTL_MINOR_VERSION > (minor)) || \ (PLAYERCTL_MAJOR_VERSION == (major) && PLAYERCTL_MINOR_VERSION == (minor) && \ PLAYERCTL_MICRO_VERSION >= (micro))) #endif /* __PLAYERCTL_VERSION_H__ */ playerctl-2.0.2/playerctl/playerctl.h000066400000000000000000000021301344655314500176670ustar00rootroot00000000000000/* * This file is part of playerctl. * * playerctl is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * playerctl 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 Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License * along with playerctl If not, see . * * Copyright © 2014, Tony Crisci and contributors */ #ifndef __PLAYERCTL_H__ #define __PLAYERCTL_H__ #define __PLAYERCTL_INSIDE__ #include #include #include #include #include #undef __PLAYERCTL_INSIDE__ #endif /* __PLAYERCTL_H__ */ playerctl-2.0.2/playerctl/playerctl.pc.in000066400000000000000000000004251344655314500204540ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: Playerctl Description: A C library for MPRIS players Version: @VERSION@ Libs: -L${libdir} -lplayerctl-1.0 Cflags: -I${includedir}/playerctl Requires: gobject-2.0 Requires.private: gio-2.0