├── .gitignore ├── CMakeLists.txt ├── CONTRIBUTORS.md ├── COPYING ├── README.md ├── awesome └── spop.lua ├── build_and_run ├── dspop └── dspop ├── plugins ├── ao.c ├── awesome.c ├── dummy.c ├── mpris2 │ ├── mpris2-generated.c │ ├── mpris2-generated.h │ ├── mpris2-player-generated.c │ ├── mpris2-player-generated.h │ ├── mpris2-tracklist-generated.c │ ├── mpris2-tracklist-generated.h │ ├── org.mpris.MediaPlayer2.Player.xml │ ├── org.mpris.MediaPlayer2.TrackList.xml │ ├── org.mpris.MediaPlayer2.xml │ ├── plugin.c │ ├── spop-mpris2.c │ └── spop-mpris2.h ├── notify.c ├── oss.c ├── savestate.c ├── scrobble.c └── sox.c ├── spopd.conf.sample ├── src ├── appkey.c ├── audio.h ├── commands.c ├── commands.h ├── config.c ├── config.h ├── interface.c ├── interface.h ├── main.c ├── plugin.c ├── plugin.h ├── queue.c ├── queue.h ├── sd-daemon.c ├── sd-daemon.h ├── spop.h ├── spotify.c ├── spotify.h ├── utils.c └── utils.h └── systemd ├── spopd.service └── spopd.socket /.gitignore: -------------------------------------------------------------------------------- 1 | # Build directory 2 | build/ 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 2 | # 3 | # This file is part of spop. 4 | # 5 | # spop is free software: you can redistribute it and/or modify it under the 6 | # terms of the GNU General Public License as published by the Free Software 7 | # Foundation, either version 3 of the License, or (at your option) any later 8 | # version. 9 | # 10 | # spop is distributed in the hope that it will be useful, but WITHOUT ANY 11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | # A PARTICULAR PURPOSE. See the GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License along with 15 | # spop. If not, see . 16 | # 17 | # Additional permission under GNU GPL version 3 section 7 18 | # 19 | # If you modify this Program, or any covered work, by linking or combining it 20 | # with libspotify (or a modified version of that library), containing parts 21 | # covered by the terms of the Libspotify Terms of Use, the licensors of this 22 | # Program grant you additional permission to convey the resulting work. 23 | 24 | cmake_minimum_required(VERSION 2.8) 25 | include(CheckIncludeFiles) 26 | 27 | project(spop C) 28 | 29 | find_package(PkgConfig) 30 | set(targets) 31 | 32 | # Check for libspotify 33 | pkg_check_modules(SPOTIFY REQUIRED libspotify>=12.1.45) 34 | string(REPLACE ";" " " SPOTIFY_CFLAGS "${SPOTIFY_CFLAGS}") 35 | 36 | # Check for required libraries 37 | pkg_check_modules(GLIB2 REQUIRED glib-2.0>=2.26) 38 | string(REPLACE ";" " " GLIB2_CFLAGS "${GLIB2_CFLAGS}") 39 | pkg_check_modules(GMODULE2 REQUIRED gmodule-2.0) 40 | string(REPLACE ";" " " GMODULE2_CFLAGS "${GMODULE2_CFLAGS}") 41 | pkg_check_modules(GTHREAD2 REQUIRED gthread-2.0) 42 | string(REPLACE ";" " " GTHREAD2_CFLAGS "${GTHREAD2_CFLAGS}") 43 | pkg_check_modules(JSON_GLIB REQUIRED json-glib-1.0>=0.12) 44 | string(REPLACE ";" " " JSON_GLIB_CFLAGS "${JSON_GLIB_CFLAGS}") 45 | 46 | # Check for optional headers 47 | check_include_files("sys/soundcard.h" HAVE_SYS_SOUNDCARD_H) 48 | 49 | # Check for optional libraries 50 | pkg_check_modules(AO ao) 51 | string(REPLACE ";" " " AO_CFLAGS "${AO_CFLAGS}") 52 | pkg_check_modules(DBUS dbus-glib-1) 53 | string(REPLACE ";" " " DBUS_CFLAGS "${DBUS_CFLAGS}") 54 | pkg_check_modules(GIO_UNIX gio-unix-2.0) 55 | string(REPLACE ";" " " GIO_UNIX_CFLAGS "${GIO_UNIX_CFLAGS}") 56 | pkg_check_modules(NOTIFY libnotify) 57 | string(REPLACE ";" " " NOTIFY_CFLAGS "${NOTIFY_CFLAGS}") 58 | pkg_check_modules(SOUP libsoup-2.4) 59 | string(REPLACE ";" " " SOUP_CFLAGS "${SOUP_CFLAGS}") 60 | pkg_check_modules(SOX sox) 61 | string(REPLACE ";" " " SOX_CFLAGS "${SOX_CFLAGS}") 62 | 63 | # spop daemon 64 | set(SPOPD 65 | src/appkey.c 66 | src/commands.c 67 | src/config.c 68 | src/interface.c 69 | src/main.c 70 | src/plugin.c 71 | src/queue.c 72 | src/sd-daemon.c 73 | src/spotify.c 74 | src/utils.c 75 | ) 76 | add_executable(spopd ${SPOPD}) 77 | 78 | set_target_properties(spopd PROPERTIES 79 | COMPILE_FLAGS "${SPOTIFY_CFLAGS} ${GLIB2_CFLAGS} ${GMODULE2_CFLAGS} ${GTHREAD2_CFLAGS} ${JSON_GLIB_CFLAGS}" 80 | ) 81 | target_link_libraries(spopd dl ${SPOTIFY_LIBRARIES} ${GLIB2_LIBRARIES} 82 | ${GMODULE2_LIBRARIES} ${GTHREAD2_LIBRARIES} ${JSON_GLIB_LIBRARIES}) 83 | set(targets ${targets} spopd) 84 | 85 | # Apple specific stuff 86 | if(APPLE) 87 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -undefined dynamic_lookup") 88 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -undefined dynamic_lookup") 89 | else(APPLE) 90 | target_link_libraries(spopd rt) 91 | endif(APPLE) 92 | 93 | # Audio plugin: dummy 94 | set(AUDIO_DUMMY 95 | plugins/dummy.c 96 | ) 97 | add_library(spop_audio_dummy MODULE ${AUDIO_DUMMY}) 98 | set_target_properties(spop_audio_dummy PROPERTIES 99 | COMPILE_FLAGS "${GLIB2_CFLAGS}" 100 | ) 101 | target_link_libraries(spop_audio_dummy ${GLIB2_LIBRARIES}) 102 | set(targets ${targets} spop_audio_dummy) 103 | 104 | # Audio plugin: OSS 105 | if(HAVE_SYS_SOUNDCARD_H) 106 | set(AUDIO_OSS 107 | plugins/oss.c 108 | ) 109 | add_library(spop_audio_oss MODULE ${AUDIO_OSS}) 110 | set_target_properties(spop_audio_oss PROPERTIES 111 | COMPILE_FLAGS "${GLIB2_CFLAGS}" 112 | ) 113 | target_link_libraries(spop_audio_oss ${GLIB2_LIBRARIES}) 114 | set(targets ${targets} spop_audio_oss) 115 | endif(HAVE_SYS_SOUNDCARD_H) 116 | 117 | # Audio plugin: libao 118 | if(AO_FOUND) 119 | set(AUDIO_AO 120 | plugins/ao.c 121 | ) 122 | add_library(spop_audio_ao MODULE ${AUDIO_AO}) 123 | set_target_properties(spop_audio_ao PROPERTIES 124 | COMPILE_FLAGS "${AO_CFLAGS} ${GTHREAD2_CFLAGS}" 125 | ) 126 | target_link_libraries(spop_audio_ao ${AO_LIBRARIES} ${GTHREAD2_LIBRARIES}) 127 | set(targets ${targets} spop_audio_ao) 128 | endif(AO_FOUND) 129 | 130 | # Audio plugin: sox 131 | if(SOX_FOUND) 132 | set(AUDIO_SOX 133 | plugins/sox.c 134 | ) 135 | add_library(spop_audio_sox MODULE ${AUDIO_SOX}) 136 | set_target_properties(spop_audio_sox PROPERTIES 137 | COMPILE_FLAGS "${SOX_CFLAGS} ${GTHREAD2_CFLAGS}" 138 | ) 139 | target_link_libraries(spop_audio_sox ${SOX_LIBRARIES} ${GTHREAD2_LIBRARIES}) 140 | set(targets ${targets} spop_audio_sox) 141 | endif(SOX_FOUND) 142 | 143 | # Generic plugin: awesome 144 | if(DBUS_FOUND) 145 | set(PLUGIN_AWESOME 146 | plugins/awesome.c 147 | ) 148 | add_library(spop_plugin_awesome MODULE ${PLUGIN_AWESOME}) 149 | set_target_properties(spop_plugin_awesome PROPERTIES 150 | COMPILE_FLAGS "${DBUS_CFLAGS} ${GLIB2_CFLAGS}" 151 | ) 152 | target_link_libraries(spop_plugin_awesome ${DBUS_LIBRARIES} ${GLIB2_LIBRARIES}) 153 | set(targets ${targets} spop_plugin_awesome) 154 | install(DIRECTORY awesome DESTINATION share/spop) 155 | endif(DBUS_FOUND) 156 | 157 | # Generic plugin: mpris2 158 | if(GIO_UNIX_FOUND) 159 | macro(GDBUS_CODEGEN DIR XML IFACE_PREFIX CCODE NAMESPACE) 160 | add_custom_command( 161 | OUTPUT "${DIR}/${CCODE}.c" 162 | OUTPUT "${DIR}/${CCODE}.h" 163 | COMMAND gdbus-codegen --interface-prefix="${IFACE_PREFIX}" --generate-c-code="${CCODE}" --c-namespace="${NAMESPACE}" "${XML}" 164 | WORKING_DIRECTORY "${DIR}" 165 | MAIN_DEPENDENCY "${DIR}/${XML}" 166 | ) 167 | endmacro(GDBUS_CODEGEN) 168 | gdbus_codegen("${CMAKE_CURRENT_SOURCE_DIR}/plugins/mpris2" "org.mpris.MediaPlayer2.xml" 169 | org.mpris.MediaPlayer2 mpris2-generated Mpris2) 170 | gdbus_codegen("${CMAKE_CURRENT_SOURCE_DIR}/plugins/mpris2" "org.mpris.MediaPlayer2.Player.xml" 171 | org.mpris.MediaPlayer2 mpris2-player-generated Mpris2) 172 | gdbus_codegen("${CMAKE_CURRENT_SOURCE_DIR}/plugins/mpris2" "org.mpris.MediaPlayer2.TrackList.xml" 173 | org.mpris.MediaPlayer2 mpris2-tracklist-generated Mpris2) 174 | 175 | set(PLUGIN_MPRIS2 176 | plugins/mpris2/mpris2-generated.c 177 | plugins/mpris2/mpris2-player-generated.c 178 | plugins/mpris2/mpris2-tracklist-generated.c 179 | plugins/mpris2/plugin.c 180 | plugins/mpris2/spop-mpris2.c 181 | ) 182 | add_library(spop_plugin_mpris2 MODULE ${PLUGIN_MPRIS2}) 183 | set_target_properties(spop_plugin_mpris2 PROPERTIES 184 | COMPILE_FLAGS "${GIO_UNIX_CFLAGS} ${GLIB2_CFLAGS}" 185 | ) 186 | target_link_libraries(spop_plugin_mpris2 ${GIO_UNIX_LIBRARIES} ${GLIB2_LIBRARIES}) 187 | set(targets ${targets} spop_plugin_mpris2) 188 | endif(GIO_UNIX_FOUND) 189 | 190 | # Generic plugin: notify 191 | if(NOTIFY_FOUND) 192 | set(PLUGIN_NOTIFY 193 | plugins/notify.c 194 | ) 195 | add_library(spop_plugin_notify MODULE ${PLUGIN_NOTIFY}) 196 | set_target_properties(spop_plugin_notify PROPERTIES 197 | COMPILE_FLAGS "${GLIB2_CFLAGS} ${NOTIFY_CFLAGS}" 198 | ) 199 | target_link_libraries(spop_plugin_notify ${GLIB2_LIBRARIES} ${NOTIFY_LIBRARIES}) 200 | set(targets ${targets} spop_plugin_notify) 201 | endif(NOTIFY_FOUND) 202 | 203 | # Generic plugin: savestate 204 | set(PLUGIN_SAVESTATE 205 | plugins/savestate.c 206 | ) 207 | add_library(spop_plugin_savestate MODULE ${PLUGIN_SAVESTATE}) 208 | set_target_properties(spop_plugin_savestate PROPERTIES 209 | COMPILE_FLAGS "${GLIB2_CFLAGS} ${JSON_GLIB_CFLAGS}" 210 | ) 211 | target_link_libraries(spop_plugin_savestate ${GLIB2_LIBRARIES} ${JSON_GLIB_LIBRARIES}) 212 | set(targets ${targets} spop_plugin_savestate) 213 | 214 | # Generic plugin: scrobble 215 | if(SOUP_FOUND) 216 | set(PLUGIN_SCROBBLE 217 | plugins/scrobble.c 218 | ) 219 | add_library(spop_plugin_scrobble MODULE ${PLUGIN_SCROBBLE}) 220 | set_target_properties(spop_plugin_scrobble PROPERTIES 221 | COMPILE_FLAGS "${GLIB2_CFLAGS} ${SOUP_CFLAGS}" 222 | ) 223 | target_link_libraries(spop_plugin_scrobble ${GLIB2_LIBRARIES} ${SOUP_LIBRARIES}) 224 | set(targets ${targets} spop_plugin_scrobble) 225 | endif(SOUP_FOUND) 226 | 227 | # dspop client 228 | install(PROGRAMS dspop/dspop DESTINATION bin) 229 | 230 | # systemd service files 231 | install(DIRECTORY systemd/ DESTINATION lib/systemd/user) 232 | 233 | # Common to all source files 234 | set(SRC 235 | ${SPOPD} 236 | ${AUDIO_OSS} 237 | ${AUDIO_AO} 238 | ${AUDIO_SOX} 239 | ${PLUGIN_AWESOME} 240 | ${PLUGIN_NOTIFY} 241 | ${PLUGIN_SAVESTATE} 242 | ${PLUGIN_SCROBBLE} 243 | ) 244 | set_source_files_properties(${SRC} 245 | COMPILE_FLAGS "-O2 -Wall" #-Werror 246 | ) 247 | include_directories(src) 248 | 249 | # System-wide installation 250 | install(TARGETS ${targets} 251 | RUNTIME DESTINATION bin 252 | LIBRARY DESTINATION lib 253 | ) 254 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | The following people have contributed code to spop: 3 | 4 | * Frode Aannevik 5 | * Michael Auchter 6 | * Olof Fredriksson 7 | * Rasmus Steinke 8 | * Theodoor 9 | * Thomas Jost 10 | * Xemle 11 | 12 | Thanks a lot! :) 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to spop! 2 | 3 | spop is a Spotify client that works as a daemon (similar to the famous [MPD][]). 4 | It is designed to be as simple and straightforward as possible: run it, control 5 | it with your keyboard and a few scripts, and just forget about it. 6 | 7 | ## Features 8 | - **Works as a daemon:** no GUI, just start it, it will run in the background and do 9 | what you want it to do. Your music won't stop playing if your X server crashes! 10 | - **Uses libspotify:** stable, reliable. Not free (a Spotify [premium account][] 11 | is required), but quite cheap. 12 | - **Written in plain C:** as lightweight as possible, only 300 kB when compiled 13 | *with debugging symbols*... 14 | - **Few dependencies:** only requires [libspotify][], [Glib][], [JSON-GLib][] 15 | and [libao][] (or [libsox][]; not required for OSS audio output). 16 | - **Powerful audio effects**: when using [libsox][], you can apply various 17 | effects to the audio output: equalisation, normalisation, reverb, "karaoke", 18 | etc. SoX is the [Swiss Army knife of sound processing][sak]! 19 | - **Powerful plugin system:** you can write your own plugin in a few dozens 20 | lines of code. 21 | - **Simple protocol:** open a TCP connection to the daemon, write a simple 22 | plain-text command, get an easily parsable JSON output. 23 | - **Portable:** designed to be platform-agnostic, it should work on any platform 24 | supported both by Glib and libspotify. (But so far it has only been tested on 25 | Linux and Apple OS X) 26 | - **Free software:** the source code is available under the terms of the GNU 27 | GPLv3 license (or, at your option, any later version), with an exception 28 | allowing to distribute code linked against libspotify. Everyone is welcome to 29 | contribute! 30 | 31 | ## Plugins 32 | Right now, several plugins are available: 33 | 34 | - *notify:* use [libnotify][] for desktop notifications 35 | - *savestate:* keep the current state (queue, current track, etc.) when stopping 36 | and restarting spop 37 | - *scrobble:* scrobble your music to [LastFM][] or [LibreFM][] (requires [libsoup][]) 38 | - *awesome:* keep an eye on your player in [Awesome][], an extremely powerful 39 | window manager 40 | - *mpris2:* support the [MPRIS2][] standard to control spopd like any other 41 | media player from your desktop or using the multimedia keys on your keyboard 42 | (work in progress) 43 | 44 | ## How to use 45 | 1. Install [libspotify][] (preferably using your favorite package manager) 46 | 2. Download spop's source code: 47 | 48 | git clone git://github.com/Schnouki/spop.git 49 | 50 | 3. Prepare your configuration file: 51 | 52 | mkdir -p ~/.config/spop 53 | cp spop/spopd.conf.sample ~/.config/spop/spopd.conf 54 | nano ~/.config/spop/spopd.conf 55 | 56 | 3. Compile and run spop: 57 | 58 | cd spop 59 | ./build_and_run -fv 60 | 61 | 4. Connect to the daemon and issue some commands: 62 | 63 | telnet localhost 6602 64 | 65 | 5. If you want something more GUI-like, you can use `dspop`, which uses either 66 | [dmenu][] or [rofi][]: 67 | 68 | ./dspop/dspop 69 | 70 | Or, on Linux, you can enable the `mpris2` plugin in the configuration file 71 | and use the media controls integrated with some desktop environments. 72 | 73 | 6. If you want a *really* nice web interface, have a look at [spop-web][] by 74 | Xemle. 75 | 76 | 77 | If you want to install spop somewhere on your system, do the following steps: 78 | 79 | mkdir build 80 | cd build 81 | cmake -DCMAKE_INSTALL_PREFIX=/where/to/install .. 82 | make 83 | sudo make install 84 | 85 | ### Debian 86 | Add Mopidy APT repository for `libspotify` from `https://github.com/mopidy/libspotify-deb`: 87 | 88 | wget -q -O - http://apt.mopidy.com/mopidy.gpg | sudo apt-key add - 89 | echo -e "deb http://apt.mopidy.com/ stable main contrib non-free\ndeb-src http://apt.mopidy.com/ stable main contrib non-free" | sudo tee /etc/apt/sources.list.d/mopidy.list 90 | sudo apt-get update 91 | 92 | Install required libraries via `apt-get`: 93 | 94 | sudo apt-get install libjson-glib-dev libao-dev libdbus-glib-1-dev libnotify-dev libsoup2.4-dev libsox-dev libspotify-dev 95 | 96 | ### Mac OSX 97 | Install libspotify with [Homebrew][]: 98 | 99 | brew install https://raw.github.com/mopidy/homebrew-mopidy/master/libspotify.rb 100 | 101 | 102 | ## Commands 103 | At the moment, spop can not modify your playlists, so you will have to use the 104 | official Spotify client to manage them. 105 | 106 | Except for that, the following commands are available: 107 | 108 | - `help`: list all available commands 109 | 110 | --- 111 | 112 | - `ls`: list all your playlists 113 | - `ls pl`: list the contents of playlist number `pl` 114 | 115 | --- 116 | 117 | - `qls`: list the contents of the queue 118 | - `qclear`: clear the contents of the queue 119 | - `qrm tr`: remove track number `tr` from the queue 120 | - `qrm tr1 tr2`: remove tracks `tr1` to `tr2` from the queue 121 | 122 | --- 123 | 124 | - `add pl`: add playlist number `pl` to the queue 125 | - `add pl tr`: add track number `tr` from playlist number `pl` to the queue 126 | - `play pl`: replace the contents of the queue with playlist `pl` and start 127 | playing 128 | - `play pl tr`: replace the contents of the queue with track `tr` from playlist 129 | `pl` and start playing 130 | 131 | --- 132 | 133 | - `uinfo uri`: display information about the given Spotify URI 134 | - `uadd uri`: add the given Spotify URI to the queue (playlist, track or album 135 | only) 136 | - `uplay uri`: replace the contents of the queue with the given Spotify URI 137 | (playlist, track or album only) and start playing 138 | - `uimage uri`: get the cover image for given uri (base64-encoded JPEG image). 139 | Uri must be an track or album uri. 140 | - `uimage uri size`: get the cover image for given uri (base64-encoded JPEG 141 | image). Uri must be an track or album uri. Use 0 for normal size (300px), 1 142 | for small size (64px) and 2 for large size (640px). 143 | 144 | --- 145 | 146 | - `search query`: perform a search with the given query 147 | 148 | --- 149 | 150 | - `play`: start playing from the queue 151 | - `toggle` or `pause`: toggle pause mode 152 | - `stop`: stop playback 153 | - `seek pos`: go to position `pos` (in milliseconds) in the current track 154 | - `next`: switch to the next track in the queue 155 | - `prev`: switch to the previous track in the queue 156 | - `goto tr`: switch to track number `tr` in the queue 157 | - `repeat`: toggle repeat mode 158 | - `shuffle`: toggle shuffle mode 159 | 160 | --- 161 | 162 | - `status`: display informations about the queue, the current track, etc. 163 | - `idle`: wait for something to change (pause, switch to other track, new track 164 | in queue...), then display `status`. Mostly useful in notification scripts. 165 | - `notify`: unlock all the currently idle sessions, just like if something had 166 | changed. 167 | - `image`: get the cover image for the current track (base64-encoded JPEG image). 168 | - `offline-status`: display informations about the current status of the offline 169 | cache (number of offline playlists, sync status...). 170 | - `offline-toggle pl`: toggle offline mode for playlist number `pl`. 171 | 172 | --- 173 | 174 | - `star`: toggle the "starred" status of the current track 175 | - `ustar uri val`: set the "starred" status of all the tracks in the given URI 176 | (playlist, track or album) to `val` (0 or 1) 177 | 178 | --- 179 | 180 | - `bye`: close the connection to the spop daemon 181 | - `quit`: exit spop 182 | 183 | ## Furthermore... 184 | 185 | This doc is probably lacking a gazillion useful informations, so feel free to 186 | ask me if you have any question regarding spop! 187 | 188 | - On GitHub: 189 | - By e-mail: <`my_nickname@my_nickname.net`> (by the way, my nickname 190 | is "Schnouki") 191 | - On Twitter: 192 | 193 | [Awesome]: http://awesome.naquadah.org/ 194 | [Glib]: http://library.gnome.org/devel/glib/ 195 | [Homebrew]: http://brew.sh/ 196 | [JSON-GLib]: http://live.gnome.org/JsonGlib 197 | [libspotify]: http://developer.spotify.com/en/libspotify/overview/ 198 | [libao]: http://www.xiph.org/ao/ 199 | [libsox]: http://sox.sourceforge.net/ 200 | [sak]: http://sox.sourceforge.net/Docs/Features 201 | [libnotify]: http://library.gnome.org/devel/libnotify/ 202 | [LastFM]: http://www.last.fm/ 203 | [LibreFM]: http://libre.fm/ 204 | [libsoup]: http://live.gnome.org/LibSoup 205 | [MPD]: http://www.musicpd.org/ 206 | [MPRIS2]: http://specifications.freedesktop.org/mpris-spec/latest/ 207 | [premium account]: http://www.spotify.com/uk/get-spotify/overview/ 208 | [dmenu]: http://tools.suckless.org/dmenu/ 209 | [rofi]: https://davedavenport.github.io/rofi/ 210 | [spop-web]: https://github.com/xemle/spop-web 211 | -------------------------------------------------------------------------------- /awesome/spop.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) 2010, 2011, 2012, 2013, 2015 The spop contributors 2 | -- 3 | -- This file is part of spop. 4 | -- 5 | -- spop is free software: you can redistribute it and/or modify it under the 6 | -- terms of the GNU General Public License as published by the Free Software 7 | -- Foundation, either version 3 of the License, or (at your option) any later 8 | -- version. 9 | -- 10 | -- spop is distributed in the hope that it will be useful, but WITHOUT ANY 11 | -- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 | -- A PARTICULAR PURPOSE. See the GNU General Public License for more details. 13 | -- 14 | -- You should have received a copy of the GNU General Public License along with 15 | -- spop. If not, see . 16 | -- 17 | -- Additional permission under GNU GPL version 3 section 7 18 | -- 19 | -- If you modify this Program, or any covered work, by linking or combining it 20 | -- with libspotify (or a modified version of that library), containing parts 21 | -- covered by the terms of the Libspotify Terms of Use, the licensors of this 22 | -- Program grant you additional permission to convey the resulting work. 23 | 24 | local socket = require("socket") 25 | 26 | module("spop") 27 | 28 | local server, port 29 | local cycle_status = 0 30 | 31 | function init(serv, por) 32 | server = serv 33 | port = por 34 | end 35 | 36 | function command(s) 37 | local conn = socket.connect(server, port) 38 | if conn then 39 | conn:send(s .. "\nbye\n") 40 | conn:close() 41 | end 42 | end 43 | 44 | function next() command("next") end 45 | function prev() command("prev") end 46 | function toggle() command("toggle") end 47 | function stop() command("stop") end 48 | 49 | function cycle() 50 | if (cycle_status % 2) == 0 then 51 | command("repeat") 52 | else 53 | command("shuffle") 54 | end 55 | cycle_status = cycle_status + 1 56 | end 57 | -------------------------------------------------------------------------------- /build_and_run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -d build -a -e build/Makefile ]; then 4 | cd build && make rebuild_cache || exit 1 5 | else 6 | if [ ! -d build ]; then 7 | mkdir build || exit 1 8 | fi 9 | cd build && cmake -Wdev -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr .. || exit 1 10 | fi 11 | 12 | make all || exit 1 13 | cd .. 14 | 15 | #LD_LIBRARY_PATH=build exec gdb -ex "handle SIGPIPE nostop noprint pass" -ex "run" --args ./build/spopd $@ 16 | LD_LIBRARY_PATH=build exec ./build/spopd $@ 17 | -------------------------------------------------------------------------------- /dspop/dspop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 4 | # 5 | # This file is part of spop. 6 | # 7 | # spop is free software: you can redistribute it and/or modify it under the 8 | # terms of the GNU General Public License as published by the Free Software 9 | # Foundation, either version 3 of the License, or (at your option) any later 10 | # version. 11 | # 12 | # spop is distributed in the hope that it will be useful, but WITHOUT ANY 13 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 14 | # A PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along with 17 | # spop. If not, see . 18 | # 19 | # Additional permission under GNU GPL version 3 section 7 20 | # 21 | # If you modify this Program, or any covered work, by linking or combining it 22 | # with libspotify (or a modified version of that library), containing parts 23 | # covered by the terms of the Libspotify Terms of Use, the licensors of this 24 | # Program grant you additional permission to convey the resulting work. 25 | 26 | import collections 27 | import json 28 | import os 29 | import os.path 30 | import socket 31 | import subprocess 32 | 33 | # {{{ Parameters 34 | # Possible values: {index}, {artist}, {track}, {album}, {duration}, {uri}, {playing} 35 | TRACK_FMT = "{playing}{index} - {artist} - {album} - {track} ({duration})" 36 | SEARCH_ALBUM_FMT = "({year}) {artist} - {album} ({tracks} tracks)" 37 | SEARCH_PLAYLIST_FMT = "{name} (by {owner}, {tracks} tracks)" 38 | SEARCH_TRACK_FMT = "{artist} - {album} - {track} ({duration})" 39 | STAR_TRACK_FMT = "{action} \"{title}\" by {artist}" 40 | 41 | DMENU_OPTS = ["-i", "-l", "40"] 42 | ROFI_OPTS = ["-dmenu", "-i" , "-columns" , "1"] 43 | 44 | # Use rofi if it's available 45 | USE_ROFI = False 46 | for _path in os.get_exec_path(): 47 | _path = os.path.join(_path, "rofi") 48 | if os.path.isfile(_path) and os.access(_path, os.R_OK | os.X_OK): 49 | USE_ROFI = True 50 | break 51 | # }}} 52 | # {{{ Spop client 53 | class SpopClient: 54 | def __init__(self, host, port): 55 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 56 | self._sock.connect((host, port)) 57 | self.greeting = self._sock.recv(1024).decode().strip() 58 | 59 | def _command(self, command, *args): 60 | esc_args = ['"'+arg.replace('"', '\\"')+'"' if type(arg) is str else str(arg) for arg in args] 61 | esc_args.insert(0, command) 62 | cmd = " ".join(esc_args) + "\n" 63 | self._sock.send(cmd.encode()) 64 | 65 | buf = b"" 66 | while True: 67 | tmp = self._sock.recv(1024) 68 | buf += tmp 69 | try: 70 | obj = json.loads(buf.decode()) 71 | return obj 72 | except: 73 | pass 74 | 75 | def __getattr__(self, name): 76 | if name in ("repeat", "shuffle", "qclear", "qls", "ls", "goto", "add", "next", "prev", "toggle", "play", "offline_toggle", 77 | "search", "star", "status", "uadd", "uinfo", "uplay"): 78 | def func(*attrs): 79 | return self._command(name.replace("_", "-"), *attrs) 80 | return func 81 | else: 82 | raise AttributeError 83 | # }}} 84 | # {{{ Dmenu interaction 85 | def dmenu(items, highlight=[], prompt="dspop > "): 86 | args = ["dmenu"] + DMENU_OPTS 87 | if USE_ROFI: 88 | args = ["rofi", "-p", prompt] + ROFI_OPTS 89 | if len(highlight) > 0: 90 | args += ["-a", ",".join(highlight)] 91 | 92 | p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) 93 | for it in items: 94 | p.stdin.write((it+"\n").encode()) 95 | 96 | out, _ = p.communicate() 97 | 98 | if len(out) == 0: 99 | return None 100 | 101 | out = out[:-1].decode() 102 | return items.index(out) 103 | 104 | def dmenu_input(prompt): 105 | args = ["rofi" if USE_ROFI else "dmenu", "-p", prompt] 106 | args.extend(ROFI_OPTS if USE_ROFI else DMENU_OPTS) 107 | p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) 108 | out, _ = p.communicate() 109 | return out[:-1].decode() 110 | 111 | 112 | def format_search_album(album): 113 | params = { 114 | "album": album["title"], 115 | "artist": album["artist"], 116 | "year": album["year"], 117 | "type": album["album_type"], 118 | "tracks": len(album["tracks"]), 119 | } 120 | return SEARCH_ALBUM_FMT.format(**params) 121 | 122 | def format_search_playlist(playlist): 123 | params = { 124 | "name": playlist["name"], 125 | "owner": playlist["owner"], 126 | "tracks": len(playlist["tracks"]), 127 | } 128 | return SEARCH_PLAYLIST_FMT.format(**params) 129 | 130 | def format_search_track(track): 131 | params = { 132 | "album": track["album"], 133 | "artist": track["artist"], 134 | "duration": "%d:%02d" % divmod(track["duration"]/1000, 60), 135 | "index": track["index"], 136 | "track": track["title"], 137 | "uri": track["uri"] 138 | } 139 | return SEARCH_TRACK_FMT.format(**params) 140 | 141 | def format_tracks(tracks, current_track): 142 | index_len = len(str(len(tracks))) 143 | index_format = "%{}d".format(index_len) 144 | 145 | items = [] 146 | for track in tracks: 147 | playing = "* " if "uri" in current_track and current_track["uri"] == track["uri"] and not USE_ROFI else " " 148 | if USE_ROFI: 149 | playing = "" 150 | params = { 151 | "album": track["album"], 152 | "artist": track["artist"], 153 | "duration": "%d:%02d" % divmod(track["duration"]/1000, 60), 154 | "index": index_format % track["index"], 155 | "track": track["title"], 156 | "uri": track["uri"], 157 | "playing": playing, 158 | } 159 | items.append(TRACK_FMT.format(**params)) 160 | return items 161 | 162 | def format_folder(items, indent=0, offline_status=False): 163 | dst, indices = [], [] 164 | for it in items: 165 | if not it: 166 | continue 167 | if it["type"] == "separator": 168 | dst.append(" " * (indent + (4 if offline_status else 0)) + "-"*40) 169 | indices.append(None) 170 | elif it["type"] == "folder": 171 | dst.append(" " * (indent + (4 if offline_status else 0)) + "+ " + it["name"]) 172 | indices.append(None) 173 | rdst, rind = format_folder(it["playlists"], indent+2, offline_status) 174 | dst.extend(rdst) 175 | indices.extend(rind) 176 | elif it["type"] == "playlist": 177 | s = " "*indent 178 | if offline_status: 179 | s = ("[X] " if it["offline"] else "[ ] ") + s 180 | s += "{0[name]} ({0[tracks]})".format(it) 181 | dst.append(s) 182 | indices.append(it["index"]) 183 | return dst, indices 184 | # }}} 185 | # {{{ Menus 186 | def main_menu(sc): 187 | done = False 188 | while not done: 189 | status = sc.status() 190 | choices = [ 191 | ("queue", "[ Queue ]>"), 192 | ("playlists", "[ Playlists ]>"), 193 | ("repeat", "[ Repeat ]"), 194 | ("shuffle", "[ Shuffle ]"), 195 | ("sep", "-"*40), 196 | ("search", "[ Search ]>"), 197 | ("search_pl", "[ Search (with playlists) ]>"), 198 | ("offline", "[ Offline playlists ]>"), 199 | ("sep", "-"*40), 200 | ("toggle", "[ Toggle Playback ]"), 201 | ("next", "[ Next Track ]"), 202 | ("prev", "[ Previous Track ]"), 203 | ] 204 | if "starred" in status: 205 | action = "[ Unstar ]" if status["starred"] else "[ Star ]" 206 | choices += [("star", STAR_TRACK_FMT.format(action=action, **status))] 207 | 208 | idx = dmenu([ch[1] for ch in choices]) 209 | choice = None if idx is None else choices[idx][0] 210 | 211 | if choice is None: 212 | done = True 213 | elif choice == "queue": 214 | done = menu_queue(sc) 215 | elif choice == "playlists": 216 | done = menu_playlists(sc) 217 | elif choice == "offline": 218 | menu_offline(sc) 219 | elif choice == "repeat": 220 | sc.repeat() 221 | elif choice == "shuffle": 222 | sc.shuffle() 223 | elif choice == "toggle": 224 | sc.toggle() 225 | elif choice == "next": 226 | sc.next() 227 | elif choice == "prev": 228 | sc.prev() 229 | elif choice == "star": 230 | sc.star() 231 | elif choice == "search": 232 | done = menu_search(sc) 233 | elif choice == "search_pl": 234 | done = menu_search(sc, include_playlists=True) 235 | 236 | def menu_queue(sc): 237 | done = False 238 | while not done: 239 | items = ["[ Clear queue ]"] 240 | 241 | tracks = sc.qls()["tracks"] 242 | current_track = sc.status() 243 | highlight = [] 244 | if "current_track" in current_track: 245 | highlight = [str(current_track["current_track"])] 246 | 247 | items.extend(format_tracks(tracks, current_track)) 248 | choice = dmenu(items, highlight, "Play Queue > ") 249 | 250 | if choice is None: 251 | done = True 252 | elif choice == 0: 253 | sc.qclear() 254 | done = True 255 | else: 256 | sc.goto(choice) 257 | return False 258 | 259 | def menu_playlists(sc): 260 | pls = sc.ls()["playlists"] 261 | items, indices = format_folder(pls) 262 | 263 | done = False 264 | while not done: 265 | choice = dmenu(items) 266 | if choice is None: 267 | return False 268 | else: 269 | idx = indices[choice] 270 | if idx is None: 271 | continue 272 | done = menu_playlist(sc, idx) 273 | return True 274 | 275 | def menu_playlist(sc, idx): 276 | tracks = sc.ls(idx)["tracks"] 277 | 278 | # TODO: handle unavailable tracks 279 | items = ["[ Set as queue ]", "[ Add to queue ]"] 280 | current_track = sc.status() 281 | items.extend(format_tracks(tracks, current_track)) 282 | 283 | choice = dmenu(items) 284 | if choice is None: 285 | return False 286 | elif choice == 0: 287 | sc.play(idx) 288 | return False 289 | elif choice == 1: 290 | sc.add(idx) 291 | return False 292 | else: 293 | sc.play(idx, choice-1) 294 | return True 295 | 296 | def menu_offline(sc): 297 | while True: 298 | pls = sc.ls()["playlists"] 299 | items, indices = format_folder(pls, offline_status=True) 300 | choice = dmenu(items) 301 | if choice is None: 302 | return False 303 | else: 304 | idx = indices[choice] 305 | if idx is None: 306 | continue 307 | sc.offline_toggle(idx) 308 | 309 | def menu_search(sc, include_playlists=False): 310 | query = dmenu_input("Query > ") 311 | if len(query) == 0: 312 | return False 313 | 314 | res = sc.search(query) 315 | containers_by_uri = {} 316 | 317 | SearchItem = collections.namedtuple("SearchItem", ["uri", "title", "is_container"]) 318 | items = [] 319 | albums, singles = [], [] 320 | 321 | if "albums" in res and len(res["albums"]) > 0: 322 | for album in res["albums"]: 323 | if not album["available"]: 324 | continue 325 | ainfo = sc.uinfo(album["uri"]) 326 | containers_by_uri[album["uri"]] = ainfo 327 | item = SearchItem(album["uri"], " " + format_search_album(ainfo), True) 328 | if ainfo["album_type"] == "single": 329 | singles.append(item) 330 | else: 331 | albums.append(item) 332 | 333 | if len(albums) > 0: 334 | albums.sort(key=lambda item: item.title) 335 | items.append(SearchItem(None, "Albums:", None)) 336 | items.extend(albums) 337 | 338 | if "tracks" in res and len(res["tracks"]) > 0: 339 | items.append(SearchItem(None, "Tracks:", None)) 340 | for track in res["tracks"]: 341 | if not track["available"]: 342 | continue 343 | items.append(SearchItem(track["uri"], " " + format_search_track(track), False)) 344 | 345 | if len(singles) > 0: 346 | singles.sort(key=lambda item: item.title) 347 | items.append(SearchItem(None, "Singles:", None)) 348 | items.extend(singles) 349 | 350 | if include_playlists and "playlists" in res and len(res["playlists"]) > 0: 351 | playlists = [] 352 | for playlist in res["playlists"]: 353 | if "error" in playlist: 354 | continue 355 | pinfo = sc.uinfo(playlist["uri"]) 356 | if "error" in pinfo: 357 | continue 358 | containers_by_uri[playlist["uri"]] = pinfo 359 | item = SearchItem(playlist["uri"], " " + format_search_playlist(pinfo), True) 360 | playlists.append(item) 361 | if len(playlists) > 0: 362 | playlists.sort(key=lambda item: item.title) 363 | items.append(SearchItem(None, "Playlists:", None)) 364 | items.extend(playlists) 365 | 366 | while True: 367 | idx = dmenu([it.title for it in items]) 368 | sel_item = None if idx is None else items[idx] 369 | if sel_item is None or sel_item.uri is None: 370 | return False 371 | 372 | sel_items = ["[ Set as queue ]", "[ Add to queue ]"] 373 | highlight = [] 374 | 375 | if sel_item.is_container: 376 | sel_cont = containers_by_uri[sel_item.uri] 377 | sel_cont_tracks = [track for track in sel_cont["tracks"] 378 | if track["available"]] 379 | current_track = sc.status() 380 | if "uri" in current_track: 381 | for idx, track in enumerate(sel_cont_tracks): 382 | if track["uri"] == current_track["uri"]: 383 | highlight = [str(idx + 2)] 384 | break 385 | sel_items.extend(format_tracks(sel_cont_tracks, current_track)) 386 | 387 | sel_choice = dmenu(sel_items, highlight) 388 | if sel_choice == 0: 389 | sc.uplay(sel_item.uri) 390 | elif sel_choice == 1: 391 | sc.uadd(sel_item.uri) 392 | elif sel_choice is not None: 393 | sc.uplay(sel_cont_tracks[sel_choice-2]["uri"]) 394 | # }}} 395 | 396 | if __name__ == "__main__": 397 | sc = SpopClient("localhost", 6602) 398 | main_menu(sc) 399 | 400 | # Local Variables: 401 | # mode: python 402 | # End: 403 | -------------------------------------------------------------------------------- /plugins/ao.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "spop.h" 35 | #include "audio.h" 36 | 37 | #define BUFSIZE 8192 38 | #define BUFNB 16 39 | 40 | typedef struct { 41 | size_t size; 42 | char* buf; 43 | } lao_buf; 44 | 45 | static gboolean g_ao_init = FALSE; 46 | static int g_ao_driver = -1; 47 | static ao_device* g_ao_dev = NULL; 48 | static ao_option* g_ao_options = NULL; 49 | static size_t g_ao_frame_size; 50 | 51 | static gboolean g_playing = FALSE; 52 | static int g_stutters = 0; 53 | 54 | static GQueue* g_free_bufs = NULL; 55 | static GQueue* g_full_bufs = NULL; 56 | static GMutex g_buf_mutex; 57 | static GCond g_play_cond; 58 | 59 | /* Prototypes for private functions */ 60 | static void lao_setup(const sp_audioformat* format); 61 | static void lao_close(); 62 | 63 | /* My own strerror for libao */ 64 | static const char* lao_strerror(void) { 65 | int ao_err = errno; 66 | 67 | switch(ao_err) { 68 | case AO_ENODRIVER: return "No corresponding driver"; 69 | case AO_ENOTFILE: return "This driver is not a file"; 70 | case AO_ENOTLIVE: return "This driver is not a live output device"; 71 | case AO_EBADOPTION: return "A valid option key has an invalid value"; 72 | case AO_EOPENDEVICE: return "Cannot open the device"; 73 | case AO_EOPENFILE: return "Cannot open the file"; 74 | case AO_EFILEEXISTS: return "File exists"; 75 | case AO_EBADFORMAT: return "Bad format"; 76 | case AO_EFAIL: 77 | default: return "Unknown error"; 78 | } 79 | } 80 | 81 | /* Audio player thread */ 82 | static void* lao_player(gpointer data) { 83 | while (!g_ao_dev) { 84 | /* Device not ready: wait */ 85 | usleep(10000); 86 | } 87 | 88 | g_mutex_lock(&g_buf_mutex); 89 | 90 | while (TRUE) { 91 | lao_buf* buf = g_queue_pop_head(g_full_bufs); 92 | 93 | if (buf) { 94 | /* There is something to play */ 95 | g_mutex_unlock(&g_buf_mutex); 96 | 97 | if (!ao_play(g_ao_dev, buf->buf, buf->size)) 98 | g_error("Error while playing sound with libao"); 99 | 100 | g_mutex_lock(&g_buf_mutex); 101 | g_queue_push_tail(g_free_bufs, buf); 102 | } 103 | else { 104 | /* Nothing to play */ 105 | if (g_playing) 106 | g_stutters += 1; 107 | 108 | /* Wait for new data to be available. If nothing happens in a few 109 | seconds, playback may have stopped for good. In that case it 110 | makes sense to close the device. */ 111 | gint64 wait_end = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND; 112 | if (!g_cond_wait_until(&g_play_cond, &g_buf_mutex, wait_end)) { 113 | /* Timeout: better reset the device */ 114 | lao_close(); 115 | 116 | /* Now wait until we're ready to play again */ 117 | g_cond_wait(&g_play_cond, &g_buf_mutex); 118 | } 119 | } 120 | } 121 | 122 | return NULL; 123 | } 124 | 125 | /* "Private" function, used to set up the libao device */ 126 | static void lao_setup(const sp_audioformat* format) { 127 | ao_sample_format lao_fmt; 128 | 129 | if (!g_ao_init) { 130 | GError* err = NULL; 131 | lao_buf* bufs = NULL; 132 | int i; 133 | 134 | ao_initialize(); 135 | g_ao_driver = ao_default_driver_id(); 136 | ao_append_option(&g_ao_options, "client_name", "spop " SPOP_VERSION); 137 | 138 | g_free_bufs = g_queue_new(); 139 | if (!g_free_bufs) 140 | g_error("Can't allocate a queue of free buffers"); 141 | g_full_bufs = g_queue_new(); 142 | if (!g_full_bufs) 143 | g_error("Can't allocate a queue of full buffers"); 144 | 145 | bufs = (lao_buf*) malloc(BUFNB*sizeof(lao_buf)); 146 | if (!bufs) 147 | g_error("Can't allocate buffer structures"); 148 | for (i=0; i < BUFNB; i++) { 149 | bufs[i].buf = malloc(BUFSIZE); 150 | if (!(bufs[i].buf)) 151 | g_error("Can't allocate buffer"); 152 | g_queue_push_tail(g_free_bufs, &(bufs[i])); 153 | } 154 | 155 | if (!g_thread_try_new("ao_player", lao_player, NULL, &err)) 156 | g_error("Error while creating libao player thread: %s", err->message); 157 | g_ao_init = TRUE; 158 | } 159 | 160 | /* Set up sample format */ 161 | if (format->sample_type != SP_SAMPLETYPE_INT16_NATIVE_ENDIAN) 162 | g_error("Unsupported sample type"); 163 | 164 | lao_fmt.bits = 16; 165 | lao_fmt.rate = format->sample_rate; 166 | lao_fmt.channels = format->channels; 167 | lao_fmt.byte_format = AO_FMT_NATIVE; 168 | lao_fmt.matrix = NULL; 169 | g_ao_frame_size = sizeof(int16_t) * format->channels; 170 | 171 | /* Open the device */ 172 | g_ao_dev = ao_open_live(g_ao_driver, &lao_fmt, g_ao_options); 173 | if (!g_ao_dev) 174 | g_error("Error while opening libao device: %s", lao_strerror()); 175 | } 176 | 177 | /* "Private" function, used to shut down the libao device */ 178 | static void lao_close() { 179 | if (g_ao_dev) { 180 | ao_close(g_ao_dev); 181 | g_ao_dev = NULL; 182 | } 183 | } 184 | 185 | /* "Public" function, called from a libspotify callback */ 186 | G_MODULE_EXPORT int audio_delivery(const sp_audioformat* format, const void* frames, int num_frames) { 187 | lao_buf* buf; 188 | size_t size; 189 | 190 | /* What are we supposed to do here? */ 191 | if (num_frames == 0) { 192 | /* Pause: flush the queue */ 193 | g_mutex_lock(&g_buf_mutex); 194 | g_playing = FALSE; 195 | while (g_queue_get_length(g_full_bufs) > 0) { 196 | buf = g_queue_pop_tail(g_full_bufs); 197 | g_queue_push_tail(g_free_bufs, buf); 198 | } 199 | g_mutex_unlock(&g_buf_mutex); 200 | return 0; 201 | } 202 | else { 203 | if (!g_ao_dev) 204 | lao_setup(format); 205 | 206 | /* Try to add data to the queue */ 207 | g_mutex_lock(&g_buf_mutex); 208 | g_playing = TRUE; 209 | 210 | if (g_queue_get_length(g_free_bufs) == 0) { 211 | /* Can't add anything */ 212 | g_mutex_unlock(&g_buf_mutex); 213 | return 0; 214 | } 215 | 216 | /* Copy data to a free buffer */ 217 | buf = g_queue_pop_head(g_free_bufs); 218 | size = BUFSIZE < (num_frames * g_ao_frame_size) ? BUFSIZE : (num_frames * g_ao_frame_size); 219 | memcpy(buf->buf, frames, size); 220 | buf->size = size; 221 | 222 | /* Make the buffer available to the player */ 223 | g_queue_push_tail(g_full_bufs, buf); 224 | g_cond_signal(&g_play_cond); 225 | 226 | g_mutex_unlock(&g_buf_mutex); 227 | 228 | return size / g_ao_frame_size; 229 | } 230 | } 231 | 232 | /* Increment stats->samples by the number of frames in a buffer */ 233 | static void _compute_samples_in_buffer(lao_buf* buf, int* samples) { 234 | if (buf) { 235 | *samples += buf->size / g_ao_frame_size; 236 | } 237 | } 238 | 239 | /* "Public" function, called from a libspotify callback */ 240 | G_MODULE_EXPORT void get_audio_buffer_stats(sp_session* session, sp_audio_buffer_stats* stats) { 241 | g_mutex_lock(&g_buf_mutex); 242 | 243 | /* Compute the number of samples in each buffer */ 244 | stats->samples = 0; 245 | if (g_full_bufs) 246 | g_queue_foreach(g_full_bufs, (GFunc) _compute_samples_in_buffer, &(stats->samples)); 247 | 248 | stats->stutter = g_stutters; 249 | g_stutters = 0; 250 | 251 | if (stats->stutter > 0) 252 | g_debug("ao stats: samples: %d; stutter: %d", stats->samples, stats->stutter); 253 | 254 | g_mutex_unlock(&g_buf_mutex); 255 | } 256 | -------------------------------------------------------------------------------- /plugins/awesome.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "spop.h" 32 | #include "interface.h" 33 | #include "queue.h" 34 | #include "spotify.h" 35 | #include "utils.h" 36 | 37 | #define AWESOME_DBUS_SERVICE "org.naquadah.awesome.awful" 38 | #define AWESOME_DBUS_PATH "/" 39 | #define AWESOME_DBUS_INTERFACE "org.naquadah.awesome.awful.Remote" 40 | #define AWESOME_DBUS_METHOD "Eval" 41 | 42 | static DBusGConnection* g_connection = NULL; 43 | static DBusGProxy* g_proxy = NULL; 44 | 45 | #define col(c, txt) "" txt "" 46 | 47 | static void set_text(const gchar* txt) { 48 | GError* err = NULL; 49 | GString* str; 50 | 51 | str = g_string_sized_new(256); 52 | g_string_printf(str, "tb_spop.text=\" %s \"\n", txt); 53 | if (!dbus_g_proxy_call(g_proxy, AWESOME_DBUS_METHOD, &err, 54 | G_TYPE_STRING, str->str, 55 | G_TYPE_INVALID, G_TYPE_INVALID)) 56 | g_warning("Could not send command to Awesome via D-Bus: %s", err->message); 57 | g_string_free(str, TRUE); 58 | } 59 | 60 | static void notification_callback(const GString* status, gpointer data) { 61 | queue_status qs; 62 | gboolean repeat, shuffle; 63 | sp_track* cur_track; 64 | int cur_track_nb; 65 | int tot_tracks; 66 | 67 | GString* text; 68 | GString* rep_shuf; 69 | 70 | /* Read full status */ 71 | qs = queue_get_status(&cur_track, &cur_track_nb, &tot_tracks); 72 | repeat = queue_get_repeat(); 73 | shuffle = queue_get_shuffle(); 74 | 75 | /* Prepare the data to display */ 76 | text = g_string_sized_new(1024); 77 | rep_shuf = g_string_sized_new(40); 78 | 79 | if (repeat || shuffle) 80 | g_string_printf(rep_shuf, " [" col("#daf", "%s") "]", 81 | repeat ? (shuffle ? "rs" : "r") : "s"); 82 | 83 | if (qs == STOPPED) 84 | g_string_printf(text, "[stopped]%s", rep_shuf->str); 85 | else { 86 | /* Read more data */ 87 | guint track_min, track_sec, pos_min, pos_sec; 88 | gchar* track_name; 89 | gchar* track_artist; 90 | GString* short_title = NULL; 91 | 92 | track_get_data(cur_track, &track_name, &track_artist, NULL, NULL, &track_sec, NULL, NULL); 93 | pos_sec = session_play_time() / 1000; 94 | track_min = track_sec / 60; 95 | track_sec %= 60; 96 | pos_min = pos_sec / 60; 97 | pos_sec %= 60; 98 | 99 | /* Prepare data to display */ 100 | if (qs == PAUSED) 101 | g_string_append(text, "[p] "); 102 | 103 | /* Assume the strings are valid UTF-8 */ 104 | if (g_utf8_strlen(track_name, -1) >= 30) { 105 | gchar* tmp_string = g_utf8_substring(track_name, 0, 29); 106 | short_title = g_string_new(tmp_string); 107 | g_string_append(short_title, "…"); 108 | g_free(tmp_string); 109 | } 110 | else { 111 | short_title = g_string_new(track_name); 112 | } 113 | 114 | g_string_append_printf(text, 115 | "[" col("#afd", "%d") ": " col("#adf", "%s") " / " col("#fad", "%s") "]" 116 | " [" col("#dfa", "%u:%02u") "/" col("#dfa", "%u:%02u") "]%s", 117 | cur_track_nb+1, track_artist, short_title->str, 118 | pos_min, pos_sec, track_min, track_sec, rep_shuf->str); 119 | 120 | /* Free some memory */ 121 | g_string_free(short_title, TRUE); 122 | g_free(track_name); 123 | g_free(track_artist); 124 | } 125 | g_string_free(rep_shuf, TRUE); 126 | 127 | /* Replace " with \" and & with & */ 128 | g_string_replace(text, "\"", "\\\""); 129 | g_string_replace(text, "&", "&"); 130 | 131 | /* Send to Awesome */ 132 | set_text(text->str); 133 | 134 | g_string_free(text, TRUE); 135 | } 136 | 137 | static gboolean timeout_callback(gpointer data) { 138 | notification_callback(NULL, NULL); 139 | return TRUE; 140 | } 141 | 142 | G_MODULE_EXPORT void spop_awesome_init() { 143 | GError* err = NULL; 144 | 145 | /* Init D-Bus */ 146 | g_connection = dbus_g_bus_get(DBUS_BUS_SESSION, &err); 147 | if (!g_connection) 148 | g_error("Can't connect to D-Bus: %s", err->message); 149 | 150 | g_proxy = dbus_g_proxy_new_for_name(g_connection, 151 | AWESOME_DBUS_SERVICE, AWESOME_DBUS_PATH, AWESOME_DBUS_INTERFACE); 152 | 153 | /* Add a notification callback */ 154 | if (!interface_notify_add_callback(notification_callback, NULL)) 155 | g_error("Could not add Awesome callback."); 156 | 157 | /* Add a timeout callback */ 158 | g_timeout_add_seconds(1, timeout_callback, NULL); 159 | 160 | /* Display something */ 161 | //set_text("[spop]"); 162 | notification_callback(NULL, NULL); 163 | } 164 | 165 | G_MODULE_EXPORT void spop_awesome_close() { 166 | /* Restore a neutral message when exiting */ 167 | set_text("[spop]"); 168 | } 169 | -------------------------------------------------------------------------------- /plugins/dummy.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #include 27 | 28 | #include "audio.h" 29 | #include "config.h" 30 | 31 | /* "Public" function, called from a libspotify callback */ 32 | G_MODULE_EXPORT int audio_delivery(const sp_audioformat* format, const void* frames, int num_frames) { 33 | return num_frames; 34 | } 35 | -------------------------------------------------------------------------------- /plugins/mpris2/mpris2-generated.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Generated by gdbus-codegen 2.41.4. DO NOT EDIT. 3 | * 4 | * The license of this code is the same as for the source it was derived from. 5 | */ 6 | 7 | #ifndef __MPRIS2_GENERATED_H__ 8 | #define __MPRIS2_GENERATED_H__ 9 | 10 | #include 11 | 12 | G_BEGIN_DECLS 13 | 14 | 15 | /* ------------------------------------------------------------------------ */ 16 | /* Declarations for org.mpris.MediaPlayer2 */ 17 | 18 | #define MPRIS2_TYPE_ (mpris2__get_type ()) 19 | #define MPRIS2_(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MPRIS2_TYPE_, Mpris2)) 20 | #define MPRIS2_IS_(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MPRIS2_TYPE_)) 21 | #define MPRIS2__GET_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), MPRIS2_TYPE_, Mpris2Iface)) 22 | 23 | struct _Mpris2; 24 | typedef struct _Mpris2 Mpris2; 25 | typedef struct _Mpris2Iface Mpris2Iface; 26 | 27 | struct _Mpris2Iface 28 | { 29 | GTypeInterface parent_iface; 30 | 31 | 32 | gboolean (*handle_quit) ( 33 | Mpris2 *object, 34 | GDBusMethodInvocation *invocation); 35 | 36 | gboolean (*handle_raise) ( 37 | Mpris2 *object, 38 | GDBusMethodInvocation *invocation); 39 | 40 | gboolean (*get_can_quit) (Mpris2 *object); 41 | 42 | gboolean (*get_can_raise) (Mpris2 *object); 43 | 44 | gboolean (*get_can_set_fullscreen) (Mpris2 *object); 45 | 46 | const gchar * (*get_desktop_entry) (Mpris2 *object); 47 | 48 | gboolean (*get_fullscreen) (Mpris2 *object); 49 | 50 | gboolean (*get_has_track_list) (Mpris2 *object); 51 | 52 | const gchar * (*get_identity) (Mpris2 *object); 53 | 54 | const gchar *const * (*get_supported_mime_types) (Mpris2 *object); 55 | 56 | const gchar *const * (*get_supported_uri_schemes) (Mpris2 *object); 57 | 58 | }; 59 | 60 | GType mpris2__get_type (void) G_GNUC_CONST; 61 | 62 | GDBusInterfaceInfo *mpris2__interface_info (void); 63 | guint mpris2__override_properties (GObjectClass *klass, guint property_id_begin); 64 | 65 | 66 | /* D-Bus method call completion functions: */ 67 | void mpris2__complete_raise ( 68 | Mpris2 *object, 69 | GDBusMethodInvocation *invocation); 70 | 71 | void mpris2__complete_quit ( 72 | Mpris2 *object, 73 | GDBusMethodInvocation *invocation); 74 | 75 | 76 | 77 | /* D-Bus method calls: */ 78 | void mpris2__call_raise ( 79 | Mpris2 *proxy, 80 | GCancellable *cancellable, 81 | GAsyncReadyCallback callback, 82 | gpointer user_data); 83 | 84 | gboolean mpris2__call_raise_finish ( 85 | Mpris2 *proxy, 86 | GAsyncResult *res, 87 | GError **error); 88 | 89 | gboolean mpris2__call_raise_sync ( 90 | Mpris2 *proxy, 91 | GCancellable *cancellable, 92 | GError **error); 93 | 94 | void mpris2__call_quit ( 95 | Mpris2 *proxy, 96 | GCancellable *cancellable, 97 | GAsyncReadyCallback callback, 98 | gpointer user_data); 99 | 100 | gboolean mpris2__call_quit_finish ( 101 | Mpris2 *proxy, 102 | GAsyncResult *res, 103 | GError **error); 104 | 105 | gboolean mpris2__call_quit_sync ( 106 | Mpris2 *proxy, 107 | GCancellable *cancellable, 108 | GError **error); 109 | 110 | 111 | 112 | /* D-Bus property accessors: */ 113 | gboolean mpris2__get_can_quit (Mpris2 *object); 114 | void mpris2__set_can_quit (Mpris2 *object, gboolean value); 115 | 116 | gboolean mpris2__get_fullscreen (Mpris2 *object); 117 | void mpris2__set_fullscreen (Mpris2 *object, gboolean value); 118 | 119 | gboolean mpris2__get_can_set_fullscreen (Mpris2 *object); 120 | void mpris2__set_can_set_fullscreen (Mpris2 *object, gboolean value); 121 | 122 | gboolean mpris2__get_can_raise (Mpris2 *object); 123 | void mpris2__set_can_raise (Mpris2 *object, gboolean value); 124 | 125 | gboolean mpris2__get_has_track_list (Mpris2 *object); 126 | void mpris2__set_has_track_list (Mpris2 *object, gboolean value); 127 | 128 | const gchar *mpris2__get_identity (Mpris2 *object); 129 | gchar *mpris2__dup_identity (Mpris2 *object); 130 | void mpris2__set_identity (Mpris2 *object, const gchar *value); 131 | 132 | const gchar *mpris2__get_desktop_entry (Mpris2 *object); 133 | gchar *mpris2__dup_desktop_entry (Mpris2 *object); 134 | void mpris2__set_desktop_entry (Mpris2 *object, const gchar *value); 135 | 136 | const gchar *const *mpris2__get_supported_uri_schemes (Mpris2 *object); 137 | gchar **mpris2__dup_supported_uri_schemes (Mpris2 *object); 138 | void mpris2__set_supported_uri_schemes (Mpris2 *object, const gchar *const *value); 139 | 140 | const gchar *const *mpris2__get_supported_mime_types (Mpris2 *object); 141 | gchar **mpris2__dup_supported_mime_types (Mpris2 *object); 142 | void mpris2__set_supported_mime_types (Mpris2 *object, const gchar *const *value); 143 | 144 | 145 | /* ---- */ 146 | 147 | #define MPRIS2_TYPE__PROXY (mpris2__proxy_get_type ()) 148 | #define MPRIS2__PROXY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MPRIS2_TYPE__PROXY, Mpris2Proxy)) 149 | #define MPRIS2__PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), MPRIS2_TYPE__PROXY, Mpris2ProxyClass)) 150 | #define MPRIS2__PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), MPRIS2_TYPE__PROXY, Mpris2ProxyClass)) 151 | #define MPRIS2_IS__PROXY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MPRIS2_TYPE__PROXY)) 152 | #define MPRIS2_IS__PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), MPRIS2_TYPE__PROXY)) 153 | 154 | typedef struct _Mpris2Proxy Mpris2Proxy; 155 | typedef struct _Mpris2ProxyClass Mpris2ProxyClass; 156 | typedef struct _Mpris2ProxyPrivate Mpris2ProxyPrivate; 157 | 158 | struct _Mpris2Proxy 159 | { 160 | /*< private >*/ 161 | GDBusProxy parent_instance; 162 | Mpris2ProxyPrivate *priv; 163 | }; 164 | 165 | struct _Mpris2ProxyClass 166 | { 167 | GDBusProxyClass parent_class; 168 | }; 169 | 170 | GType mpris2__proxy_get_type (void) G_GNUC_CONST; 171 | 172 | void mpris2__proxy_new ( 173 | GDBusConnection *connection, 174 | GDBusProxyFlags flags, 175 | const gchar *name, 176 | const gchar *object_path, 177 | GCancellable *cancellable, 178 | GAsyncReadyCallback callback, 179 | gpointer user_data); 180 | Mpris2 *mpris2__proxy_new_finish ( 181 | GAsyncResult *res, 182 | GError **error); 183 | Mpris2 *mpris2__proxy_new_sync ( 184 | GDBusConnection *connection, 185 | GDBusProxyFlags flags, 186 | const gchar *name, 187 | const gchar *object_path, 188 | GCancellable *cancellable, 189 | GError **error); 190 | 191 | void mpris2__proxy_new_for_bus ( 192 | GBusType bus_type, 193 | GDBusProxyFlags flags, 194 | const gchar *name, 195 | const gchar *object_path, 196 | GCancellable *cancellable, 197 | GAsyncReadyCallback callback, 198 | gpointer user_data); 199 | Mpris2 *mpris2__proxy_new_for_bus_finish ( 200 | GAsyncResult *res, 201 | GError **error); 202 | Mpris2 *mpris2__proxy_new_for_bus_sync ( 203 | GBusType bus_type, 204 | GDBusProxyFlags flags, 205 | const gchar *name, 206 | const gchar *object_path, 207 | GCancellable *cancellable, 208 | GError **error); 209 | 210 | 211 | /* ---- */ 212 | 213 | #define MPRIS2_TYPE__SKELETON (mpris2__skeleton_get_type ()) 214 | #define MPRIS2__SKELETON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MPRIS2_TYPE__SKELETON, Mpris2Skeleton)) 215 | #define MPRIS2__SKELETON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), MPRIS2_TYPE__SKELETON, Mpris2SkeletonClass)) 216 | #define MPRIS2__SKELETON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), MPRIS2_TYPE__SKELETON, Mpris2SkeletonClass)) 217 | #define MPRIS2_IS__SKELETON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MPRIS2_TYPE__SKELETON)) 218 | #define MPRIS2_IS__SKELETON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), MPRIS2_TYPE__SKELETON)) 219 | 220 | typedef struct _Mpris2Skeleton Mpris2Skeleton; 221 | typedef struct _Mpris2SkeletonClass Mpris2SkeletonClass; 222 | typedef struct _Mpris2SkeletonPrivate Mpris2SkeletonPrivate; 223 | 224 | struct _Mpris2Skeleton 225 | { 226 | /*< private >*/ 227 | GDBusInterfaceSkeleton parent_instance; 228 | Mpris2SkeletonPrivate *priv; 229 | }; 230 | 231 | struct _Mpris2SkeletonClass 232 | { 233 | GDBusInterfaceSkeletonClass parent_class; 234 | }; 235 | 236 | GType mpris2__skeleton_get_type (void) G_GNUC_CONST; 237 | 238 | Mpris2 *mpris2__skeleton_new (void); 239 | 240 | 241 | G_END_DECLS 242 | 243 | #endif /* __MPRIS2_GENERATED_H__ */ 244 | -------------------------------------------------------------------------------- /plugins/mpris2/mpris2-player-generated.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Generated by gdbus-codegen 2.41.4. DO NOT EDIT. 3 | * 4 | * The license of this code is the same as for the source it was derived from. 5 | */ 6 | 7 | #ifndef __MPRIS2_PLAYER_GENERATED_H__ 8 | #define __MPRIS2_PLAYER_GENERATED_H__ 9 | 10 | #include 11 | 12 | G_BEGIN_DECLS 13 | 14 | 15 | /* ------------------------------------------------------------------------ */ 16 | /* Declarations for org.mpris.MediaPlayer2.Player */ 17 | 18 | #define MPRIS2_TYPE_PLAYER (mpris2_player_get_type ()) 19 | #define MPRIS2_PLAYER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MPRIS2_TYPE_PLAYER, Mpris2Player)) 20 | #define MPRIS2_IS_PLAYER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MPRIS2_TYPE_PLAYER)) 21 | #define MPRIS2_PLAYER_GET_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), MPRIS2_TYPE_PLAYER, Mpris2PlayerIface)) 22 | 23 | struct _Mpris2Player; 24 | typedef struct _Mpris2Player Mpris2Player; 25 | typedef struct _Mpris2PlayerIface Mpris2PlayerIface; 26 | 27 | struct _Mpris2PlayerIface 28 | { 29 | GTypeInterface parent_iface; 30 | 31 | 32 | 33 | gboolean (*handle_next) ( 34 | Mpris2Player *object, 35 | GDBusMethodInvocation *invocation); 36 | 37 | gboolean (*handle_open_uri) ( 38 | Mpris2Player *object, 39 | GDBusMethodInvocation *invocation, 40 | const gchar *arg_Uri); 41 | 42 | gboolean (*handle_pause) ( 43 | Mpris2Player *object, 44 | GDBusMethodInvocation *invocation); 45 | 46 | gboolean (*handle_play) ( 47 | Mpris2Player *object, 48 | GDBusMethodInvocation *invocation); 49 | 50 | gboolean (*handle_play_pause) ( 51 | Mpris2Player *object, 52 | GDBusMethodInvocation *invocation); 53 | 54 | gboolean (*handle_previous) ( 55 | Mpris2Player *object, 56 | GDBusMethodInvocation *invocation); 57 | 58 | gboolean (*handle_seek) ( 59 | Mpris2Player *object, 60 | GDBusMethodInvocation *invocation, 61 | gint64 arg_Offset); 62 | 63 | gboolean (*handle_set_position) ( 64 | Mpris2Player *object, 65 | GDBusMethodInvocation *invocation, 66 | const gchar *arg_TrackId, 67 | gint64 arg_Position); 68 | 69 | gboolean (*handle_stop) ( 70 | Mpris2Player *object, 71 | GDBusMethodInvocation *invocation); 72 | 73 | gboolean (*get_can_control) (Mpris2Player *object); 74 | 75 | gboolean (*get_can_go_next) (Mpris2Player *object); 76 | 77 | gboolean (*get_can_go_previous) (Mpris2Player *object); 78 | 79 | gboolean (*get_can_pause) (Mpris2Player *object); 80 | 81 | gboolean (*get_can_play) (Mpris2Player *object); 82 | 83 | gboolean (*get_can_seek) (Mpris2Player *object); 84 | 85 | const gchar * (*get_loop_status) (Mpris2Player *object); 86 | 87 | gdouble (*get_maximum_rate) (Mpris2Player *object); 88 | 89 | GVariant * (*get_metadata) (Mpris2Player *object); 90 | 91 | gdouble (*get_minimum_rate) (Mpris2Player *object); 92 | 93 | const gchar * (*get_playback_status) (Mpris2Player *object); 94 | 95 | gint64 (*get_position) (Mpris2Player *object); 96 | 97 | gdouble (*get_rate) (Mpris2Player *object); 98 | 99 | gboolean (*get_shuffle) (Mpris2Player *object); 100 | 101 | gdouble (*get_volume) (Mpris2Player *object); 102 | 103 | void (*seeked) ( 104 | Mpris2Player *object, 105 | gint64 arg_Position); 106 | 107 | }; 108 | 109 | GType mpris2_player_get_type (void) G_GNUC_CONST; 110 | 111 | GDBusInterfaceInfo *mpris2_player_interface_info (void); 112 | guint mpris2_player_override_properties (GObjectClass *klass, guint property_id_begin); 113 | 114 | 115 | /* D-Bus method call completion functions: */ 116 | void mpris2_player_complete_next ( 117 | Mpris2Player *object, 118 | GDBusMethodInvocation *invocation); 119 | 120 | void mpris2_player_complete_previous ( 121 | Mpris2Player *object, 122 | GDBusMethodInvocation *invocation); 123 | 124 | void mpris2_player_complete_pause ( 125 | Mpris2Player *object, 126 | GDBusMethodInvocation *invocation); 127 | 128 | void mpris2_player_complete_play_pause ( 129 | Mpris2Player *object, 130 | GDBusMethodInvocation *invocation); 131 | 132 | void mpris2_player_complete_stop ( 133 | Mpris2Player *object, 134 | GDBusMethodInvocation *invocation); 135 | 136 | void mpris2_player_complete_play ( 137 | Mpris2Player *object, 138 | GDBusMethodInvocation *invocation); 139 | 140 | void mpris2_player_complete_seek ( 141 | Mpris2Player *object, 142 | GDBusMethodInvocation *invocation); 143 | 144 | void mpris2_player_complete_set_position ( 145 | Mpris2Player *object, 146 | GDBusMethodInvocation *invocation); 147 | 148 | void mpris2_player_complete_open_uri ( 149 | Mpris2Player *object, 150 | GDBusMethodInvocation *invocation); 151 | 152 | 153 | 154 | /* D-Bus signal emissions functions: */ 155 | void mpris2_player_emit_seeked ( 156 | Mpris2Player *object, 157 | gint64 arg_Position); 158 | 159 | 160 | 161 | /* D-Bus method calls: */ 162 | void mpris2_player_call_next ( 163 | Mpris2Player *proxy, 164 | GCancellable *cancellable, 165 | GAsyncReadyCallback callback, 166 | gpointer user_data); 167 | 168 | gboolean mpris2_player_call_next_finish ( 169 | Mpris2Player *proxy, 170 | GAsyncResult *res, 171 | GError **error); 172 | 173 | gboolean mpris2_player_call_next_sync ( 174 | Mpris2Player *proxy, 175 | GCancellable *cancellable, 176 | GError **error); 177 | 178 | void mpris2_player_call_previous ( 179 | Mpris2Player *proxy, 180 | GCancellable *cancellable, 181 | GAsyncReadyCallback callback, 182 | gpointer user_data); 183 | 184 | gboolean mpris2_player_call_previous_finish ( 185 | Mpris2Player *proxy, 186 | GAsyncResult *res, 187 | GError **error); 188 | 189 | gboolean mpris2_player_call_previous_sync ( 190 | Mpris2Player *proxy, 191 | GCancellable *cancellable, 192 | GError **error); 193 | 194 | void mpris2_player_call_pause ( 195 | Mpris2Player *proxy, 196 | GCancellable *cancellable, 197 | GAsyncReadyCallback callback, 198 | gpointer user_data); 199 | 200 | gboolean mpris2_player_call_pause_finish ( 201 | Mpris2Player *proxy, 202 | GAsyncResult *res, 203 | GError **error); 204 | 205 | gboolean mpris2_player_call_pause_sync ( 206 | Mpris2Player *proxy, 207 | GCancellable *cancellable, 208 | GError **error); 209 | 210 | void mpris2_player_call_play_pause ( 211 | Mpris2Player *proxy, 212 | GCancellable *cancellable, 213 | GAsyncReadyCallback callback, 214 | gpointer user_data); 215 | 216 | gboolean mpris2_player_call_play_pause_finish ( 217 | Mpris2Player *proxy, 218 | GAsyncResult *res, 219 | GError **error); 220 | 221 | gboolean mpris2_player_call_play_pause_sync ( 222 | Mpris2Player *proxy, 223 | GCancellable *cancellable, 224 | GError **error); 225 | 226 | void mpris2_player_call_stop ( 227 | Mpris2Player *proxy, 228 | GCancellable *cancellable, 229 | GAsyncReadyCallback callback, 230 | gpointer user_data); 231 | 232 | gboolean mpris2_player_call_stop_finish ( 233 | Mpris2Player *proxy, 234 | GAsyncResult *res, 235 | GError **error); 236 | 237 | gboolean mpris2_player_call_stop_sync ( 238 | Mpris2Player *proxy, 239 | GCancellable *cancellable, 240 | GError **error); 241 | 242 | void mpris2_player_call_play ( 243 | Mpris2Player *proxy, 244 | GCancellable *cancellable, 245 | GAsyncReadyCallback callback, 246 | gpointer user_data); 247 | 248 | gboolean mpris2_player_call_play_finish ( 249 | Mpris2Player *proxy, 250 | GAsyncResult *res, 251 | GError **error); 252 | 253 | gboolean mpris2_player_call_play_sync ( 254 | Mpris2Player *proxy, 255 | GCancellable *cancellable, 256 | GError **error); 257 | 258 | void mpris2_player_call_seek ( 259 | Mpris2Player *proxy, 260 | gint64 arg_Offset, 261 | GCancellable *cancellable, 262 | GAsyncReadyCallback callback, 263 | gpointer user_data); 264 | 265 | gboolean mpris2_player_call_seek_finish ( 266 | Mpris2Player *proxy, 267 | GAsyncResult *res, 268 | GError **error); 269 | 270 | gboolean mpris2_player_call_seek_sync ( 271 | Mpris2Player *proxy, 272 | gint64 arg_Offset, 273 | GCancellable *cancellable, 274 | GError **error); 275 | 276 | void mpris2_player_call_set_position ( 277 | Mpris2Player *proxy, 278 | const gchar *arg_TrackId, 279 | gint64 arg_Position, 280 | GCancellable *cancellable, 281 | GAsyncReadyCallback callback, 282 | gpointer user_data); 283 | 284 | gboolean mpris2_player_call_set_position_finish ( 285 | Mpris2Player *proxy, 286 | GAsyncResult *res, 287 | GError **error); 288 | 289 | gboolean mpris2_player_call_set_position_sync ( 290 | Mpris2Player *proxy, 291 | const gchar *arg_TrackId, 292 | gint64 arg_Position, 293 | GCancellable *cancellable, 294 | GError **error); 295 | 296 | void mpris2_player_call_open_uri ( 297 | Mpris2Player *proxy, 298 | const gchar *arg_Uri, 299 | GCancellable *cancellable, 300 | GAsyncReadyCallback callback, 301 | gpointer user_data); 302 | 303 | gboolean mpris2_player_call_open_uri_finish ( 304 | Mpris2Player *proxy, 305 | GAsyncResult *res, 306 | GError **error); 307 | 308 | gboolean mpris2_player_call_open_uri_sync ( 309 | Mpris2Player *proxy, 310 | const gchar *arg_Uri, 311 | GCancellable *cancellable, 312 | GError **error); 313 | 314 | 315 | 316 | /* D-Bus property accessors: */ 317 | const gchar *mpris2_player_get_playback_status (Mpris2Player *object); 318 | gchar *mpris2_player_dup_playback_status (Mpris2Player *object); 319 | void mpris2_player_set_playback_status (Mpris2Player *object, const gchar *value); 320 | 321 | const gchar *mpris2_player_get_loop_status (Mpris2Player *object); 322 | gchar *mpris2_player_dup_loop_status (Mpris2Player *object); 323 | void mpris2_player_set_loop_status (Mpris2Player *object, const gchar *value); 324 | 325 | gdouble mpris2_player_get_rate (Mpris2Player *object); 326 | void mpris2_player_set_rate (Mpris2Player *object, gdouble value); 327 | 328 | gboolean mpris2_player_get_shuffle (Mpris2Player *object); 329 | void mpris2_player_set_shuffle (Mpris2Player *object, gboolean value); 330 | 331 | GVariant *mpris2_player_get_metadata (Mpris2Player *object); 332 | GVariant *mpris2_player_dup_metadata (Mpris2Player *object); 333 | void mpris2_player_set_metadata (Mpris2Player *object, GVariant *value); 334 | 335 | gdouble mpris2_player_get_volume (Mpris2Player *object); 336 | void mpris2_player_set_volume (Mpris2Player *object, gdouble value); 337 | 338 | gint64 mpris2_player_get_position (Mpris2Player *object); 339 | void mpris2_player_set_position (Mpris2Player *object, gint64 value); 340 | 341 | gdouble mpris2_player_get_minimum_rate (Mpris2Player *object); 342 | void mpris2_player_set_minimum_rate (Mpris2Player *object, gdouble value); 343 | 344 | gdouble mpris2_player_get_maximum_rate (Mpris2Player *object); 345 | void mpris2_player_set_maximum_rate (Mpris2Player *object, gdouble value); 346 | 347 | gboolean mpris2_player_get_can_go_next (Mpris2Player *object); 348 | void mpris2_player_set_can_go_next (Mpris2Player *object, gboolean value); 349 | 350 | gboolean mpris2_player_get_can_go_previous (Mpris2Player *object); 351 | void mpris2_player_set_can_go_previous (Mpris2Player *object, gboolean value); 352 | 353 | gboolean mpris2_player_get_can_play (Mpris2Player *object); 354 | void mpris2_player_set_can_play (Mpris2Player *object, gboolean value); 355 | 356 | gboolean mpris2_player_get_can_pause (Mpris2Player *object); 357 | void mpris2_player_set_can_pause (Mpris2Player *object, gboolean value); 358 | 359 | gboolean mpris2_player_get_can_seek (Mpris2Player *object); 360 | void mpris2_player_set_can_seek (Mpris2Player *object, gboolean value); 361 | 362 | gboolean mpris2_player_get_can_control (Mpris2Player *object); 363 | void mpris2_player_set_can_control (Mpris2Player *object, gboolean value); 364 | 365 | 366 | /* ---- */ 367 | 368 | #define MPRIS2_TYPE_PLAYER_PROXY (mpris2_player_proxy_get_type ()) 369 | #define MPRIS2_PLAYER_PROXY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MPRIS2_TYPE_PLAYER_PROXY, Mpris2PlayerProxy)) 370 | #define MPRIS2_PLAYER_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), MPRIS2_TYPE_PLAYER_PROXY, Mpris2PlayerProxyClass)) 371 | #define MPRIS2_PLAYER_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), MPRIS2_TYPE_PLAYER_PROXY, Mpris2PlayerProxyClass)) 372 | #define MPRIS2_IS_PLAYER_PROXY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MPRIS2_TYPE_PLAYER_PROXY)) 373 | #define MPRIS2_IS_PLAYER_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), MPRIS2_TYPE_PLAYER_PROXY)) 374 | 375 | typedef struct _Mpris2PlayerProxy Mpris2PlayerProxy; 376 | typedef struct _Mpris2PlayerProxyClass Mpris2PlayerProxyClass; 377 | typedef struct _Mpris2PlayerProxyPrivate Mpris2PlayerProxyPrivate; 378 | 379 | struct _Mpris2PlayerProxy 380 | { 381 | /*< private >*/ 382 | GDBusProxy parent_instance; 383 | Mpris2PlayerProxyPrivate *priv; 384 | }; 385 | 386 | struct _Mpris2PlayerProxyClass 387 | { 388 | GDBusProxyClass parent_class; 389 | }; 390 | 391 | GType mpris2_player_proxy_get_type (void) G_GNUC_CONST; 392 | 393 | void mpris2_player_proxy_new ( 394 | GDBusConnection *connection, 395 | GDBusProxyFlags flags, 396 | const gchar *name, 397 | const gchar *object_path, 398 | GCancellable *cancellable, 399 | GAsyncReadyCallback callback, 400 | gpointer user_data); 401 | Mpris2Player *mpris2_player_proxy_new_finish ( 402 | GAsyncResult *res, 403 | GError **error); 404 | Mpris2Player *mpris2_player_proxy_new_sync ( 405 | GDBusConnection *connection, 406 | GDBusProxyFlags flags, 407 | const gchar *name, 408 | const gchar *object_path, 409 | GCancellable *cancellable, 410 | GError **error); 411 | 412 | void mpris2_player_proxy_new_for_bus ( 413 | GBusType bus_type, 414 | GDBusProxyFlags flags, 415 | const gchar *name, 416 | const gchar *object_path, 417 | GCancellable *cancellable, 418 | GAsyncReadyCallback callback, 419 | gpointer user_data); 420 | Mpris2Player *mpris2_player_proxy_new_for_bus_finish ( 421 | GAsyncResult *res, 422 | GError **error); 423 | Mpris2Player *mpris2_player_proxy_new_for_bus_sync ( 424 | GBusType bus_type, 425 | GDBusProxyFlags flags, 426 | const gchar *name, 427 | const gchar *object_path, 428 | GCancellable *cancellable, 429 | GError **error); 430 | 431 | 432 | /* ---- */ 433 | 434 | #define MPRIS2_TYPE_PLAYER_SKELETON (mpris2_player_skeleton_get_type ()) 435 | #define MPRIS2_PLAYER_SKELETON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MPRIS2_TYPE_PLAYER_SKELETON, Mpris2PlayerSkeleton)) 436 | #define MPRIS2_PLAYER_SKELETON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), MPRIS2_TYPE_PLAYER_SKELETON, Mpris2PlayerSkeletonClass)) 437 | #define MPRIS2_PLAYER_SKELETON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), MPRIS2_TYPE_PLAYER_SKELETON, Mpris2PlayerSkeletonClass)) 438 | #define MPRIS2_IS_PLAYER_SKELETON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MPRIS2_TYPE_PLAYER_SKELETON)) 439 | #define MPRIS2_IS_PLAYER_SKELETON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), MPRIS2_TYPE_PLAYER_SKELETON)) 440 | 441 | typedef struct _Mpris2PlayerSkeleton Mpris2PlayerSkeleton; 442 | typedef struct _Mpris2PlayerSkeletonClass Mpris2PlayerSkeletonClass; 443 | typedef struct _Mpris2PlayerSkeletonPrivate Mpris2PlayerSkeletonPrivate; 444 | 445 | struct _Mpris2PlayerSkeleton 446 | { 447 | /*< private >*/ 448 | GDBusInterfaceSkeleton parent_instance; 449 | Mpris2PlayerSkeletonPrivate *priv; 450 | }; 451 | 452 | struct _Mpris2PlayerSkeletonClass 453 | { 454 | GDBusInterfaceSkeletonClass parent_class; 455 | }; 456 | 457 | GType mpris2_player_skeleton_get_type (void) G_GNUC_CONST; 458 | 459 | Mpris2Player *mpris2_player_skeleton_new (void); 460 | 461 | 462 | G_END_DECLS 463 | 464 | #endif /* __MPRIS2_PLAYER_GENERATED_H__ */ 465 | -------------------------------------------------------------------------------- /plugins/mpris2/mpris2-tracklist-generated.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Generated by gdbus-codegen 2.41.4. DO NOT EDIT. 3 | * 4 | * The license of this code is the same as for the source it was derived from. 5 | */ 6 | 7 | #ifndef __MPRIS2_TRACKLIST_GENERATED_H__ 8 | #define __MPRIS2_TRACKLIST_GENERATED_H__ 9 | 10 | #include 11 | 12 | G_BEGIN_DECLS 13 | 14 | 15 | /* ------------------------------------------------------------------------ */ 16 | /* Declarations for org.mpris.MediaPlayer2.TrackList */ 17 | 18 | #define MPRIS2_TYPE_TRACK_LIST (mpris2_track_list_get_type ()) 19 | #define MPRIS2_TRACK_LIST(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MPRIS2_TYPE_TRACK_LIST, Mpris2TrackList)) 20 | #define MPRIS2_IS_TRACK_LIST(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MPRIS2_TYPE_TRACK_LIST)) 21 | #define MPRIS2_TRACK_LIST_GET_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), MPRIS2_TYPE_TRACK_LIST, Mpris2TrackListIface)) 22 | 23 | struct _Mpris2TrackList; 24 | typedef struct _Mpris2TrackList Mpris2TrackList; 25 | typedef struct _Mpris2TrackListIface Mpris2TrackListIface; 26 | 27 | struct _Mpris2TrackListIface 28 | { 29 | GTypeInterface parent_iface; 30 | 31 | 32 | 33 | gboolean (*handle_add_track) ( 34 | Mpris2TrackList *object, 35 | GDBusMethodInvocation *invocation, 36 | const gchar *arg_Uri, 37 | const gchar *arg_AfterTrack, 38 | gboolean arg_SetAsCurrent); 39 | 40 | gboolean (*handle_get_tracks_metadata) ( 41 | Mpris2TrackList *object, 42 | GDBusMethodInvocation *invocation, 43 | const gchar *const *arg_TrackIds); 44 | 45 | gboolean (*handle_go_to) ( 46 | Mpris2TrackList *object, 47 | GDBusMethodInvocation *invocation, 48 | const gchar *arg_TrackId); 49 | 50 | gboolean (*handle_remove_track) ( 51 | Mpris2TrackList *object, 52 | GDBusMethodInvocation *invocation, 53 | const gchar *arg_TrackId); 54 | 55 | gboolean (*get_can_edit_tracks) (Mpris2TrackList *object); 56 | 57 | const gchar *const * (*get_tracks) (Mpris2TrackList *object); 58 | 59 | void (*track_added) ( 60 | Mpris2TrackList *object, 61 | GVariant *arg_Metadata, 62 | const gchar *arg_AfterTrack); 63 | 64 | void (*track_list_replaced) ( 65 | Mpris2TrackList *object, 66 | const gchar *const *arg_Tracks, 67 | const gchar *arg_CurrentTrack); 68 | 69 | void (*track_metadata_changed) ( 70 | Mpris2TrackList *object, 71 | const gchar *arg_TrackId, 72 | GVariant *arg_Metadata); 73 | 74 | void (*track_removed) ( 75 | Mpris2TrackList *object, 76 | const gchar *arg_TrackId); 77 | 78 | }; 79 | 80 | GType mpris2_track_list_get_type (void) G_GNUC_CONST; 81 | 82 | GDBusInterfaceInfo *mpris2_track_list_interface_info (void); 83 | guint mpris2_track_list_override_properties (GObjectClass *klass, guint property_id_begin); 84 | 85 | 86 | /* D-Bus method call completion functions: */ 87 | void mpris2_track_list_complete_get_tracks_metadata ( 88 | Mpris2TrackList *object, 89 | GDBusMethodInvocation *invocation, 90 | GVariant *Metadata); 91 | 92 | void mpris2_track_list_complete_add_track ( 93 | Mpris2TrackList *object, 94 | GDBusMethodInvocation *invocation); 95 | 96 | void mpris2_track_list_complete_remove_track ( 97 | Mpris2TrackList *object, 98 | GDBusMethodInvocation *invocation); 99 | 100 | void mpris2_track_list_complete_go_to ( 101 | Mpris2TrackList *object, 102 | GDBusMethodInvocation *invocation); 103 | 104 | 105 | 106 | /* D-Bus signal emissions functions: */ 107 | void mpris2_track_list_emit_track_list_replaced ( 108 | Mpris2TrackList *object, 109 | const gchar *const *arg_Tracks, 110 | const gchar *arg_CurrentTrack); 111 | 112 | void mpris2_track_list_emit_track_added ( 113 | Mpris2TrackList *object, 114 | GVariant *arg_Metadata, 115 | const gchar *arg_AfterTrack); 116 | 117 | void mpris2_track_list_emit_track_removed ( 118 | Mpris2TrackList *object, 119 | const gchar *arg_TrackId); 120 | 121 | void mpris2_track_list_emit_track_metadata_changed ( 122 | Mpris2TrackList *object, 123 | const gchar *arg_TrackId, 124 | GVariant *arg_Metadata); 125 | 126 | 127 | 128 | /* D-Bus method calls: */ 129 | void mpris2_track_list_call_get_tracks_metadata ( 130 | Mpris2TrackList *proxy, 131 | const gchar *const *arg_TrackIds, 132 | GCancellable *cancellable, 133 | GAsyncReadyCallback callback, 134 | gpointer user_data); 135 | 136 | gboolean mpris2_track_list_call_get_tracks_metadata_finish ( 137 | Mpris2TrackList *proxy, 138 | GVariant **out_Metadata, 139 | GAsyncResult *res, 140 | GError **error); 141 | 142 | gboolean mpris2_track_list_call_get_tracks_metadata_sync ( 143 | Mpris2TrackList *proxy, 144 | const gchar *const *arg_TrackIds, 145 | GVariant **out_Metadata, 146 | GCancellable *cancellable, 147 | GError **error); 148 | 149 | void mpris2_track_list_call_add_track ( 150 | Mpris2TrackList *proxy, 151 | const gchar *arg_Uri, 152 | const gchar *arg_AfterTrack, 153 | gboolean arg_SetAsCurrent, 154 | GCancellable *cancellable, 155 | GAsyncReadyCallback callback, 156 | gpointer user_data); 157 | 158 | gboolean mpris2_track_list_call_add_track_finish ( 159 | Mpris2TrackList *proxy, 160 | GAsyncResult *res, 161 | GError **error); 162 | 163 | gboolean mpris2_track_list_call_add_track_sync ( 164 | Mpris2TrackList *proxy, 165 | const gchar *arg_Uri, 166 | const gchar *arg_AfterTrack, 167 | gboolean arg_SetAsCurrent, 168 | GCancellable *cancellable, 169 | GError **error); 170 | 171 | void mpris2_track_list_call_remove_track ( 172 | Mpris2TrackList *proxy, 173 | const gchar *arg_TrackId, 174 | GCancellable *cancellable, 175 | GAsyncReadyCallback callback, 176 | gpointer user_data); 177 | 178 | gboolean mpris2_track_list_call_remove_track_finish ( 179 | Mpris2TrackList *proxy, 180 | GAsyncResult *res, 181 | GError **error); 182 | 183 | gboolean mpris2_track_list_call_remove_track_sync ( 184 | Mpris2TrackList *proxy, 185 | const gchar *arg_TrackId, 186 | GCancellable *cancellable, 187 | GError **error); 188 | 189 | void mpris2_track_list_call_go_to ( 190 | Mpris2TrackList *proxy, 191 | const gchar *arg_TrackId, 192 | GCancellable *cancellable, 193 | GAsyncReadyCallback callback, 194 | gpointer user_data); 195 | 196 | gboolean mpris2_track_list_call_go_to_finish ( 197 | Mpris2TrackList *proxy, 198 | GAsyncResult *res, 199 | GError **error); 200 | 201 | gboolean mpris2_track_list_call_go_to_sync ( 202 | Mpris2TrackList *proxy, 203 | const gchar *arg_TrackId, 204 | GCancellable *cancellable, 205 | GError **error); 206 | 207 | 208 | 209 | /* D-Bus property accessors: */ 210 | const gchar *const *mpris2_track_list_get_tracks (Mpris2TrackList *object); 211 | gchar **mpris2_track_list_dup_tracks (Mpris2TrackList *object); 212 | void mpris2_track_list_set_tracks (Mpris2TrackList *object, const gchar *const *value); 213 | 214 | gboolean mpris2_track_list_get_can_edit_tracks (Mpris2TrackList *object); 215 | void mpris2_track_list_set_can_edit_tracks (Mpris2TrackList *object, gboolean value); 216 | 217 | 218 | /* ---- */ 219 | 220 | #define MPRIS2_TYPE_TRACK_LIST_PROXY (mpris2_track_list_proxy_get_type ()) 221 | #define MPRIS2_TRACK_LIST_PROXY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MPRIS2_TYPE_TRACK_LIST_PROXY, Mpris2TrackListProxy)) 222 | #define MPRIS2_TRACK_LIST_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), MPRIS2_TYPE_TRACK_LIST_PROXY, Mpris2TrackListProxyClass)) 223 | #define MPRIS2_TRACK_LIST_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), MPRIS2_TYPE_TRACK_LIST_PROXY, Mpris2TrackListProxyClass)) 224 | #define MPRIS2_IS_TRACK_LIST_PROXY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MPRIS2_TYPE_TRACK_LIST_PROXY)) 225 | #define MPRIS2_IS_TRACK_LIST_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), MPRIS2_TYPE_TRACK_LIST_PROXY)) 226 | 227 | typedef struct _Mpris2TrackListProxy Mpris2TrackListProxy; 228 | typedef struct _Mpris2TrackListProxyClass Mpris2TrackListProxyClass; 229 | typedef struct _Mpris2TrackListProxyPrivate Mpris2TrackListProxyPrivate; 230 | 231 | struct _Mpris2TrackListProxy 232 | { 233 | /*< private >*/ 234 | GDBusProxy parent_instance; 235 | Mpris2TrackListProxyPrivate *priv; 236 | }; 237 | 238 | struct _Mpris2TrackListProxyClass 239 | { 240 | GDBusProxyClass parent_class; 241 | }; 242 | 243 | GType mpris2_track_list_proxy_get_type (void) G_GNUC_CONST; 244 | 245 | void mpris2_track_list_proxy_new ( 246 | GDBusConnection *connection, 247 | GDBusProxyFlags flags, 248 | const gchar *name, 249 | const gchar *object_path, 250 | GCancellable *cancellable, 251 | GAsyncReadyCallback callback, 252 | gpointer user_data); 253 | Mpris2TrackList *mpris2_track_list_proxy_new_finish ( 254 | GAsyncResult *res, 255 | GError **error); 256 | Mpris2TrackList *mpris2_track_list_proxy_new_sync ( 257 | GDBusConnection *connection, 258 | GDBusProxyFlags flags, 259 | const gchar *name, 260 | const gchar *object_path, 261 | GCancellable *cancellable, 262 | GError **error); 263 | 264 | void mpris2_track_list_proxy_new_for_bus ( 265 | GBusType bus_type, 266 | GDBusProxyFlags flags, 267 | const gchar *name, 268 | const gchar *object_path, 269 | GCancellable *cancellable, 270 | GAsyncReadyCallback callback, 271 | gpointer user_data); 272 | Mpris2TrackList *mpris2_track_list_proxy_new_for_bus_finish ( 273 | GAsyncResult *res, 274 | GError **error); 275 | Mpris2TrackList *mpris2_track_list_proxy_new_for_bus_sync ( 276 | GBusType bus_type, 277 | GDBusProxyFlags flags, 278 | const gchar *name, 279 | const gchar *object_path, 280 | GCancellable *cancellable, 281 | GError **error); 282 | 283 | 284 | /* ---- */ 285 | 286 | #define MPRIS2_TYPE_TRACK_LIST_SKELETON (mpris2_track_list_skeleton_get_type ()) 287 | #define MPRIS2_TRACK_LIST_SKELETON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MPRIS2_TYPE_TRACK_LIST_SKELETON, Mpris2TrackListSkeleton)) 288 | #define MPRIS2_TRACK_LIST_SKELETON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), MPRIS2_TYPE_TRACK_LIST_SKELETON, Mpris2TrackListSkeletonClass)) 289 | #define MPRIS2_TRACK_LIST_SKELETON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), MPRIS2_TYPE_TRACK_LIST_SKELETON, Mpris2TrackListSkeletonClass)) 290 | #define MPRIS2_IS_TRACK_LIST_SKELETON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MPRIS2_TYPE_TRACK_LIST_SKELETON)) 291 | #define MPRIS2_IS_TRACK_LIST_SKELETON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), MPRIS2_TYPE_TRACK_LIST_SKELETON)) 292 | 293 | typedef struct _Mpris2TrackListSkeleton Mpris2TrackListSkeleton; 294 | typedef struct _Mpris2TrackListSkeletonClass Mpris2TrackListSkeletonClass; 295 | typedef struct _Mpris2TrackListSkeletonPrivate Mpris2TrackListSkeletonPrivate; 296 | 297 | struct _Mpris2TrackListSkeleton 298 | { 299 | /*< private >*/ 300 | GDBusInterfaceSkeleton parent_instance; 301 | Mpris2TrackListSkeletonPrivate *priv; 302 | }; 303 | 304 | struct _Mpris2TrackListSkeletonClass 305 | { 306 | GDBusInterfaceSkeletonClass parent_class; 307 | }; 308 | 309 | GType mpris2_track_list_skeleton_get_type (void) G_GNUC_CONST; 310 | 311 | Mpris2TrackList *mpris2_track_list_skeleton_new (void); 312 | 313 | 314 | G_END_DECLS 315 | 316 | #endif /* __MPRIS2_TRACKLIST_GENERATED_H__ */ 317 | -------------------------------------------------------------------------------- /plugins/mpris2/org.mpris.MediaPlayer2.TrackList.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

7 | Provides access to a short list of tracks which were recently played or 8 | will be played shortly. This is intended to provide context to the 9 | currently-playing track, rather than giving complete access to the 10 | media player's playlist. 11 |

12 |

13 | Example use cases are the list of tracks from the same album as the 14 | currently playing song or the 15 | Rhythmbox play queue. 16 |

17 |

18 | Each track in the tracklist has a unique identifier. 19 | The intention is that this uniquely identifies the track within 20 | the scope of the tracklist. In particular, if a media item 21 | (a particular music file, say) occurs twice in the track list, each 22 | occurrence should have a different identifier. If a track is removed 23 | from the middle of the playlist, it should not affect the track ids 24 | of any other tracks in the tracklist. 25 |

26 |

27 | As a result, the traditional track identifiers of URLs and position 28 | in the playlist cannot be used. Any scheme which satisfies the 29 | uniqueness requirements is valid, as clients should not make any 30 | assumptions about the value of the track id beyond the fact 31 | that it is a unique identifier. 32 |

33 |

34 | Note that the (memory and processing) burden of implementing the 35 | TrackList interface and maintaining unique track ids for the 36 | playlist can be mitigated by only exposing a subset of the playlist when 37 | it is very long (the 20 or so tracks around the currently playing 38 | track, for example). This is a recommended practice as the tracklist 39 | interface is not designed to enable browsing through a large list of tracks, 40 | but rather to provide clients with context about the currently playing track. 41 |

42 |
43 | 44 | 45 | 46 |

A mapping from metadata attribute names to values.

47 |

48 | The mpris:trackid attribute must always be present, and must be 49 | of D-Bus type "o". This contains a D-Bus path that uniquely identifies 50 | the track within the scope of the playlist. There may or may not be 51 | an actual D-Bus object at that path; this specification says nothing 52 | about what interfaces such an object may implement. 53 |

54 |

55 | If the length of the track is known, it should be provided in the 56 | metadata property with the "mpris:length" key. The length must be 57 | given in microseconds, and be represented as a signed 64-bit integer. 58 |

59 |

60 | If there is an image associated with the track, a URL for it may be 61 | provided using the "mpris:artUrl" key. For other metadata, fields 62 | defined by the 63 | Xesam ontology 64 | should be used, prefixed by "xesam:". See the 65 | metadata page on the freedesktop.org wiki 66 | for a list of common fields. 67 |

68 |

69 | Lists of strings should be passed using the array-of-string ("as") 70 | D-Bus type. Dates should be passed as strings using the ISO 8601 71 | extended format (eg: 2007-04-29T14:35:51). If the timezone is 72 | known, RFC 3339's internet profile should be used (eg: 73 | 2007-04-29T14:35:51+02:00). 74 |

75 |
76 | 77 | 78 |

79 | The name of the attribute; see the 80 | metadata page 81 | for guidelines on names to use. 82 |

83 |
84 |
85 | 86 | 87 |

The value of the attribute, in the most appropriate format.

88 |
89 |
90 |
91 | 92 | 93 | 94 |

A unique resource identifier.

95 |
96 |
97 | 98 | 99 | 100 | 101 |

The list of track ids for which metadata is requested.

102 |
103 |
104 | 105 | 106 |

Metadata of the set of tracks given as input.

107 |

See the type documentation for more details.

108 |
109 |
110 | 111 |

Gets all the metadata available for a set of tracks.

112 |

113 | Each set of metadata must have a "mpris:trackid" entry at the very least, 114 | which contains a string that uniquely identifies this track within 115 | the scope of the tracklist. 116 |

117 |
118 |
119 | 120 | 121 | 122 | 123 |

124 | The uri of the item to add. Its uri scheme should be an element of the 125 | org.mpris.MediaPlayer2.SupportedUriSchemes 126 | property and the mime-type should match one of the elements of the 127 | org.mpris.MediaPlayer2.SupportedMimeTypes 128 |

129 |
130 |
131 | 132 | 133 |

134 | The identifier of the track after which 135 | the new item should be inserted. The path 136 | /org/mpris/MediaPlayer2/TrackList/NoTrack 137 | indicates that the track should be inserted at the 138 | start of the track list. 139 |

140 |
141 |
142 | 143 | 144 |

145 | Whether the newly inserted track should be considered as 146 | the current track. Setting this to true has the same effect as 147 | calling GoTo afterwards. 148 |

149 |
150 |
151 | 152 |

Adds a URI in the TrackList.

153 |

154 | If the CanEditTracks property is false, 155 | this has no effect. 156 |

157 |

158 | Note: Clients should not assume that the track has been added at the 159 | time when this method returns. They should wait for a TrackAdded (or 160 | TrackListReplaced) signal. 161 |

162 |
163 |
164 | 165 | 166 | 167 | 168 |

Identifier of the track to be removed.

169 |

170 | /org/mpris/MediaPlayer2/TrackList/NoTrack 171 | is not a valid value for this argument. 172 |

173 |
174 |
175 | 176 |

Removes an item from the TrackList.

177 |

If the track is not part of this tracklist, this has no effect.

178 |

179 | If the CanEditTracks property is false, 180 | this has no effect. 181 |

182 |

183 | Note: Clients should not assume that the track has been removed at the 184 | time when this method returns. They should wait for a TrackRemoved (or 185 | TrackListReplaced) signal. 186 |

187 |
188 |
189 | 190 | 191 | 192 | 193 |

Identifier of the track to skip to.

194 |

195 | /org/mpris/MediaPlayer2/TrackList/NoTrack 196 | is not a valid value for this argument. 197 |

198 |
199 |
200 | 201 |

Skip to the specified TrackId.

202 |

If the track is not part of this tracklist, this has no effect.

203 |

204 | If this object is not /org/mpris/MediaPlayer2, 205 | the current TrackList's tracks should be replaced with the contents of 206 | this TrackList, and the TrackListReplaced signal should be fired from 207 | /org/mpris/MediaPlayer2. 208 |

209 |
210 |
211 | 212 | 213 | 214 | 215 |

216 | An array which contains the identifier of each track 217 | in the tracklist, in order. 218 |

219 |

220 | The org.freedesktop.DBus.Properties.PropertiesChanged 221 | signal is emited every time this property changes, but the signal 222 | message does not contain the new value. 223 | 224 | Client implementations should rather rely on the 225 | TrackAdded, 226 | TrackRemoved and 227 | TrackListReplaced signals to keep their 228 | representation of the tracklist up to date. 229 |

230 |
231 |
232 | 233 | 234 | 235 | 236 |

237 | If false, calling 238 | AddTrack or 239 | RemoveTrack will have no effect, 240 | and may raise a NotSupported error. 241 |

242 |
243 |
244 | 245 | 246 | 247 | 248 |

The new content of the tracklist.

249 |
250 |
251 | 252 | 253 |

The identifier of the track to be considered as current.

254 |

255 | /org/mpris/MediaPlayer2/TrackList/NoTrack 256 | indicates that there is no current track. 257 |

258 |

259 | This should correspond to the mpris:trackid field of the 260 | Metadata property of the org.mpris.MediaPlayer2.Player 261 | interface. 262 |

263 |
264 |
265 | 266 |

Indicates that the entire tracklist has been replaced.

267 |

268 | It is left up to the implementation to decide when 269 | a change to the track list is invasive enough that 270 | this signal should be emitted instead of a series of 271 | TrackAdded and TrackRemoved signals. 272 |

273 |
274 |
275 | 276 | 277 | 278 | 279 |

The metadata of the newly added item.

280 |

This must include a mpris:trackid entry.

281 |

See the type documentation for more details.

282 |
283 |
284 | 285 | 286 |

287 | The identifier of the track after which the new track 288 | was inserted. The path 289 | /org/mpris/MediaPlayer2/TrackList/NoTrack 290 | indicates that the track was inserted at the 291 | start of the track list. 292 |

293 |
294 |
295 | 296 |

Indicates that a track has been added to the track list.

297 |
298 |
299 | 300 | 301 | 302 | 303 |

The identifier of the track being removed.

304 |

305 | /org/mpris/MediaPlayer2/TrackList/NoTrack 306 | is not a valid value for this argument. 307 |

308 |
309 |
310 | 311 |

Indicates that a track has been removed from the track list.

312 |
313 |
314 | 315 | 316 | 317 | 318 |

The id of the track which metadata has changed.

319 |

If the track id has changed, this will be the old value.

320 |

321 | /org/mpris/MediaPlayer2/TrackList/NoTrack 322 | is not a valid value for this argument. 323 |

324 |
325 |
326 | 327 | 328 |

The new track metadata.

329 |

330 | This must include a mpris:trackid entry. If the track id has 331 | changed, this will be the new value. 332 |

333 |

See the type documentation for more details.

334 |
335 |
336 | 337 |

338 | Indicates that the metadata of a track in the tracklist has changed. 339 |

340 |

341 | This may indicate that a track has been replaced, in which case the 342 | mpris:trackid metadata entry is different from the TrackId argument. 343 |

344 |
345 |
346 | 347 |
348 |
349 | 350 | -------------------------------------------------------------------------------- /plugins/mpris2/org.mpris.MediaPlayer2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

9 | Brings the media player's user interface to the front using any 10 | appropriate mechanism available. 11 |

12 |

13 | The media player may be unable to control how its user interface 14 | is displayed, or it may not have a graphical user interface at all. 15 | In this case, the CanRaise property is 16 | false and this method does nothing. 17 |

18 |
19 |
20 | 21 | 22 | 23 |

Causes the media player to stop running.

24 |

25 | The media player may refuse to allow clients to shut it down. 26 | In this case, the CanQuit property is 27 | false and this method does nothing. 28 |

29 |

30 | Note: Media players which can be D-Bus activated, or for which there is 31 | no sensibly easy way to terminate a running instance (via the main 32 | interface or a notification area icon for example) should allow clients 33 | to use this method. Otherwise, it should not be needed. 34 |

35 |

If the media player does not have a UI, this should be implemented.

36 |
37 |
38 | 39 | 40 | 41 |

42 | If false, calling 43 | Quit will have no effect, and may 44 | raise a NotSupported error. If true, calling 45 | Quit will cause the media application 46 | to attempt to quit (although it may still be prevented from quitting 47 | by the user, for example). 48 |

49 |
50 |
51 | 52 | 53 | 54 | 55 | 56 |

Whether the media player is occupying the fullscreen.

57 |

58 | This is typically used for videos. A value of true 59 | indicates that the media player is taking up the full screen. 60 |

61 |

62 | Media centre software may well have this value fixed to true 63 |

64 |

65 | If CanSetFullscreen is true, 66 | clients may set this property to true to tell the media player 67 | to enter fullscreen mode, or to false to return to windowed 68 | mode. 69 |

70 |

71 | If CanSetFullscreen is false, 72 | then attempting to set this property should have no effect, and may raise 73 | an error. However, even if it is true, the media player 74 | may still be unable to fulfil the request, in which case attempting to set 75 | this property will have no effect (but should not raise an error). 76 |

77 | 78 |

79 | This allows remote control interfaces, such as LIRC or mobile devices like 80 | phones, to control whether a video is shown in fullscreen. 81 |

82 |
83 |
84 |
85 | 86 | 87 | 88 | 89 | 90 |

91 | If false, attempting to set 92 | Fullscreen will have no effect, and may 93 | raise an error. If true, attempting to set 94 | Fullscreen will not raise an error, and (if it 95 | is different from the current value) will cause the media player to attempt to 96 | enter or exit fullscreen mode. 97 |

98 |

99 | Note that the media player may be unable to fulfil the request. 100 | In this case, the value will not change. If the media player knows in 101 | advance that it will not be able to fulfil the request, however, this 102 | property should be false. 103 |

104 | 105 |

106 | This allows clients to choose whether to display controls for entering 107 | or exiting fullscreen mode. 108 |

109 |
110 |
111 |
112 | 113 | 114 | 115 |

116 | If false, calling 117 | Raise will have no effect, and may 118 | raise a NotSupported error. If true, calling 119 | Raise will cause the media application 120 | to attempt to bring its user interface to the front, although it may 121 | be prevented from doing so (by the window manager, for example). 122 |

123 |
124 |
125 | 126 | 127 | 128 |

129 | Indicates whether the /org/mpris/MediaPlayer2 130 | object implements the org.mpris.MediaPlayer2.TrackList 131 | interface. 132 |

133 |
134 |
135 | 136 | 137 | 138 |

A friendly name to identify the media player to users.

139 |

This should usually match the name found in .desktop files

140 |

(eg: "VLC media player").

141 |
142 |
143 | 144 | 145 | 146 | 147 |

The basename of an installed .desktop file which complies with the Desktop entry specification, 148 | with the ".desktop" extension stripped.

149 |

150 | Example: The desktop entry file is "/usr/share/applications/vlc.desktop", 151 | and this property contains "vlc" 152 |

153 |
154 |
155 | 156 | 157 | 158 |

159 | The URI schemes supported by the media player. 160 |

161 |

162 | This can be viewed as protocols supported by the player in almost 163 | all cases. Almost every media player will include support for the 164 | "file" scheme. Other common schemes are "http" and "rtsp". 165 |

166 |

167 | Note that URI schemes should be lower-case. 168 |

169 | 170 |

171 | This is important for clients to know when using the editing 172 | capabilities of the Playlist interface, for example. 173 |

174 |
175 |
176 |
177 | 178 | 179 | 180 |

181 | The mime-types supported by the media player. 182 |

183 |

184 | Mime-types should be in the standard format (eg: audio/mpeg or 185 | application/ogg). 186 |

187 | 188 |

189 | This is important for clients to know when using the editing 190 | capabilities of the Playlist interface, for example. 191 |

192 |
193 |
194 |
195 | 196 |
197 |
198 | 199 | -------------------------------------------------------------------------------- /plugins/mpris2/plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include "spop.h" 31 | 32 | #include "spop-mpris2.h" 33 | 34 | /* Global variables */ 35 | static GDBusConnection* g_conn = NULL; 36 | static guint g_bus_name_id = 0; 37 | static Mpris2* g_iface = NULL; 38 | static Mpris2Player* g_iface_player = NULL; 39 | static Mpris2TrackList* g_iface_tracklist = NULL; 40 | 41 | /* Plugin management */ 42 | static void on_bus_acquired(GDBusConnection* conn, const gchar* name, gpointer user_data) { 43 | GError* err = NULL; 44 | 45 | g_debug("mpris2: bus acquired on %p, exporting interfaces...", conn, g_iface); 46 | g_conn = conn; 47 | 48 | if (!g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(g_iface), 49 | g_conn, "/org/mpris/MediaPlayer2", &err)) 50 | g_error("mpris2: can't export MediaPlayer2: %s", err->message); 51 | 52 | if (!g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(g_iface_player), 53 | g_conn, "/org/mpris/MediaPlayer2", &err)) 54 | g_error("mpris2: can't export MediaPlayer2.Player: %s", err->message); 55 | 56 | if (!g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(g_iface_tracklist), 57 | g_conn, "/org/mpris/MediaPlayer2", &err)) 58 | g_error("mpris2: can't export MediaPlayer2.TrackList: %s", err->message); 59 | 60 | g_debug("mpris2: interfaces exported."); 61 | } 62 | 63 | static void on_name_acquired(GDBusConnection* conn, const gchar* name, gpointer user_data) { 64 | g_debug("mpris2: name acquired on %p", conn); 65 | } 66 | 67 | static void on_name_lost(GDBusConnection* conn, const gchar* name, gpointer user_data) { 68 | g_error("mpris2: name lost on %p", conn); 69 | } 70 | 71 | G_MODULE_EXPORT void spop_mpris2_init() { 72 | /* Prepare interfaces */ 73 | g_iface = spop_mpris2_skeleton_new(); 74 | if (!g_iface) 75 | g_error("Can't init Mpris2"); 76 | 77 | g_iface_player = spop_mpris2_player_skeleton_new(); 78 | if (!g_iface_player) 79 | g_error("Can't init Mpris2Player"); 80 | 81 | g_iface_tracklist = spop_mpris2_tracklist_skeleton_new(); 82 | if (!g_iface_tracklist) 83 | g_error("Can't init Mpris2TrackList"); 84 | 85 | /* Connect to D-Bus and acquire name */ 86 | g_bus_name_id = g_bus_own_name(G_BUS_TYPE_SESSION, "org.mpris.MediaPlayer2.spopd", 87 | G_BUS_NAME_OWNER_FLAGS_NONE | G_BUS_NAME_OWNER_FLAGS_REPLACE, 88 | on_bus_acquired, on_name_acquired, on_name_lost, 89 | NULL, NULL); 90 | } 91 | 92 | G_MODULE_EXPORT void spop_mpris2_close() { 93 | if (g_bus_name_id != 0) { 94 | g_bus_unown_name(g_bus_name_id); 95 | g_bus_name_id = 0; 96 | } 97 | 98 | if (g_conn) { 99 | g_object_unref(g_conn); 100 | g_conn = NULL; 101 | } 102 | 103 | if (g_iface) { 104 | g_object_unref(g_iface); 105 | g_iface = NULL; 106 | } 107 | 108 | if (g_iface_player) { 109 | g_object_unref(g_iface_player); 110 | g_iface_player = NULL; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /plugins/mpris2/spop-mpris2.h: -------------------------------------------------------------------------------- 1 | #ifndef SPOP_MPRIS2_H 2 | #define SPOP_MPRIS2_H 3 | 4 | #include "mpris2-generated.h" 5 | #include "mpris2-player-generated.h" 6 | #include "mpris2-tracklist-generated.h" 7 | 8 | /* Init interfaces skeleton */ 9 | Mpris2* spop_mpris2_skeleton_new(); 10 | Mpris2Player* spop_mpris2_player_skeleton_new(); 11 | Mpris2TrackList* spop_mpris2_tracklist_skeleton_new(); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /plugins/notify.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "spop.h" 32 | #include "config.h" 33 | #include "interface.h" 34 | #include "queue.h" 35 | #include "spotify.h" 36 | #include "utils.h" 37 | 38 | #define NOTIF_TITLE "spop update" 39 | #define NOTIF_TIMEOUT 8000 40 | #define NOTIF_IMAGE_TIMEOUT 100 41 | 42 | static NotifyNotification* g_notif = NULL; 43 | static gchar* g_notif_image_path = NULL; 44 | 45 | #define col(c, txt) "" txt "" 46 | 47 | static gboolean notif_set_image(sp_track* track) { 48 | GdkPixbuf* pb = NULL; 49 | GError* err = NULL; 50 | guchar* img_data = NULL; 51 | gsize img_size; 52 | gboolean res; 53 | 54 | if (!track_get_image_data(track, (gpointer*) &img_data, &img_size)) 55 | /* Not loaded yet */ 56 | return FALSE; 57 | 58 | if (!img_data) { 59 | /* No cover: we're done */ 60 | g_debug("notif: image has no cover"); 61 | return TRUE; 62 | } 63 | 64 | /* An image is present: save it to a file and update the notification */ 65 | g_debug("notif: image loaded!"); 66 | res = g_file_set_contents(g_notif_image_path, (gchar*) img_data, img_size, &err); 67 | g_free(img_data); 68 | if (!res) { 69 | g_info("notif: can't save image to file: %s", err->message); 70 | return TRUE; 71 | } 72 | 73 | gint wh = config_get_int_opt_group("notify", "image_size", 120); 74 | pb = gdk_pixbuf_new_from_file_at_size(g_notif_image_path, wh, wh, &err); 75 | if (!pb) { 76 | g_info("notif: can't create pixbuf from file: %s", err->message); 77 | return TRUE; 78 | } 79 | notify_notification_set_image_from_pixbuf(g_notif, pb); 80 | g_object_unref(pb); 81 | return TRUE; 82 | } 83 | 84 | /* Callback to update the notification once a track cover is loaded */ 85 | typedef struct { 86 | gchar* body; 87 | sp_track* track; 88 | guint nb_calls; 89 | } notif_image_data; 90 | static gboolean notif_image_callback(notif_image_data* nid) { 91 | queue_status qs; 92 | sp_track* cur_track; 93 | GError* err = NULL; 94 | int time_left = NOTIF_TIMEOUT - (nid->nb_calls * NOTIF_IMAGE_TIMEOUT); 95 | 96 | g_debug("notif: in image callback..."); 97 | 98 | /* Is this timeout still valid/relevant? */ 99 | qs = queue_get_status(&cur_track, NULL, NULL); 100 | if ((qs != STOPPED) && (cur_track == nid->track) && (time_left > 0)) { 101 | /* Yes: try to add the image */ 102 | if (notif_set_image(cur_track)) { 103 | /* Ok: show the timeout and destroy it */ 104 | notify_notification_set_timeout(g_notif, time_left); 105 | if (!notify_notification_show(g_notif, &err)) 106 | g_info("Can't show notification: %s", err->message); 107 | goto nic_destroy; 108 | } 109 | else { 110 | /* Not loaded yet: wait again... */ 111 | nid->nb_calls++; 112 | return TRUE; 113 | } 114 | } 115 | else { 116 | /* No: clean it */ 117 | g_debug("notif: destroying old image callback"); 118 | goto nic_destroy; 119 | } 120 | 121 | nic_destroy: 122 | g_free(nid->body); 123 | g_free(nid); 124 | return FALSE; 125 | } 126 | 127 | static void notification_callback(const GString* status, gpointer data) { 128 | queue_status qs; 129 | sp_track* cur_track; 130 | int cur_track_nb; 131 | int tot_tracks; 132 | 133 | GString* body; 134 | 135 | GError* err = NULL; 136 | 137 | /* Read full status */ 138 | qs = queue_get_status(&cur_track, &cur_track_nb, &tot_tracks); 139 | 140 | /* Prepare the data to display */ 141 | body = g_string_sized_new(1024); 142 | 143 | if (qs == STOPPED) 144 | g_string_printf(body, "[stopped]\n%d tracks in queue", tot_tracks); 145 | else { 146 | /* Read more data */ 147 | gboolean repeat, shuffle; 148 | gchar* track_name; 149 | gchar* track_artist; 150 | gchar* track_album; 151 | 152 | repeat = queue_get_repeat(); 153 | shuffle = queue_get_shuffle(); 154 | track_get_data(cur_track, &track_name, &track_artist, &track_album, NULL, NULL, NULL, NULL); 155 | 156 | /* Prepare data to display */ 157 | if (qs == PAUSED) 158 | g_string_append(body, "[paused]\n"); 159 | 160 | g_string_append_printf(body, "\nNow playing track " col("#afd", "%d") "/" col("#afd", "%d") ":\n\n" 161 | "\t" col("#fad", "%s") "\n" /* title */ 162 | "by\t" col("#adf", "%s") "\n" /* artist */ 163 | "from\t" col("#fda", "%s"), /* album */ 164 | cur_track_nb+1, tot_tracks, track_name, track_artist, track_album); 165 | if (repeat || shuffle) { 166 | g_string_append(body, "\n\nMode: "); 167 | if (repeat) { 168 | g_string_append(body, "" col("#daf", "repeat") ""); 169 | if (shuffle) 170 | g_string_append(body, ", " col("#daf", "shuffle") ""); 171 | } 172 | else if (shuffle) 173 | g_string_append(body, "" col("#daf", "shuffle") ""); 174 | } 175 | 176 | /* Free some memory */ 177 | g_free(track_name); 178 | g_free(track_artist); 179 | g_free(track_album); 180 | 181 | /* Replace "&" with "&" */ 182 | g_string_replace(body, "&", "&"); 183 | } 184 | 185 | /* Create or update the notification */ 186 | if (!g_notif) { 187 | g_notif = notify_notification_new(NOTIF_TITLE, body->str, NULL); 188 | notify_notification_set_urgency(g_notif, NOTIFY_URGENCY_LOW); 189 | } 190 | else 191 | notify_notification_update(g_notif, NOTIF_TITLE, body->str, NULL); 192 | notify_notification_set_timeout(g_notif, NOTIF_TIMEOUT); 193 | notify_notification_set_image_from_pixbuf(g_notif, NULL); 194 | 195 | /* Add an image if needed */ 196 | if ((qs != STOPPED) && config_get_bool_opt_group("notify", "use_images", TRUE)) { 197 | if (!notif_set_image(cur_track)) { 198 | /* Not loaded: add a timeout to try again later */ 199 | notif_image_data* nid = g_malloc(sizeof(notif_image_data)); 200 | nid->body = g_strdup(body->str); 201 | nid->track = cur_track; 202 | nid->nb_calls = 1; 203 | g_timeout_add(100, (GSourceFunc) notif_image_callback, (gpointer) nid); 204 | } 205 | } 206 | 207 | if (!notify_notification_show(g_notif, &err)) 208 | g_info("Can't show notification: %s", err->message); 209 | 210 | g_string_free(body, TRUE); 211 | } 212 | 213 | G_MODULE_EXPORT void spop_notify_init() { 214 | if (!notify_init(g_get_prgname())) 215 | g_error("Can't initialize libnotify."); 216 | 217 | if (!interface_notify_add_callback(notification_callback, NULL)) 218 | g_error("Could not add libnotify callback."); 219 | 220 | g_notif_image_path = g_build_filename(g_get_user_cache_dir(), g_get_prgname(), "notify-image.jpg", NULL); 221 | } 222 | 223 | G_MODULE_EXPORT void spop_notify_close() { 224 | GError* err = NULL; 225 | if (g_notif && !notify_notification_close(g_notif, &err)) 226 | g_warning("Can't close notification: %s", err->message); 227 | notify_uninit(); 228 | g_free(g_notif_image_path); 229 | } 230 | -------------------------------------------------------------------------------- /plugins/oss.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "audio.h" 40 | #include "config.h" 41 | 42 | static int g_oss_fd = -1; 43 | static struct pollfd g_pfd; 44 | static size_t g_oss_frame_size; 45 | static GMutex g_oss_mutex; 46 | 47 | /* "Private" functions, used to set up the OSS device */ 48 | static void oss_open() { 49 | const gchar *oss_dev; 50 | 51 | oss_dev = config_get_string_opt_group("oss", "device", "/dev/dsp"); 52 | 53 | /* Open the device */ 54 | g_debug("Opening OSS device"); 55 | g_oss_fd = open(oss_dev, O_WRONLY); 56 | if (g_oss_fd == -1) 57 | g_error("Can't open OSS device: %s", g_strerror(errno)); 58 | 59 | /* Populate the pollfd struct used by poll() */ 60 | g_pfd.fd = g_oss_fd; 61 | g_pfd.events = POLLOUT; 62 | } 63 | 64 | static void oss_close() { 65 | g_debug("Closing OSS device"); 66 | if (close(g_oss_fd) == -1) 67 | g_error("Can't close OSS device: %s", g_strerror(errno)); 68 | g_oss_fd = -1; 69 | } 70 | 71 | /* Set OSS parameters using "format" from libspotify */ 72 | static void oss_setup(const sp_audioformat* format) { 73 | int tmp; 74 | 75 | /* sample_type is an enum */ 76 | int sample_type; 77 | switch (format->sample_type) { 78 | case SP_SAMPLETYPE_INT16_NATIVE_ENDIAN: 79 | sample_type = AFMT_S16_NE; 80 | g_oss_frame_size = sizeof(int16_t) * format->channels; 81 | break; 82 | default: 83 | g_error("Unknown sample type"); 84 | } 85 | 86 | /* Now really setup the device. The order of the initialization is the one 87 | suggested in the OSS doc for some old devices, just in case... 88 | (http://manuals.opensound.com/developer/callorder.html) */ 89 | 90 | tmp = format->channels; 91 | if (ioctl(g_oss_fd, SNDCTL_DSP_CHANNELS, &tmp) == -1) 92 | g_error("Error setting OSS channels: %s", g_strerror(errno)); 93 | if (tmp != format->channels) 94 | g_error( "Could not set OSS channels to %d (set to %d instead)", format->channels, tmp); 95 | 96 | tmp = sample_type; 97 | if (ioctl(g_oss_fd, SNDCTL_DSP_SETFMT, &tmp) == -1) 98 | g_error("Error setting OSS sample type: %s", g_strerror(errno)); 99 | if (tmp != sample_type) 100 | g_error("Could not set OSS sample type to %d (set to %d instead)", sample_type, tmp); 101 | 102 | tmp = format->sample_rate; 103 | if (ioctl(g_oss_fd, SNDCTL_DSP_SPEED, &tmp) == -1) 104 | g_error("Error setting OSS sample rate: %s", g_strerror(errno)); 105 | /* Sample rate: the OSS doc that differences up to 10% should be accepted */ 106 | if (((100*abs(format->sample_rate - tmp))/format->sample_rate) > 10) 107 | g_error("Could not set OSS sample rate to %d (set to %d instead)", format->sample_rate, tmp); 108 | } 109 | 110 | /* "Public" function, called from a libspotify callback */ 111 | G_MODULE_EXPORT int audio_delivery(const sp_audioformat* format, const void* frames, int num_frames) { 112 | int ret; 113 | 114 | g_mutex_lock(&g_oss_mutex); 115 | 116 | /* What are we supposed to do here? */ 117 | if (num_frames == 0) { 118 | /* Pause: close the device */ 119 | if (g_oss_fd != -1) 120 | oss_close(); 121 | ret = 0; 122 | } 123 | else { 124 | if (g_oss_fd == -1) { 125 | /* Some frames to play, but the device is closed: open it and set it up */ 126 | oss_open(); 127 | oss_setup(format); 128 | } 129 | 130 | /* Is the device ready to be written to? */ 131 | ret = poll(&g_pfd, 1, 0); 132 | if (ret == -1) 133 | g_error("Can't poll OSS device: %s", g_strerror(errno)); 134 | else if (ret != 0) { 135 | /* Ok, we can write to the device without blocking */ 136 | ret = write(g_oss_fd, frames, g_oss_frame_size * num_frames); 137 | if (ret == -1) 138 | g_error("Can't write to OSS device: %s", g_strerror(errno)); 139 | 140 | ret /= g_oss_frame_size; 141 | } 142 | } 143 | 144 | g_mutex_unlock(&g_oss_mutex); 145 | return ret; 146 | } 147 | -------------------------------------------------------------------------------- /plugins/savestate.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "spop.h" 33 | #include "interface.h" 34 | #include "queue.h" 35 | #include "spotify.h" 36 | 37 | /* Global variables */ 38 | static gchar* g_state_file_path = NULL; 39 | 40 | typedef struct { 41 | queue_status qs; 42 | gboolean repeat, shuffle; 43 | int cur_track; 44 | GArray* tracks; 45 | } saved_state; 46 | 47 | /* Save state */ 48 | static gboolean save_state(gpointer data) { 49 | queue_status qs; 50 | gboolean repeat, shuffle; 51 | int cur_track; 52 | GArray* tracks = NULL; 53 | 54 | JsonBuilder* jb = NULL; 55 | JsonGenerator* gen = NULL; 56 | gchar* out = NULL; 57 | 58 | GError* err = NULL; 59 | 60 | g_debug("savestate: starting to save the current state..."); 61 | 62 | /* Get data about the current state */ 63 | qs = queue_get_status(NULL, &cur_track, NULL); 64 | tracks = queue_tracks(); 65 | repeat = queue_get_repeat(); 66 | shuffle = queue_get_shuffle(); 67 | 68 | /* Save them in JSON */ 69 | jb = json_builder_new(); 70 | json_builder_begin_object(jb); 71 | 72 | json_builder_set_member_name(jb, "status"); 73 | switch (qs) { 74 | case STOPPED: 75 | json_builder_add_string_value(jb, "stopped"); break; 76 | case PLAYING: 77 | json_builder_add_string_value(jb, "playing"); break; 78 | case PAUSED: 79 | json_builder_add_string_value(jb, "paused"); break; 80 | default: 81 | g_warning("savestate: bad queue_status value: %d", qs); 82 | goto savestate_clean; 83 | } 84 | 85 | json_builder_set_member_name(jb, "repeat"); 86 | json_builder_add_boolean_value(jb, repeat); 87 | 88 | json_builder_set_member_name(jb, "shuffle"); 89 | json_builder_add_boolean_value(jb, shuffle); 90 | 91 | json_builder_set_member_name(jb, "current_track"); 92 | json_builder_add_int_value(jb, cur_track); 93 | 94 | json_builder_set_member_name(jb, "tracks"); 95 | json_builder_begin_array(jb); 96 | 97 | int i; 98 | for (i=0; i < tracks->len; i++) { 99 | sp_track* tr = g_array_index(tracks, sp_track*, i); 100 | if (!sp_track_is_loaded(tr)) { 101 | g_warning("savestate: queue track %d is not loaded", i); 102 | goto savestate_clean; 103 | } 104 | 105 | sp_link* lnk = sp_link_create_from_track(tr, 0); 106 | gchar uri[1024]; 107 | int uri_len = sp_link_as_string(lnk, uri, 1024); 108 | sp_link_release(lnk); 109 | if (uri_len >= 1024) { 110 | g_warning("savestate: URI too long for track %d", i); 111 | goto savestate_clean; 112 | } 113 | 114 | json_builder_add_string_value(jb, uri); 115 | } 116 | json_builder_end_array(jb); 117 | json_builder_end_object(jb); 118 | 119 | /* Store JSON to file */ 120 | gen = json_generator_new(); 121 | json_generator_set_root(gen, json_builder_get_root(jb)); 122 | out = json_generator_to_data(gen, NULL); 123 | 124 | if (g_file_set_contents(g_state_file_path, out, -1, &err)) 125 | g_debug("savestate: state saved to %s.", g_state_file_path); 126 | else 127 | g_warning("savestate: unable to dump status to file: %s", err->message); 128 | 129 | savestate_clean: 130 | if (tracks) 131 | g_array_free(tracks, TRUE); 132 | if (gen) 133 | g_object_unref(gen); 134 | if (jb) 135 | g_object_unref(jb); 136 | if (out) 137 | g_free(out); 138 | return FALSE; 139 | } 140 | 141 | /* Notification callback */ 142 | static void savestate_notification_callback(const GString* status, gpointer data) { 143 | /* Schedule a call to save_state when idle. This is added with priority 144 | G_PRIORITY_DEFAULT_IDLE, which is lower than G_PRIORITY_DEFAULT, so this 145 | should not interfere with anything else. */ 146 | guint ev = g_idle_add(save_state, NULL); 147 | g_debug("savestate: idle callback added (%d)", ev); 148 | } 149 | 150 | /* Restore state */ 151 | static gboolean really_restore_state(gpointer data) { 152 | saved_state* s = (saved_state*) data; 153 | sp_track* tr; 154 | 155 | /* Check if all tracks are loaded */ 156 | size_t i; 157 | for (i=0; i < s->tracks->len; i++) { 158 | tr = g_array_index(s->tracks, sp_track*, i); 159 | if (!sp_track_is_loaded(tr)) 160 | return TRUE; 161 | } 162 | 163 | /* All tracks are loaded: restore them */ 164 | g_debug("savestate: restoring saved state..."); 165 | queue_clear(FALSE); 166 | 167 | for (i=0; i < s->tracks->len; i++) { 168 | tr = g_array_index(s->tracks, sp_track*, i); 169 | if (track_available(tr)) 170 | queue_add_track(FALSE, tr); 171 | else { 172 | g_info("savestate: track %zu is no longer available", i); 173 | if (s->cur_track == i) { 174 | s->cur_track = -1; 175 | s->qs = STOPPED; 176 | } 177 | else if (s->cur_track > i) 178 | s->cur_track -= 1; 179 | } 180 | sp_track_release(tr); 181 | } 182 | 183 | queue_set_repeat(FALSE, s->repeat); 184 | queue_set_shuffle(FALSE, s->shuffle); 185 | 186 | if (s->cur_track >= 0) 187 | queue_goto(FALSE, s->cur_track, TRUE); 188 | if (s->qs == PLAYING) 189 | queue_play(FALSE); 190 | 191 | /* Done! */ 192 | queue_notify(); 193 | 194 | g_array_free(s->tracks, TRUE); 195 | g_free(s); 196 | 197 | g_debug("savestate: state restored!"); 198 | 199 | return FALSE; 200 | } 201 | 202 | static void restore_state(session_callback_type type, gpointer data, gpointer user_data) { 203 | JsonParser* jp = NULL; 204 | JsonReader* jr = NULL; 205 | 206 | const gchar* sqs; 207 | saved_state* s = NULL; 208 | 209 | GError* err = NULL; 210 | 211 | /* Is it the callback we're interested in? */ 212 | if (type != SPOP_SESSION_LOGGED_IN) 213 | return; 214 | 215 | /* First disable the callback so it's not called again */ 216 | session_remove_callback(restore_state, NULL); 217 | 218 | g_debug("savestate: reading saved state..."); 219 | s = g_new0(saved_state, 1); 220 | 221 | /* Read and parse state file */ 222 | jp = json_parser_new(); 223 | if (!json_parser_load_from_file(jp, g_state_file_path, &err)) { 224 | g_warning("savestate: error while reading state file: %s", err->message); 225 | goto restorestate_error; 226 | } 227 | 228 | jr = json_reader_new(json_parser_get_root(jp)); 229 | 230 | /* Read basic state */ 231 | if (!json_reader_read_member(jr, "status")) goto restorestate_jr_error; 232 | sqs = json_reader_get_string_value(jr); 233 | json_reader_end_member(jr); 234 | if (strcmp(sqs, "stopped")) s->qs = STOPPED; 235 | else if (strcmp(sqs, "playing")) s->qs = PLAYING; 236 | else if (strcmp(sqs, "paused")) s->qs = PAUSED; 237 | else { 238 | g_warning("savestate: bad value for queue status: %s", sqs); 239 | goto restorestate_error; 240 | } 241 | 242 | if (!json_reader_read_member(jr, "repeat")) goto restorestate_jr_error; 243 | s->repeat = json_reader_get_boolean_value(jr); 244 | json_reader_end_member(jr); 245 | 246 | if (!json_reader_read_member(jr, "shuffle")) goto restorestate_jr_error; 247 | s->shuffle = json_reader_get_boolean_value(jr); 248 | json_reader_end_member(jr); 249 | 250 | if (!json_reader_read_member(jr, "current_track")) goto restorestate_jr_error; 251 | s->cur_track = json_reader_get_int_value(jr); 252 | json_reader_end_member(jr); 253 | 254 | /* Now read tracks URIs */ 255 | if (!json_reader_read_member(jr, "tracks")) goto restorestate_jr_error; 256 | if (!json_reader_is_array(jr)) { 257 | g_warning("savestate: error while parsing JSON: tracks is not an array"); 258 | goto restorestate_error; 259 | } 260 | gint tracks = json_reader_count_elements(jr); 261 | if (s->cur_track >= tracks) { 262 | g_warning("savestate: incoherent state file: cur_track >= tracks"); 263 | goto restorestate_error; 264 | } 265 | 266 | s->tracks = g_array_sized_new(FALSE, FALSE, sizeof(sp_track*), tracks); 267 | if (!s->tracks) 268 | g_error("Can't allocate array of %d tracks.", tracks); 269 | 270 | size_t i; 271 | gboolean can_restore_now = TRUE; 272 | for (i=0; i < tracks; i++) { 273 | json_reader_read_element(jr, i); 274 | const gchar* uri = json_reader_get_string_value(jr); 275 | json_reader_end_element(jr); 276 | 277 | sp_link* lnk = sp_link_create_from_string(uri); 278 | sp_linktype lt = sp_link_type(lnk); 279 | if (lt != SP_LINKTYPE_TRACK) { 280 | g_warning("savestate: invalid link type for track %zu: %d", i, lt); 281 | sp_link_release(lnk); 282 | goto restorestate_error; 283 | } 284 | sp_track* tr = sp_link_as_track(lnk); 285 | sp_track_add_ref(tr); 286 | sp_link_release(lnk); 287 | g_array_append_val(s->tracks, tr); 288 | if (!sp_track_is_loaded(tr)) 289 | can_restore_now = FALSE; 290 | } 291 | 292 | /* If possible, restore now, else wait for all tracks to be loaded */ 293 | if (can_restore_now) 294 | really_restore_state(s); 295 | else { 296 | g_timeout_add(100, (GSourceFunc) really_restore_state, s); 297 | g_debug("savestate: waiting for all tracks to be loaded before restoring saved state..."); 298 | } 299 | 300 | /* Add a notification callback */ 301 | if (!interface_notify_add_callback(savestate_notification_callback, NULL)) 302 | g_error("Could not add savestate callback."); 303 | 304 | goto restorestate_clean; 305 | 306 | restorestate_jr_error: 307 | err = (GError*) json_reader_get_error(jr); 308 | g_warning("savestate: error while parsing JSON: %s", err->message); 309 | 310 | restorestate_error: 311 | if (s) { 312 | if (s->tracks) 313 | g_array_free(s->tracks, TRUE); 314 | g_free(s); 315 | } 316 | 317 | restorestate_clean: 318 | if (jp) 319 | g_object_unref(jp); 320 | if (jr) 321 | g_object_unref(jr); 322 | } 323 | 324 | /* Plugin management */ 325 | G_MODULE_EXPORT void spop_savestate_init() { 326 | g_state_file_path = g_build_filename(g_get_user_cache_dir(), g_get_prgname(), "state.json", NULL); 327 | 328 | /* Startup time: restore the previous state once we're logged in */ 329 | session_add_callback(restore_state, NULL); 330 | } 331 | 332 | G_MODULE_EXPORT void spop_savestate_close() { 333 | /* Exit time: save the current state */ 334 | save_state(NULL); 335 | 336 | g_free(g_state_file_path); 337 | } 338 | -------------------------------------------------------------------------------- /plugins/sox.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "spop.h" 32 | #include "audio.h" 33 | #include "config.h" 34 | 35 | /* The SoX API is not the most pleasant to use. 36 | * 37 | * SoX uses the concept of an effects chain. At the beginning of this chain, 38 | * there is an "input" effect, and at the end an "output". Here the input is a 39 | * custom effect that reads libspotify frames from an internal buffer and decode 40 | * them (from "frames" to SoX samples). The output is probably an audio device 41 | * (ALSA, OSS or whatever), as requested by the user in the config file, and 42 | * controlled by SoX. 43 | * 44 | * So here the audio_delivery callback initializes SoX, starts audio output 45 | * (with _sox_start()), fills buffers with data provided by libspotify, and 46 | * closes audio output with _sox_stop(). _sox_start() opens the SoX output, 47 | * creates an effects chain using the data in the config file, and starts the 48 | * player thread. The player thread (_sox_player()) calls sox_flow_effects() to 49 | * run the effects chain from input to output, hence calling the 50 | * _sox_input_drain() callback as needed, and then makes some cleanup (close 51 | * effects chain and output). The _sox_input_drain() callback actually decodes 52 | * frames provided by libspotify into SoX samples, and feeds the rest of libsox 53 | * with these samples so that other effects can be applied. 54 | * 55 | * One problem remains: because of how SoX works, there can be audio output 56 | * *after* playback is stopped (echo, reverb, etc.). So to be able to precisely 57 | * control *when* the output end, we have to add another effect at the end of 58 | * the effects chain: that's _sox_output_flow() (and it sucks). 59 | * 60 | * Because of effects, stopping playback can take a little while. This is 61 | * probably not a desired behaviour. 62 | */ 63 | 64 | #define BUFSIZE 8192 65 | #define BUFNB 8 66 | 67 | /* Buffers */ 68 | typedef struct { 69 | size_t size; 70 | gpointer buf; 71 | } sox_buf; 72 | static GQueue* g_free_bufs = NULL; 73 | static GQueue* g_full_bufs = NULL; 74 | static GMutex g_buf_mutex; 75 | static GCond g_buf_cond; 76 | 77 | /* Player thread control */ 78 | static GThread* g_player_thread = NULL; 79 | static gboolean g_player_stop = FALSE; 80 | static int g_stutters = 0; 81 | 82 | /* SoX settings */ 83 | static gboolean g_sox_init = FALSE; 84 | static const gchar* g_sox_out_type = NULL; 85 | static const gchar* g_sox_out_name = NULL; 86 | static gchar** g_sox_effects = NULL; 87 | static gsize g_sox_effects_size; 88 | 89 | /* SoX output */ 90 | static sox_format_t* g_sox_out = NULL; 91 | static size_t g_sox_frame_size; 92 | 93 | /* SoX effects */ 94 | static sox_effects_chain_t* g_effects_chain = NULL; 95 | 96 | /* Prototypes */ 97 | static void* _sox_player(gpointer data); 98 | static void _sox_log_handler(unsigned level, const char* filename, const char* fmt, va_list ap); 99 | static int _sox_input_drain(sox_effect_t*, sox_sample_t*, size_t*); 100 | static int _sox_output_flow(sox_effect_t*, const sox_sample_t*, sox_sample_t*, size_t*, size_t*); 101 | static sox_effect_handler_t g_sox_input = { "spop_input", NULL, SOX_EFF_MCHAN, NULL, NULL, NULL, 102 | _sox_input_drain, NULL, NULL, 0 }; 103 | static sox_effect_handler_t g_sox_output = { "spop_output", NULL, SOX_EFF_MCHAN, NULL, NULL, _sox_output_flow, 104 | NULL, NULL, NULL, 0 }; 105 | 106 | /* "Private" function used to set up SoX */ 107 | static void _sox_init() { 108 | size_t i; 109 | if (!g_sox_init) { 110 | if (sox_init() != SOX_SUCCESS) 111 | g_error("Can't initialize SoX"); 112 | 113 | sox_globals.output_message_handler = _sox_log_handler; 114 | 115 | g_free_bufs = g_queue_new(); 116 | g_full_bufs = g_queue_new(); 117 | sox_buf* bufs = g_new(sox_buf, BUFNB); 118 | for (i=0; i < BUFNB; i++) { 119 | bufs[i].buf = g_malloc(BUFSIZE); 120 | g_queue_push_tail(g_free_bufs, &bufs[i]); 121 | } 122 | 123 | g_sox_out_type = config_get_string_opt_group("sox", "output_type", NULL); 124 | g_sox_out_name = config_get_string_opt_group("sox", "output_name", "default"); 125 | g_sox_effects = config_get_string_list_group("sox", "effects", &g_sox_effects_size); 126 | 127 | g_sox_init = TRUE; 128 | } 129 | } 130 | 131 | /* "Private" SoX log handler */ 132 | static void _sox_log_handler(unsigned level, const char* filename, const char* fmt, va_list ap) { 133 | /* SoX levels: 1 = FAIL, 2 = WARN, 3 = INFO, 4 = DEBUG, 5 = DEBUG_MORE, 6 = DEBUG_MOST. */ 134 | gchar* msg = g_strdup_vprintf(fmt, ap); 135 | switch (level) { 136 | case 1: 137 | g_warning("libsox: %s: %s", filename, msg); break; 138 | case 2: 139 | g_info("libsox: %s: %s", filename, msg); break; 140 | case 3: 141 | g_debug("libsox: %s: %s", filename, msg); break; 142 | } 143 | g_free(msg); 144 | } 145 | 146 | 147 | /* "Private" heler function to add parse and add a SoX effect to the effects chain */ 148 | static void _sox_parse_effect(gint argc, gchar** argv) { 149 | sox_effect_t* effp; 150 | const sox_effect_handler_t* effhp; 151 | 152 | if (argc < 1) 153 | g_error("Can't parse empty SoX effect"); 154 | 155 | g_debug("SoX: adding effect: %s", argv[0]); 156 | 157 | if (strcmp(argv[0], "spop_input") == 0) 158 | effhp = &g_sox_input; 159 | else if (strcmp(argv[0], "spop_output") == 0) 160 | effhp = &g_sox_output; 161 | else 162 | effhp = sox_find_effect(argv[0]); 163 | 164 | effp = sox_create_effect(effhp); 165 | if (!effp) 166 | g_error("SoX: unknown effect: %s", argv[0]); 167 | 168 | if (sox_effect_options(effp, argc-1, &argv[1]) != SOX_SUCCESS) 169 | g_error("SoX: can't parse options for effect %s", argv[0]); 170 | 171 | if (sox_add_effect(g_effects_chain, effp, &g_sox_out->signal, &g_sox_out->signal) != SOX_SUCCESS) 172 | g_error("SoX: could not add effect %s to effects chain", argv[0]); 173 | g_free(effp); 174 | } 175 | 176 | /* "Private" function used when starting playback with SoX */ 177 | static void _sox_start(const sp_audioformat* format) { 178 | GError* err = NULL; 179 | sox_signalinfo_t si; 180 | sox_encodinginfo_t ei; 181 | 182 | g_debug("SoX: starting playback..."); 183 | 184 | /* Set up sample format */ 185 | if (format->sample_type != SP_SAMPLETYPE_INT16_NATIVE_ENDIAN) 186 | g_error("Unsupported sample type"); 187 | 188 | si.rate = format->sample_rate; 189 | si.channels = format->channels; 190 | si.precision = 16; 191 | si.length = SOX_IGNORE_LENGTH; 192 | si.mult = NULL; 193 | 194 | sox_init_encodinginfo(&ei); 195 | 196 | g_sox_frame_size = sizeof(int16_t) * format->channels; 197 | 198 | /* Open SoX output */ 199 | g_debug("Opening SoX output (type: %s, name: %s)...", g_sox_out_type, g_sox_out_name); 200 | g_sox_out = sox_open_write(g_sox_out_name, &si, NULL, g_sox_out_type, NULL, NULL); 201 | if (!g_sox_out) 202 | g_error("Can't open SoX output"); 203 | 204 | /* Effects */ 205 | if (g_effects_chain) { 206 | sox_delete_effects_chain(g_effects_chain); 207 | g_effects_chain = NULL; 208 | } 209 | g_effects_chain = sox_create_effects_chain(&ei, &g_sox_out->encoding); 210 | if (!g_effects_chain) 211 | g_error("Can't create SoX effects chain"); 212 | 213 | /* Add input effect */ 214 | gchar* args[2]; 215 | args[0] = "spop_input"; 216 | _sox_parse_effect(1, args); 217 | 218 | /* Add user effects */ 219 | gsize i; 220 | for (i=0; i < g_sox_effects_size; i++) { 221 | gint argc; 222 | gchar** argv; 223 | GError* err = NULL; 224 | 225 | if (!g_shell_parse_argv(g_strstrip(g_sox_effects[i]), &argc, &argv, &err)) 226 | g_error("Can't parse SoX effect \"%s\": %s", g_sox_effects[i], err->message); 227 | _sox_parse_effect(argc, argv); 228 | g_strfreev(argv); 229 | } 230 | 231 | /* Add our output control effect */ 232 | args[0] = "spop_output"; 233 | _sox_parse_effect(1, args); 234 | 235 | /* Add output effect */ 236 | args[0] = "output"; 237 | args[1] = (gchar*) g_sox_out; 238 | _sox_parse_effect(2, args); 239 | 240 | /* Start the player thread */ 241 | g_player_stop = FALSE; 242 | g_player_thread = g_thread_try_new("sox_player", _sox_player, NULL, &err); 243 | if (!g_player_thread) 244 | g_error("Error while creating SoX player thread: %s", err->message); 245 | } 246 | 247 | static void _sox_stop() { 248 | g_debug("SoX: requesting player thread to stop."); 249 | 250 | /* Flush the queue */ 251 | g_mutex_lock(&g_buf_mutex); 252 | g_player_stop = TRUE; 253 | while (g_queue_get_length(g_full_bufs) > 0) { 254 | sox_buf* buf = g_queue_pop_tail(g_full_bufs); 255 | g_queue_push_tail(g_free_bufs, buf); 256 | } 257 | g_cond_signal(&g_buf_cond); 258 | g_mutex_unlock(&g_buf_mutex); 259 | 260 | /* Wait until the thread has actually stopped */ 261 | if (g_player_thread) { 262 | g_thread_join(g_player_thread); 263 | g_player_thread = NULL; 264 | } 265 | 266 | /* It's now safe to do some cleanup */ 267 | if (g_effects_chain) { 268 | sox_delete_effects_chain(g_effects_chain); 269 | g_effects_chain = NULL; 270 | } 271 | if (g_sox_out) { 272 | sox_close(g_sox_out); 273 | g_sox_out = NULL; 274 | } 275 | } 276 | 277 | /* Audio player thread */ 278 | static void* _sox_player(gpointer data) { 279 | g_debug("SoX: player thread started."); 280 | sox_flow_effects(g_effects_chain, NULL, NULL); 281 | 282 | g_debug("SoX: player thread stopped."); 283 | 284 | return NULL; 285 | } 286 | 287 | /* Input callback */ 288 | static int _sox_input_drain(sox_effect_t* effp, sox_sample_t* obuf, size_t* osamp) { 289 | /* Is a buffer available? */ 290 | g_mutex_lock(&g_buf_mutex); 291 | while ((g_queue_get_length(g_full_bufs) == 0) && !g_player_stop) { 292 | g_stutters += 1; 293 | g_cond_wait(&g_buf_cond, &g_buf_mutex); 294 | } 295 | 296 | /* Should we stop now? */ 297 | if (g_player_stop) { 298 | g_mutex_unlock(&g_buf_mutex); 299 | g_debug("SoX: stopping playback."); 300 | *osamp = 0; 301 | return SOX_EOF; 302 | } 303 | 304 | sox_buf* buf = g_queue_pop_head(g_full_bufs); 305 | g_mutex_unlock(&g_buf_mutex); 306 | 307 | /* Decode that buffer */ 308 | size_t max_samples = *osamp; 309 | size_t avail_samples = buf->size / (g_sox_frame_size / sizeof(int16_t)); 310 | if (avail_samples > max_samples) 311 | g_error("SoX: avail_samples (%zu) > max_samples (%zu)", avail_samples, max_samples); 312 | 313 | int16_t* frm = (int16_t*) buf->buf; 314 | size_t i; 315 | for (i=0; i < avail_samples; i++) 316 | obuf[i] = SOX_SIGNED_16BIT_TO_SAMPLE(frm[i],); 317 | *osamp = avail_samples; 318 | 319 | /* Make the buffer available */ 320 | g_mutex_lock(&g_buf_mutex); 321 | g_queue_push_tail(g_free_bufs, buf); 322 | g_mutex_unlock(&g_buf_mutex); 323 | 324 | return SOX_SUCCESS; 325 | } 326 | 327 | /* Output callback */ 328 | static int _sox_output_flow(sox_effect_t* effp, const sox_sample_t* ibuf, sox_sample_t* obuf, 329 | size_t* isamp, size_t* osamp) { 330 | /* Should we stop now? */ 331 | /* TODO: is the mutex really needed? */ 332 | g_mutex_lock(&g_buf_mutex); 333 | gboolean stop = g_player_stop; 334 | g_mutex_unlock(&g_buf_mutex); 335 | if (stop) { 336 | *osamp = 0; 337 | return SOX_EOF; 338 | } 339 | 340 | /* Minimal safety check... */ 341 | if (*osamp < *isamp) 342 | g_error("SoX: osamp (%zu) < isamp (%zu)", *osamp, *isamp); 343 | 344 | memcpy(obuf, ibuf, *isamp * sizeof(sox_sample_t)); 345 | *osamp = *isamp; 346 | 347 | return SOX_SUCCESS; 348 | } 349 | 350 | /* "Public" function, called from a libspotify callback */ 351 | G_MODULE_EXPORT int audio_delivery(const sp_audioformat* format, const void* frames, int num_frames) { 352 | static sp_audioformat old_fmt = { .sample_type = SP_SAMPLETYPE_INT16_NATIVE_ENDIAN, 353 | .sample_rate = 0, 354 | .channels = 0 }; 355 | 356 | /* (Maybe) init SoX */ 357 | _sox_init(); 358 | 359 | /* What are we supposed to do here? */ 360 | if (num_frames == 0) { 361 | /* Pause */ 362 | _sox_stop(); 363 | return 0; 364 | } 365 | else { 366 | /* Was there a format change? */ 367 | if ((format->sample_type != old_fmt.sample_type) || 368 | (format->sample_rate != old_fmt.sample_rate) || 369 | (format->channels != old_fmt.channels)) { 370 | g_debug("SoX: format change detected"); 371 | _sox_stop(); 372 | 373 | old_fmt.sample_type = format->sample_type; 374 | old_fmt.sample_rate = format->sample_rate; 375 | old_fmt.channels = format->channels; 376 | } 377 | 378 | /* Is there a free buffer? */ 379 | g_mutex_lock(&g_buf_mutex); 380 | if (g_queue_get_length(g_free_bufs) == 0) { 381 | g_mutex_unlock(&g_buf_mutex); 382 | return 0; 383 | } 384 | 385 | /* Has playback started? */ 386 | if (!g_player_thread) { 387 | _sox_start(format); 388 | } 389 | 390 | /* Copy frames to a free buffer */ 391 | sox_buf* buf = g_queue_pop_head(g_free_bufs); 392 | size_t max_frames = BUFSIZE / g_sox_frame_size; 393 | size_t copied_frames = num_frames < max_frames ? num_frames : max_frames; 394 | size_t copied_size = copied_frames * g_sox_frame_size; 395 | memcpy(buf->buf, frames, copied_size); 396 | buf->size = copied_size; 397 | 398 | /* Make the buffer available to the player */ 399 | g_queue_push_tail(g_full_bufs, buf); 400 | g_cond_signal(&g_buf_cond); 401 | g_mutex_unlock(&g_buf_mutex); 402 | 403 | return copied_frames; 404 | } 405 | } 406 | 407 | /* Increment stats->samples by the number of frames in a buffer */ 408 | static void _compute_samples_in_buffer(sox_buf* buf, int* samples) { 409 | if (buf) { 410 | *samples += buf->size / g_sox_frame_size; 411 | } 412 | } 413 | 414 | /* "Public" function, called from a libspotify callback */ 415 | G_MODULE_EXPORT void get_audio_buffer_stats(sp_session* session, sp_audio_buffer_stats* stats) { 416 | g_mutex_lock(&g_buf_mutex); 417 | 418 | /* Compute the number of samples in each buffer */ 419 | stats->samples = 0; 420 | if (g_full_bufs) 421 | g_queue_foreach(g_full_bufs, (GFunc) _compute_samples_in_buffer, &(stats->samples)); 422 | 423 | stats->stutter = g_stutters; 424 | g_stutters = 0; 425 | 426 | if (stats->stutter > 0) 427 | g_debug("sox stats: samples: %d; stutter: %d", stats->samples, stats->stutter); 428 | 429 | g_mutex_unlock(&g_buf_mutex); 430 | } 431 | -------------------------------------------------------------------------------- /spopd.conf.sample: -------------------------------------------------------------------------------- 1 | # This is a sample configuration file for spopd. 2 | # Be sure to update it to fit your setup! 3 | # 4 | # Since this file contains your Spotify username and password in clear text, 5 | # be sure to keep it in a safe place, with reasonable read permissions 6 | # (something like "chmod 0600 spopd.conf" should be safe). 7 | 8 | [spop] 9 | spotify_username = USERNAME 10 | spotify_password = PASSWORD 11 | 12 | # Prefer high bitrate (320k instead of 160k). This is the default. 13 | #high_bitrate = true 14 | 15 | # Prefer high bitrate for offlinesync. This is the default. 16 | #offline_high_bitrate = true 17 | 18 | # Settings path -- the location where Spotify will write setting files and 19 | # per-user cache items. This includes playlists, track metadata, etc. Defaults 20 | # to $XDG_CACHE_HOME/spop (usually ~/.cache/spop). 21 | #settings_path = /var/lib/spop 22 | 23 | # Cache path -- the location where Spotify will write cache files. This cache 24 | # include tracks, cached browse results and coverarts. Set to empty string to 25 | # disable cache. Can be the same as the settings path. Defaults to 26 | # $XDG_CACHE_HOME/spop (usually ~/.cache/spop). 27 | #cache_path = /var/lib/spop 28 | 29 | # Cache size, in megabytes. The default value is 0, which means 10% of disk free 30 | # space. 31 | #cache_size = 0 32 | 33 | # Enable volume normalization in libspotify. Enabled by default. 34 | #normalize_volume = true 35 | 36 | # Number of results returned by the search command. 37 | #search_results = 100 38 | 39 | # Audio plugin -- right now, four plugins are available: 40 | # - ao: uses libao, a simple and very portable library. Recommended for people 41 | # who use ALSA, a sound server (Pulse Audio, aRts, etc.), or a platform that 42 | # does not support OSS (Windows, MacOS X). 43 | # - oss: uses OSS, which is very simple and highly reliable. Recommended for 44 | # people who use OSS as their main sound system (Linux with OSSv4, *BSD). 45 | # - sox: uses libsox, "the Swiss Army knife of sound processing" library. 46 | # Probably not as lightweight as ao and oss, but supports more platform, and - 47 | # adds the possibility to apply effects to the audio output. More details in - 48 | # the [sox] section. 49 | # - dummy: a dummy plugin that does nothing. Useful when you just want to use 50 | # spop on a device without a sound card. 51 | audio_output = ao 52 | 53 | # Address and port on which spopd should listen for commands. 54 | # The address can be IPv4 (x.x.x.x) or IPv6 (a:b:c::d). 55 | # Use 0.0.0.0 or :: to listen on all the available interfaces. 56 | # Default is port 6602 on all the available loopback addresses (127.0.0.1 and 57 | # ::1). 58 | #listen_address = 127.0.0.1 59 | #listen_port = 6602 60 | 61 | # Path to the log file. If blank, messages will not be saved anywhere. 62 | # Default is blank. 63 | #log_file = 64 | 65 | # Various plugins to load. 66 | #plugins = notify;scrobble 67 | 68 | # Where to look for plugins. If a plugin is not found here, spopd will look for 69 | # it in the standard directories (/usr/lib, /lib, etc). 70 | #plugins_search_path = 71 | 72 | # Pretty-print the JSON output. This makes the output easier to read, which may 73 | # be useful when debugging or using spop using only a telnet client... 74 | #pretty_json = false 75 | 76 | # Proxy configuration 77 | #proxy=http://proxy.lan:3128 78 | #proxy_username= 79 | #proxy_password= 80 | 81 | [scrobble] 82 | # API endpoint of the scrobbling service you're using. 83 | # - Last.FM: http://post.audioscrobbler.com:80/ 84 | # - Libre.FM: http://turtle.libre.fm/ 85 | api_endpoint = http://post.audioscrobbler.com:80/ 86 | username = USERNAME 87 | password = PASSWORD 88 | 89 | [notify] 90 | # Enable or disable notification images. Enabled by default. 91 | #use_images = true 92 | 93 | # Size of images in notifications. libspotify provides 300x300 images, which is 94 | # probably too much for most people, so the default value is 120x120. 95 | #image_size = 120 96 | 97 | [sox] 98 | # Output type for SoX output. Common values: alsa, oss, coreaudio (MacOS X), 99 | # pulseaudio, etc. Can be a file type: wav, mp3, ogg, etc. See `man soxformat' 100 | # for details. 101 | #output_type = alsa 102 | 103 | # Output name. Common values: default (for alsa, coreaudio, pulseaudio), hw:0 104 | # (alsa), /dev/dsp (oss), etc. This can also be left empty to use the default 105 | # value of the device driver. Can also be a file name: output.ogg, etc. See `man 106 | # soxformat' for details. 107 | #output_name = default 108 | 109 | # List of effets to apply to the audio output, with their parameters. See `man 110 | # soxeffects' for details. 111 | #effects = gain -3; pad 0 3; reverb 112 | 113 | [oss] 114 | # Device to use for OSS output. Default is /dev/dsp 115 | #device = /dev/dsp 116 | -------------------------------------------------------------------------------- /src/appkey.c: -------------------------------------------------------------------------------- 1 | /** 2 | * THIS APPLICATION KEY MAY ONLY AND EXCLUSIVELY BE USED IN SPOP. ANY OTHER 3 | * USAGE IS COMPLETELY FORBIDDEN. 4 | */ 5 | #include 6 | #include 7 | const uint8_t g_appkey[] = { 8 | 0x01, 0x5C, 0x66, 0x9B, 0x22, 0x6C, 0x24, 0x01, 0x9E, 0xCD, 0x04, 0xBB, 0x52, 0xF1, 0xA0, 0x64, 9 | 0xC8, 0x44, 0xB2, 0x2E, 0x46, 0x5E, 0xCB, 0xD7, 0x10, 0x09, 0x9F, 0xB1, 0xE3, 0x55, 0x8A, 0x0D, 10 | 0x22, 0x21, 0xBD, 0x23, 0x80, 0x06, 0x1C, 0xBC, 0xF5, 0x5C, 0x20, 0x20, 0x97, 0x68, 0xD7, 0x5D, 11 | 0x9F, 0x55, 0x8E, 0x34, 0xE6, 0x08, 0xD7, 0x07, 0x69, 0x9B, 0xA7, 0x67, 0x55, 0x89, 0xAF, 0xE0, 12 | 0x46, 0x30, 0x62, 0xBB, 0x49, 0xB3, 0x1F, 0x33, 0xAA, 0x18, 0x89, 0x83, 0x57, 0x9E, 0xDD, 0xE3, 13 | 0x33, 0x21, 0x0C, 0x1F, 0x14, 0x18, 0x80, 0x76, 0xDC, 0x08, 0x97, 0x1E, 0xF6, 0xE3, 0x87, 0xF7, 14 | 0x34, 0xB9, 0xB5, 0x68, 0x37, 0x92, 0x6E, 0x7C, 0xBC, 0x4D, 0xAC, 0x56, 0x67, 0xFD, 0x0F, 0x03, 15 | 0xEE, 0x09, 0xE7, 0x74, 0x73, 0xE3, 0xC4, 0xA2, 0x12, 0x94, 0x21, 0x71, 0x15, 0xC6, 0xDB, 0xD2, 16 | 0xAD, 0x61, 0xA4, 0x16, 0x51, 0x8F, 0x1B, 0xEB, 0xC2, 0x0D, 0x60, 0x4F, 0xE4, 0xD2, 0xDA, 0x77, 17 | 0x8D, 0x5E, 0x34, 0xDC, 0x32, 0x2A, 0xAC, 0x9A, 0xCE, 0xA9, 0xC8, 0xB2, 0xF8, 0xE6, 0x5C, 0x90, 18 | 0x7F, 0x5A, 0x1A, 0x28, 0x9D, 0xCF, 0xF3, 0xFB, 0x86, 0x0C, 0x18, 0xFA, 0x81, 0x87, 0xD0, 0x8D, 19 | 0x48, 0x0D, 0x28, 0x8A, 0xBA, 0x01, 0xE6, 0xAC, 0x0D, 0x12, 0x6B, 0x8A, 0x53, 0x1C, 0x00, 0x3A, 20 | 0xA7, 0x89, 0x61, 0x18, 0x2C, 0xF7, 0x15, 0xA5, 0x87, 0x33, 0x0F, 0x55, 0x4E, 0x8F, 0xA5, 0x1F, 21 | 0x8B, 0xCF, 0x1F, 0x28, 0xE8, 0x46, 0xF6, 0x30, 0x3E, 0xA6, 0xE0, 0x2E, 0x96, 0x40, 0x64, 0x9E, 22 | 0x57, 0x70, 0x03, 0x2B, 0x2F, 0x54, 0x58, 0x35, 0xAD, 0xAD, 0xB1, 0xE2, 0xC2, 0x8B, 0xBF, 0x08, 23 | 0xC0, 0x9D, 0xCB, 0x90, 0xC0, 0x58, 0xF3, 0xD1, 0x43, 0x78, 0xE4, 0x73, 0x62, 0x67, 0x8A, 0x8B, 24 | 0x96, 0xE9, 0xE2, 0x77, 0x9A, 0x25, 0x8C, 0xC5, 0xCB, 0x5E, 0xFA, 0x86, 0xB0, 0xF8, 0x8A, 0x1A, 25 | 0x6C, 0xB6, 0x58, 0x7E, 0x03, 0xB2, 0x4B, 0x0D, 0xAC, 0x0B, 0xD9, 0xE9, 0x40, 0x31, 0x37, 0x86, 26 | 0x81, 0x4C, 0x73, 0x0C, 0x64, 0x0C, 0x42, 0x4D, 0x35, 0x73, 0x94, 0x88, 0xC8, 0x30, 0xB5, 0x3D, 27 | 0xA8, 0x1F, 0x21, 0x03, 0x0E, 0x7B, 0x9F, 0x32, 0xB2, 0xF9, 0x76, 0x9A, 0x83, 0xDE, 0x38, 0x25, 28 | 0x44, 29 | }; 30 | const size_t g_appkey_size = sizeof(g_appkey); 31 | -------------------------------------------------------------------------------- /src/audio.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #ifndef AUDIO_H 27 | #define AUDIO_H 28 | 29 | #include 30 | 31 | int audio_delivery(const sp_audioformat *format, const void *frames, int num_frames); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/commands.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #ifndef COMMANDS_H 27 | #define COMMANDS_H 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #include "interface.h" 34 | 35 | typedef void (*command_finalize_func)(gchar* json_result, gpointer data); 36 | typedef struct { 37 | JsonBuilder* jb; 38 | command_finalize_func finalize; 39 | gpointer finalize_data; 40 | } command_context; 41 | 42 | gboolean command_run(command_finalize_func finalize, gpointer finalize_data, command_descriptor* desc, int argc, char** argv); 43 | void command_end(command_context* ctx); 44 | 45 | /* Actual commands */ 46 | gboolean help(command_context* ctx); 47 | 48 | gboolean list_playlists(command_context* ctx); 49 | gboolean list_tracks(command_context* ctx, guint idx); 50 | 51 | gboolean status(command_context* ctx); 52 | gboolean notify(command_context* ctx); 53 | gboolean repeat(command_context* ctx); 54 | gboolean shuffle(command_context* ctx); 55 | 56 | gboolean list_queue(command_context* ctx); 57 | gboolean clear_queue(command_context* ctx); 58 | gboolean remove_queue_items(command_context* ctx, guint first, guint last); 59 | gboolean remove_queue_item(command_context* ctx, guint idx); 60 | 61 | gboolean play_playlist(command_context* ctx, guint idx); 62 | gboolean play_track(command_context* ctx, guint pl_idx, guint tr_idx); 63 | 64 | gboolean add_playlist(command_context* ctx, guint idx); 65 | gboolean add_track(command_context* ctx, guint pl_idx, guint tr_idx); 66 | 67 | gboolean play(command_context* ctx); 68 | gboolean toggle(command_context* ctx); 69 | gboolean stop(command_context* ctx); 70 | gboolean seek(command_context* ctx, guint pos); 71 | 72 | gboolean goto_next(command_context* ctx); 73 | gboolean goto_prev(command_context* ctx); 74 | gboolean goto_nb(command_context* ctx, guint nb); 75 | 76 | gboolean offline_status(command_context* ctx); 77 | gboolean offline_toggle(command_context* ctx, guint idx); 78 | 79 | gboolean image(command_context* ctx); 80 | 81 | gboolean uri_info(command_context* ctx, sp_link* lnk); 82 | gboolean uri_add(command_context* ctx, sp_link* lnk); 83 | gboolean uri_play(command_context* ctx, sp_link* lnk); 84 | gboolean uri_image(command_context* ctx, sp_link* lnk); 85 | gboolean uri_image_size(command_context* ctx, sp_link* lnk, guint size); 86 | 87 | gboolean toggle_star(command_context* ctx); 88 | gboolean uri_star(command_context* ctx, sp_link* lnk, guint starred); 89 | 90 | gboolean search(command_context* ctx, const gchar* query); 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include "config.h" 31 | 32 | /* Mutex for accessing config file */ 33 | static GMutex config_mutex; 34 | 35 | /* Data structure representing the configuration file */ 36 | static GKeyFile* g_config_file = NULL; 37 | 38 | /* Internal function: initialize the data structure and load the config file */ 39 | static void config_ready() { 40 | gchar* cfg_path; 41 | GError* err = NULL; 42 | 43 | g_mutex_lock(&config_mutex); 44 | 45 | if (g_config_file) { 46 | /* Ready to use the config file */ 47 | g_mutex_unlock(&config_mutex); 48 | return; 49 | } 50 | 51 | /* Not ready yet: load the configuration file */ 52 | g_config_file = g_key_file_new(); 53 | if (!g_config_file) 54 | g_error( "Could not allocate a data structure for reading the configuration file."); 55 | 56 | /* Name of the configuration file. */ 57 | cfg_path = g_strdup(g_getenv("SPOPD_CONFIG")); 58 | if (!cfg_path) 59 | cfg_path = g_build_filename(g_get_user_config_dir(), g_get_prgname(), "spopd.conf", NULL); 60 | 61 | if (!g_key_file_load_from_file(g_config_file, cfg_path, G_KEY_FILE_NONE, &err)) 62 | g_error("Can't read configuration file '%s': %s", cfg_path, err->message); 63 | g_free(cfg_path); 64 | g_mutex_unlock(&config_mutex); 65 | } 66 | 67 | /* Read options from the config file. To avoid repetitions, this is put in an ugly macro :) */ 68 | #define CONFIG_GET_FCT(rtype, dsptype, read_fct, fct_name) \ 69 | rtype fct_name##_group(const char* group, const char* name) { \ 70 | rtype value; \ 71 | GError* err = NULL; \ 72 | config_ready(); \ 73 | value = read_fct(g_config_file, group, name, &err); \ 74 | if (err) \ 75 | g_error("Error while reading " dsptype " \"%s::%s\" in configuration file: %s", group, name, err->message); \ 76 | return value; \ 77 | } \ 78 | rtype fct_name(const char* name) { \ 79 | return fct_name##_group(g_get_prgname(), name); \ 80 | } \ 81 | rtype fct_name##_opt_group(const char* group, const char* name, rtype def_value) { \ 82 | config_ready(); \ 83 | if (g_key_file_has_key(g_config_file, group, name, NULL)) \ 84 | return fct_name##_group(group, name); \ 85 | else \ 86 | return def_value; \ 87 | } \ 88 | rtype fct_name##_opt(const char* name, rtype def_value) { \ 89 | return fct_name##_opt_group(g_get_prgname(), name, def_value); \ 90 | } \ 91 | rtype* fct_name##_list_group(const char* group, const char* name, gsize* length) { \ 92 | rtype* value; \ 93 | GError* err = NULL; \ 94 | config_ready(); \ 95 | value = (rtype*) read_fct##_list(g_config_file, group, name, length, &err); \ 96 | if (err) { \ 97 | if (err->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND) { \ 98 | if (length) *length = 0; \ 99 | return NULL; \ 100 | } \ 101 | else \ 102 | g_error("Error while reading " dsptype "_list \"%s\" in configuration file: %s", name, err->message); \ 103 | } \ 104 | return value; \ 105 | } \ 106 | rtype* fct_name##_list(const char* name, gsize* length) { \ 107 | return fct_name##_list_group(g_get_prgname(), name, length); \ 108 | } 109 | 110 | CONFIG_GET_FCT(gboolean, "boolean", g_key_file_get_boolean, config_get_bool) 111 | CONFIG_GET_FCT(int, "integer", g_key_file_get_integer, config_get_int) 112 | CONFIG_GET_FCT(gchar*, "string", g_key_file_get_string, config_get_string) 113 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #ifndef CONFIG_H 27 | #define CONFIG_H 28 | 29 | #include 30 | 31 | /* Prototypes for functions used to read options from the config file. To avoid 32 | * repetitions, this is put in an ugly macro :) */ 33 | #define CONFIG_GET_FCT_PROTO(rtype, fct_name) \ 34 | rtype fct_name##_group(const char* group, const char* name); \ 35 | rtype fct_name(const char* name); \ 36 | rtype fct_name##_opt_group(const char* group, const char* name, rtype def_value); \ 37 | rtype fct_name##_opt(const char* name, rtype def_value); \ 38 | rtype* fct_name##_list_group(const char* group, const char* name, gsize* length); \ 39 | rtype* fct_name##_list(const char* name, gsize* length); 40 | 41 | CONFIG_GET_FCT_PROTO(gboolean, config_get_bool) 42 | CONFIG_GET_FCT_PROTO(int, config_get_int) 43 | CONFIG_GET_FCT_PROTO(gchar*, config_get_string) 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /src/interface.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #ifndef INTERFACE_H 27 | #define INTERFACE_H 28 | 29 | #include 30 | 31 | /* Commands management */ 32 | #define MAX_CMD_ARGS 2 33 | typedef enum { CA_NONE=0, CA_INT, CA_STR, CA_URI } command_arg; 34 | typedef struct { 35 | void* func; 36 | command_arg args[MAX_CMD_ARGS]; 37 | } command_descriptor; 38 | typedef enum { CT_FUNC=0, CT_BYE, CT_QUIT, CT_IDLE } command_type; 39 | typedef struct { 40 | gchar* name; 41 | command_type type; 42 | command_descriptor desc; 43 | gchar* summary; 44 | } command_full_descriptor; 45 | extern command_full_descriptor g_commands[]; 46 | 47 | /* Functions called directly from spop */ 48 | void interface_init(); 49 | 50 | /* Internal functions used to manage the network interface */ 51 | typedef enum { CR_OK=0, CR_CLOSE, CR_DEFERED, CR_IDLE } command_result; 52 | gboolean interface_event(GIOChannel* source, GIOCondition condition, gpointer data); 53 | gboolean interface_client_event(GIOChannel* source, GIOCondition condition, gpointer data); 54 | command_result interface_handle_command(GIOChannel* chan, gchar* command); 55 | gboolean interface_write(GIOChannel* source, const gchar* str); 56 | void interface_finalize(const gchar* str, GIOChannel* chan); 57 | 58 | /* Notify clients (channels or plugins) that are waiting for an update */ 59 | void interface_notify(); 60 | void interface_notify_chan(gpointer data, gpointer user_data); 61 | void interface_notify_callback(gpointer data, gpointer user_data); 62 | 63 | typedef void (*spop_notify_callback_ptr)(const GString*, gpointer); 64 | gboolean interface_notify_add_callback(spop_notify_callback_ptr func, gpointer data); 65 | gboolean interface_notify_remove_callback(spop_notify_callback_ptr func, gpointer data); 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #if __APPLE__ 27 | // In Mac OS X 10.5 and later trying to use the daemon function gives a “‘daemon’ is deprecated” 28 | // error, which prevents compilation because we build with "-Werror". 29 | // Since this is supposed to be portable cross-platform code, we don't care that daemon is 30 | // deprecated on Mac OS X 10.5, so we use this preprocessor trick to eliminate the error message. 31 | // See: 32 | #define daemon fake_daemon_function 33 | #endif 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #if __APPLE__ 45 | #undef daemon 46 | extern int daemon(int, int); 47 | #endif 48 | 49 | #include "spop.h" 50 | #include "config.h" 51 | #include "interface.h" 52 | #include "plugin.h" 53 | #include "queue.h" 54 | #include "spotify.h" 55 | 56 | static const char* copyright_notice = 57 | "spop Copyright (C) " SPOP_YEAR " Thomas Jost and the spop contributors\n" 58 | "This program comes with ABSOLUTELY NO WARRANTY.\n" 59 | "This is free software, and you are welcome to redistribute it under certain conditions.\n" 60 | "See the COPYING file bundled with this program for details.\n" 61 | "Powered by SPOTIFY(R) CORE\n"; 62 | 63 | /*************************************** 64 | *** Global variables and prototypes *** 65 | ***************************************/ 66 | gboolean debug_mode = FALSE; 67 | gboolean verbose_mode = FALSE; 68 | 69 | static void exit_handler_init(); 70 | static void exit_handler(); 71 | static void sigint_handler(int signum); 72 | 73 | /* Logging stuff */ 74 | static GRecMutex g_log_mutex; 75 | static const gchar* g_log_file_path = NULL; 76 | static GIOChannel* g_log_channel = NULL; 77 | static void logging_init(); 78 | static void sighup_handler(int signum); 79 | static void spop_log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data); 80 | 81 | 82 | /********************** 83 | *** Initialization *** 84 | **********************/ 85 | int main(int argc, char** argv) { 86 | gboolean daemon_mode = TRUE; 87 | const char* username; 88 | const char* password; 89 | GMainLoop* main_loop; 90 | 91 | /* Parse command line options */ 92 | int opt; 93 | while ((opt = getopt(argc, argv, "dfhvc:")) != -1) { 94 | switch (opt) { 95 | case 'd': 96 | debug_mode = TRUE; 97 | case 'v': 98 | verbose_mode = TRUE; 99 | case 'f': 100 | daemon_mode = FALSE; break; 101 | case 'c': 102 | g_setenv("SPOPD_CONFIG", optarg, 1); break; 103 | default: 104 | printf("Usage: spopd [options]\n" 105 | "Options:\n" 106 | " -d debug mode (implies -f and -v)\n" 107 | " -f run in foreground (default: fork to background)\n" 108 | " -v verbose mode (implies -f)\n" 109 | " -c location and name of configuration file\n" 110 | " -h display this message\n"); 111 | return 0; 112 | } 113 | } 114 | 115 | #if !GLIB_CHECK_VERSION(2, 35, 0) 116 | g_type_init(); 117 | #endif 118 | 119 | g_set_application_name("spop " SPOP_VERSION); 120 | g_set_prgname("spop"); 121 | 122 | /* PulseAudio properties */ 123 | g_setenv("PULSE_PROP_application.name", "spop " SPOP_VERSION, TRUE); 124 | g_setenv("PULSE_PROP_media.role", "music", TRUE); 125 | //g_setenv("PULSE_PROP_application.icon_name", "music", TRUE); 126 | 127 | printf("%s\n", copyright_notice); 128 | 129 | /* Log handler */ 130 | logging_init(); 131 | 132 | if (!daemon_mode) { 133 | /* Stay in foreground: do everything here */ 134 | if (debug_mode) 135 | g_info("Running in debug mode"); 136 | } 137 | else { 138 | /* Run in daemon mode: fork to background */ 139 | printf("Switching to daemon mode...\n"); 140 | if (daemon(0, 0) != 0) 141 | g_error("Error while forking process: %s", g_strerror(errno)); 142 | 143 | } 144 | 145 | /* Init essential stuff */ 146 | main_loop = g_main_loop_new(NULL, FALSE); 147 | exit_handler_init(); 148 | 149 | /* Read username and password */ 150 | username = config_get_string("spotify_username"); 151 | password = config_get_string("spotify_password"); 152 | 153 | /* Init plugins */ 154 | plugins_init(); 155 | 156 | /* Init login */ 157 | session_init(); 158 | session_login(username, password); 159 | 160 | /* Init various subsystems */ 161 | interface_init(); 162 | 163 | /* Event loop */ 164 | g_main_loop_run(main_loop); 165 | 166 | return 0; 167 | } 168 | 169 | 170 | /*************************************************** 171 | *** Exit handler (called on normal termination) *** 172 | ***************************************************/ 173 | void exit_handler_init() { 174 | /* On normal exit, use exit_handler */ 175 | atexit(exit_handler); 176 | 177 | /* Trap SIGINT (Ctrl+C) to also use exit_handler */ 178 | if (signal(SIGINT, sigint_handler) == SIG_ERR) 179 | g_error("Can't install signal handler: %s", g_strerror(errno)); 180 | } 181 | 182 | void exit_handler() { 183 | g_debug("Entering exit handler..."); 184 | 185 | plugins_close(); 186 | session_logout(); 187 | 188 | g_message("Exiting."); 189 | } 190 | 191 | void sigint_handler(int signum) { 192 | g_info("Got SIGINT."); 193 | 194 | exit_handler(); 195 | 196 | /* The proper way of doing this is to kill ourself (not to call exit()) */ 197 | if (signal(SIGINT, SIG_DFL) == SIG_ERR) 198 | g_error("Can't install signal handler: %s", g_strerror(errno)); 199 | raise(SIGINT); 200 | } 201 | 202 | 203 | /************************** 204 | *** Logging management *** 205 | **************************/ 206 | void logging_init() { 207 | /* Set the default handler */ 208 | g_log_set_default_handler(spop_log_handler, NULL); 209 | 210 | /* Open the log file */ 211 | g_log_file_path = config_get_string_opt("log_file", ""); 212 | if (strlen(g_log_file_path) > 0) { 213 | /* Install a handler so that we can reopen the file on SIGHUP */ 214 | if (signal(SIGHUP, sighup_handler) == SIG_ERR) 215 | g_error("Can't install signal handler: %s", g_strerror(errno)); 216 | 217 | /* And open the file using this handler :) */ 218 | sighup_handler(0); 219 | } 220 | } 221 | 222 | void sighup_handler(int signum) { 223 | GError* err = NULL; 224 | 225 | g_rec_mutex_lock(&g_log_mutex); 226 | if (g_log_channel && (g_io_channel_shutdown(g_log_channel, TRUE, &err) != G_IO_STATUS_NORMAL)) 227 | g_error("Can't close log file: %s", err->message); 228 | 229 | if (strlen(g_log_file_path) > 0) { 230 | g_log_channel = g_io_channel_new_file(g_log_file_path, "a", &err); 231 | if (!g_log_channel) 232 | g_error("Can't open log file (%s): %s", g_log_file_path, err->message); 233 | } 234 | g_rec_mutex_unlock(&g_log_mutex); 235 | } 236 | 237 | void spop_log_handler(const gchar* log_domain, GLogLevelFlags log_level, const gchar* message, gpointer user_data) { 238 | GString* log_line = NULL; 239 | GDateTime* datetime; 240 | gchar* timestr; 241 | 242 | GError* err = NULL; 243 | gchar* level = ""; 244 | 245 | g_rec_mutex_lock(&g_log_mutex); 246 | 247 | /* Convert log_level to a string */ 248 | if (log_level & G_LOG_LEVEL_ERROR) 249 | level = "ERR "; 250 | else if (log_level & G_LOG_LEVEL_CRITICAL) 251 | level = "CRIT"; 252 | else if (log_level & G_LOG_LEVEL_WARNING) 253 | level = "WARN"; 254 | else if (log_level & G_LOG_LEVEL_MESSAGE) 255 | level = "MSG "; 256 | else if (log_level & G_LOG_LEVEL_INFO) { 257 | if (!verbose_mode) { 258 | g_rec_mutex_unlock(&g_log_mutex); 259 | return; 260 | } 261 | level = "INFO"; 262 | } 263 | else if (log_level & G_LOG_LEVEL_DEBUG) { 264 | if (!debug_mode) { 265 | g_rec_mutex_unlock(&g_log_mutex); 266 | return; 267 | } 268 | level = "DBG "; 269 | } 270 | else if (log_level & G_LOG_LEVEL_LIBSPOTIFY) 271 | level = "SPTF"; 272 | else 273 | g_warn_if_reached(); 274 | 275 | /* Allocate memory and read date/time */ 276 | log_line = g_string_sized_new(1024); 277 | if (!log_line) 278 | g_error("Can't allocate memory."); 279 | 280 | datetime = g_date_time_new_now_local(); 281 | if (!datetime) 282 | g_error("Can't get the current date."); 283 | timestr = g_date_time_format(datetime, "%Y-%m-%d %H:%M:%S"); 284 | if (!timestr) 285 | g_error("Can't format current date to a string."); 286 | 287 | /* Format the message that will be displayed and logged */ 288 | if (log_domain) 289 | g_string_printf(log_line, "%s ", log_domain); 290 | g_string_append_printf(log_line, "%s [%s] %s\n", timestr, level, message); 291 | 292 | /* Free memory used by datetime and timestr */ 293 | g_date_time_unref(datetime); 294 | g_free(timestr); 295 | 296 | /* First display to stderr... */ 297 | fprintf(stderr, "%s", log_line->str); 298 | /* ... then to the log file. */ 299 | if (g_log_channel) { 300 | if (g_io_channel_write_chars(g_log_channel, log_line->str, log_line->len, NULL, &err) != G_IO_STATUS_NORMAL) 301 | g_error("Can't write to log file: %s", err->message); 302 | if (g_io_channel_flush(g_log_channel, &err) != G_IO_STATUS_NORMAL) 303 | g_error("Can't flush log file: %s", err->message); 304 | } 305 | 306 | g_rec_mutex_unlock(&g_log_mutex); 307 | } 308 | -------------------------------------------------------------------------------- /src/plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #include 27 | #include 28 | 29 | #include "spop.h" 30 | #include "config.h" 31 | #include "plugin.h" 32 | 33 | audio_delivery_func_ptr g_audio_delivery_func = NULL; 34 | audio_buffer_stats_func_ptr g_audio_buffer_stats_func = NULL; 35 | 36 | static GList* g_plugins_close_functions = NULL; 37 | 38 | static GModule* plugin_open(char* module_name, char** search_path, gsize size_search_path) { 39 | int i; 40 | gchar* module_path; 41 | GModule* module; 42 | 43 | for (i=0; i < size_search_path; i++) { 44 | module_path = g_module_build_path(search_path[i], module_name); 45 | module = g_module_open(module_path, G_MODULE_BIND_LAZY); 46 | g_free(module_path); 47 | if (module) 48 | return module; 49 | } 50 | return g_module_open(module_name, G_MODULE_BIND_LAZY); 51 | } 52 | 53 | void plugins_init() { 54 | GString* module_name = NULL; 55 | GModule* module; 56 | 57 | gchar* audio_output; 58 | 59 | char** search_path; 60 | gsize search_path_size; 61 | char** plugins; 62 | gsize plugins_size; 63 | void (*plugin_init)(); 64 | void (*plugin_close)(); 65 | 66 | int i; 67 | 68 | module_name = g_string_sized_new(80); 69 | if (!module_name) 70 | g_error("Can't allocate memory."); 71 | 72 | search_path = config_get_string_list("plugins_search_path", &search_path_size); 73 | 74 | /* Load audio plugin */ 75 | audio_output = config_get_string("audio_output"); 76 | g_string_printf(module_name, "libspop_audio_%s", audio_output); 77 | 78 | module = plugin_open(module_name->str, search_path, search_path_size); 79 | if (!module) 80 | g_error("Can't load %s audio plugin: %s", audio_output, g_module_error()); 81 | 82 | if (!g_module_symbol(module, "audio_delivery", (void**) &g_audio_delivery_func)) 83 | g_error("Can't find symbol in audio plugin: %s", g_module_error()); 84 | 85 | if (!g_module_symbol(module, "get_audio_buffer_stats", (void**) &g_audio_buffer_stats_func)) 86 | g_audio_buffer_stats_func = NULL; 87 | 88 | /* Now load other plugins */ 89 | plugins = config_get_string_list("plugins", &plugins_size); 90 | for (i=0; i < plugins_size; i++) { 91 | g_strstrip(plugins[i]); 92 | g_info("Loading plugin %s...", plugins[i]); 93 | 94 | /* Load the module and the symbols (spop__init and spop__close) */ 95 | g_string_printf(module_name, "libspop_plugin_%s", plugins[i]); 96 | module = plugin_open(module_name->str, search_path, search_path_size); 97 | if (!module) { 98 | g_warning("Can't load plugin \"%s\": %s", plugins[i], g_module_error()); 99 | continue; 100 | } 101 | 102 | g_string_printf(module_name, "spop_%s_init", plugins[i]); 103 | if (!g_module_symbol(module, module_name->str, (void**) &plugin_init)) { 104 | g_warning("Can't find symbol \"%s\" in module \"%s\": %s", module_name->str, plugins[i], g_module_error()); 105 | continue; 106 | } 107 | 108 | g_string_printf(module_name, "spop_%s_close", plugins[i]); 109 | if (g_module_symbol(module, module_name->str, (void**) &plugin_close)) 110 | g_plugins_close_functions = g_list_prepend(g_plugins_close_functions, plugin_close); 111 | else 112 | g_info("Module \"%s\" does not have a \"%s\" symbol: %s", plugins[i], module_name->str, g_module_error()); 113 | 114 | /* Really init the plugin (hoping it will not blow up) */ 115 | plugin_init(); 116 | 117 | g_debug("Plugin %s loaded and initialized", plugins[i]); 118 | } 119 | g_string_free(module_name, TRUE); 120 | g_strfreev(plugins); 121 | g_strfreev(search_path); 122 | } 123 | 124 | void plugins_close() { 125 | GList* cur = g_plugins_close_functions; 126 | void (*func)(); 127 | 128 | g_debug("Closing plugins..."); 129 | while (cur) { 130 | func = cur->data; 131 | func(); 132 | cur = cur->next; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/plugin.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #ifndef PLUGIN_H 27 | #define PLUGIN_H 28 | 29 | #include 30 | 31 | typedef int (*audio_delivery_func_ptr)(const sp_audioformat*, const void*, int); 32 | extern audio_delivery_func_ptr g_audio_delivery_func; 33 | 34 | typedef void (*audio_buffer_stats_func_ptr)(sp_session*, sp_audio_buffer_stats*); 35 | extern audio_buffer_stats_func_ptr g_audio_buffer_stats_func; 36 | 37 | void plugins_init(); 38 | void plugins_close(); 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /src/queue.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #ifndef QUEUE_H 27 | #define QUEUE_H 28 | 29 | #include 30 | #include 31 | 32 | typedef enum { STOPPED, PLAYING, PAUSED } queue_status; 33 | 34 | /* Queue management */ 35 | void queue_set_track(gboolean notif, sp_track* track); 36 | void queue_add_track(gboolean notif, sp_track* track); 37 | 38 | void queue_set_playlist(gboolean notif, sp_playlist* pl); 39 | void queue_add_playlist(gboolean notif, sp_playlist* pl); 40 | 41 | void queue_clear(gboolean notif); 42 | void queue_remove_tracks(gboolean notif, int idx, int nb); 43 | 44 | /* Playback management */ 45 | void queue_play(gboolean notif); 46 | void queue_stop(gboolean notif); 47 | void queue_toggle(gboolean notif); 48 | void queue_seek(guint pos); 49 | 50 | /* Information about the queue */ 51 | queue_status queue_get_status(sp_track** current_track, int* current_track_number, int* total_tracks); 52 | GArray* queue_tracks(); 53 | 54 | /* Notify clients that something changed */ 55 | void queue_notify(); 56 | 57 | /* Move into the queue */ 58 | void queue_next(gboolean notif); 59 | void queue_prev(gboolean notif); 60 | void queue_goto(gboolean notif, int idx, gboolean reset_shuffle_first); 61 | 62 | /* Playback mode */ 63 | gboolean queue_get_shuffle(); 64 | void queue_set_shuffle(gboolean notif, gboolean shuffle); 65 | void queue_setup_shuffle(); 66 | 67 | gboolean queue_get_repeat(); 68 | void queue_set_repeat(gboolean notif, gboolean repeat); 69 | 70 | /* Callback functions, not to be used directly */ 71 | void cb_queue_track_release(gpointer data, gpointer user_data); 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /src/sd-daemon.h: -------------------------------------------------------------------------------- 1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ 2 | 3 | #ifndef foosddaemonhfoo 4 | #define foosddaemonhfoo 5 | 6 | /*** 7 | Copyright 2010 Lennart Poettering 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation files 11 | (the "Software"), to deal in the Software without restriction, 12 | including without limitation the rights to use, copy, modify, merge, 13 | publish, distribute, sublicense, and/or sell copies of the Software, 14 | and to permit persons to whom the Software is furnished to do so, 15 | subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 24 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 25 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 26 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | ***/ 29 | 30 | #include 31 | #include 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif 36 | 37 | /* 38 | Reference implementation of a few systemd related interfaces for 39 | writing daemons. These interfaces are trivial to implement. To 40 | simplify porting we provide this reference implementation. 41 | Applications are welcome to reimplement the algorithms described 42 | here if they do not want to include these two source files. 43 | 44 | The following functionality is provided: 45 | 46 | - Support for logging with log levels on stderr 47 | - File descriptor passing for socket-based activation 48 | - Daemon startup and status notification 49 | - Detection of systemd boots 50 | 51 | You may compile this with -DDISABLE_SYSTEMD to disable systemd 52 | support. This makes all those calls NOPs that are directly related to 53 | systemd (i.e. only sd_is_xxx() will stay useful). 54 | 55 | Since this is drop-in code we don't want any of our symbols to be 56 | exported in any case. Hence we declare hidden visibility for all of 57 | them. 58 | 59 | You may find an up-to-date version of these source files online: 60 | 61 | http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-daemon.h 62 | http://cgit.freedesktop.org/systemd/systemd/plain/src/libsystemd-daemon/sd-daemon.c 63 | 64 | This should compile on non-Linux systems, too, but with the 65 | exception of the sd_is_xxx() calls all functions will become NOPs. 66 | 67 | See sd-daemon(3) for more information. 68 | */ 69 | 70 | #ifndef _sd_printf_attr_ 71 | #if __GNUC__ >= 4 72 | #define _sd_printf_attr_(a,b) __attribute__ ((format (printf, a, b))) 73 | #else 74 | #define _sd_printf_attr_(a,b) 75 | #endif 76 | #endif 77 | 78 | /* 79 | Log levels for usage on stderr: 80 | 81 | fprintf(stderr, SD_NOTICE "Hello World!\n"); 82 | 83 | This is similar to printk() usage in the kernel. 84 | */ 85 | #define SD_EMERG "<0>" /* system is unusable */ 86 | #define SD_ALERT "<1>" /* action must be taken immediately */ 87 | #define SD_CRIT "<2>" /* critical conditions */ 88 | #define SD_ERR "<3>" /* error conditions */ 89 | #define SD_WARNING "<4>" /* warning conditions */ 90 | #define SD_NOTICE "<5>" /* normal but significant condition */ 91 | #define SD_INFO "<6>" /* informational */ 92 | #define SD_DEBUG "<7>" /* debug-level messages */ 93 | 94 | /* The first passed file descriptor is fd 3 */ 95 | #define SD_LISTEN_FDS_START 3 96 | 97 | /* 98 | Returns how many file descriptors have been passed, or a negative 99 | errno code on failure. Optionally, removes the $LISTEN_FDS and 100 | $LISTEN_PID file descriptors from the environment (recommended, but 101 | problematic in threaded environments). If r is the return value of 102 | this function you'll find the file descriptors passed as fds 103 | SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative 104 | errno style error code on failure. This function call ensures that 105 | the FD_CLOEXEC flag is set for the passed file descriptors, to make 106 | sure they are not passed on to child processes. If FD_CLOEXEC shall 107 | not be set, the caller needs to unset it after this call for all file 108 | descriptors that are used. 109 | 110 | See sd_listen_fds(3) for more information. 111 | */ 112 | int sd_listen_fds(int unset_environment); 113 | 114 | /* 115 | Helper call for identifying a passed file descriptor. Returns 1 if 116 | the file descriptor is a FIFO in the file system stored under the 117 | specified path, 0 otherwise. If path is NULL a path name check will 118 | not be done and the call only verifies if the file descriptor 119 | refers to a FIFO. Returns a negative errno style error code on 120 | failure. 121 | 122 | See sd_is_fifo(3) for more information. 123 | */ 124 | int sd_is_fifo(int fd, const char *path); 125 | 126 | /* 127 | Helper call for identifying a passed file descriptor. Returns 1 if 128 | the file descriptor is a special character device on the file 129 | system stored under the specified path, 0 otherwise. 130 | If path is NULL a path name check will not be done and the call 131 | only verifies if the file descriptor refers to a special character. 132 | Returns a negative errno style error code on failure. 133 | 134 | See sd_is_special(3) for more information. 135 | */ 136 | int sd_is_special(int fd, const char *path); 137 | 138 | /* 139 | Helper call for identifying a passed file descriptor. Returns 1 if 140 | the file descriptor is a socket of the specified family (AF_INET, 141 | ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If 142 | family is 0 a socket family check will not be done. If type is 0 a 143 | socket type check will not be done and the call only verifies if 144 | the file descriptor refers to a socket. If listening is > 0 it is 145 | verified that the socket is in listening mode. (i.e. listen() has 146 | been called) If listening is == 0 it is verified that the socket is 147 | not in listening mode. If listening is < 0 no listening mode check 148 | is done. Returns a negative errno style error code on failure. 149 | 150 | See sd_is_socket(3) for more information. 151 | */ 152 | int sd_is_socket(int fd, int family, int type, int listening); 153 | 154 | /* 155 | Helper call for identifying a passed file descriptor. Returns 1 if 156 | the file descriptor is an Internet socket, of the specified family 157 | (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM, 158 | SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version 159 | check is not done. If type is 0 a socket type check will not be 160 | done. If port is 0 a socket port check will not be done. The 161 | listening flag is used the same way as in sd_is_socket(). Returns a 162 | negative errno style error code on failure. 163 | 164 | See sd_is_socket_inet(3) for more information. 165 | */ 166 | int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port); 167 | 168 | /* 169 | Helper call for identifying a passed file descriptor. Returns 1 if 170 | the file descriptor is an AF_UNIX socket of the specified type 171 | (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0 172 | a socket type check will not be done. If path is NULL a socket path 173 | check will not be done. For normal AF_UNIX sockets set length to 174 | 0. For abstract namespace sockets set length to the length of the 175 | socket name (including the initial 0 byte), and pass the full 176 | socket path in path (including the initial 0 byte). The listening 177 | flag is used the same way as in sd_is_socket(). Returns a negative 178 | errno style error code on failure. 179 | 180 | See sd_is_socket_unix(3) for more information. 181 | */ 182 | int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length); 183 | 184 | /* 185 | Helper call for identifying a passed file descriptor. Returns 1 if 186 | the file descriptor is a POSIX Message Queue of the specified name, 187 | 0 otherwise. If path is NULL a message queue name check is not 188 | done. Returns a negative errno style error code on failure. 189 | */ 190 | int sd_is_mq(int fd, const char *path); 191 | 192 | /* 193 | Informs systemd about changed daemon state. This takes a number of 194 | newline separated environment-style variable assignments in a 195 | string. The following variables are known: 196 | 197 | READY=1 Tells systemd that daemon startup is finished (only 198 | relevant for services of Type=notify). The passed 199 | argument is a boolean "1" or "0". Since there is 200 | little value in signaling non-readiness the only 201 | value daemons should send is "READY=1". 202 | 203 | STATUS=... Passes a single-line status string back to systemd 204 | that describes the daemon state. This is free-from 205 | and can be used for various purposes: general state 206 | feedback, fsck-like programs could pass completion 207 | percentages and failing programs could pass a human 208 | readable error message. Example: "STATUS=Completed 209 | 66% of file system check..." 210 | 211 | ERRNO=... If a daemon fails, the errno-style error code, 212 | formatted as string. Example: "ERRNO=2" for ENOENT. 213 | 214 | BUSERROR=... If a daemon fails, the D-Bus error-style error 215 | code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut" 216 | 217 | MAINPID=... The main pid of a daemon, in case systemd did not 218 | fork off the process itself. Example: "MAINPID=4711" 219 | 220 | WATCHDOG=1 Tells systemd to update the watchdog timestamp. 221 | Services using this feature should do this in 222 | regular intervals. A watchdog framework can use the 223 | timestamps to detect failed services. 224 | 225 | Daemons can choose to send additional variables. However, it is 226 | recommended to prefix variable names not listed above with X_. 227 | 228 | Returns a negative errno-style error code on failure. Returns > 0 229 | if systemd could be notified, 0 if it couldn't possibly because 230 | systemd is not running. 231 | 232 | Example: When a daemon finished starting up, it could issue this 233 | call to notify systemd about it: 234 | 235 | sd_notify(0, "READY=1"); 236 | 237 | See sd_notifyf() for more complete examples. 238 | 239 | See sd_notify(3) for more information. 240 | */ 241 | int sd_notify(int unset_environment, const char *state); 242 | 243 | /* 244 | Similar to sd_notify() but takes a format string. 245 | 246 | Example 1: A daemon could send the following after initialization: 247 | 248 | sd_notifyf(0, "READY=1\n" 249 | "STATUS=Processing requests...\n" 250 | "MAINPID=%lu", 251 | (unsigned long) getpid()); 252 | 253 | Example 2: A daemon could send the following shortly before 254 | exiting, on failure: 255 | 256 | sd_notifyf(0, "STATUS=Failed to start up: %s\n" 257 | "ERRNO=%i", 258 | strerror(errno), 259 | errno); 260 | 261 | See sd_notifyf(3) for more information. 262 | */ 263 | int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_attr_(2,3); 264 | 265 | /* 266 | Returns > 0 if the system was booted with systemd. Returns < 0 on 267 | error. Returns 0 if the system was not booted with systemd. Note 268 | that all of the functions above handle non-systemd boots just 269 | fine. You should NOT protect them with a call to this function. Also 270 | note that this function checks whether the system, not the user 271 | session is controlled by systemd. However the functions above work 272 | for both user and system services. 273 | 274 | See sd_booted(3) for more information. 275 | */ 276 | int sd_booted(void); 277 | 278 | #ifdef __cplusplus 279 | } 280 | #endif 281 | 282 | #endif 283 | -------------------------------------------------------------------------------- /src/spop.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #ifndef SPOP_H 27 | #define SPOP_H 28 | 29 | #include 30 | 31 | #define SPOP_VERSION "0.0.1" 32 | #define SPOP_YEAR "2010, 2011, 2012, 2013, 2014, 2015" 33 | 34 | /* Verbosity */ 35 | extern gboolean debug_mode; 36 | extern gboolean verbose_mode; 37 | 38 | /* Logging */ 39 | #define G_LOG_LEVEL_LIBSPOTIFY (1 << (G_LOG_LEVEL_USER_SHIFT)) 40 | 41 | #ifndef g_info 42 | #define g_info(...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, __VA_ARGS__) 43 | #endif 44 | 45 | #define g_log_libspotify(...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_LIBSPOTIFY, __VA_ARGS__) 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/spotify.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #ifndef SPOTIFY_H 27 | #define SPOTIFY_H 28 | 29 | #include 30 | #include 31 | 32 | /* Init functions */ 33 | void session_init(); 34 | void session_login(const char* username, const char* password); 35 | void session_logout(); 36 | 37 | /* Playlist management */ 38 | int playlists_len(); 39 | sp_playlist* playlist_get(int nb); 40 | sp_playlist* playlist_get_from_link(sp_link* lnk); 41 | sp_playlist_type playlist_type(int nb); 42 | gchar* playlist_folder_name(int nb); 43 | sp_playlist_offline_status playlist_get_offline_status(sp_playlist* pl); 44 | void playlist_set_offline_mode(sp_playlist* pl, gboolean mode); 45 | int playlist_get_offline_download_completed(sp_playlist* pl); 46 | 47 | /* Session management */ 48 | void session_load(sp_track* track); 49 | void session_unload(); 50 | void session_play(gboolean play); 51 | void session_seek(guint pos); 52 | guint session_play_time(); 53 | void session_get_offline_sync_status(sp_offline_sync_status* status, gboolean* sync_in_progress, 54 | int* tracks_to_sync, int* num_playlists, int* time_left); 55 | 56 | /* Session callbacks management */ 57 | typedef enum { 58 | SPOP_SESSION_LOGGED_IN, 59 | SPOP_SESSION_LOAD, 60 | SPOP_SESSION_UNLOAD, 61 | } session_callback_type; 62 | typedef void (*spop_session_callback_ptr)(session_callback_type type, gpointer data, gpointer user_data); 63 | void session_call_callback(gpointer data, gpointer user_data); 64 | gboolean session_add_callback(spop_session_callback_ptr func, gpointer user_data); 65 | gboolean session_remove_callback(spop_session_callback_ptr func, gpointer user_data); 66 | 67 | /* Tracks management */ 68 | GArray* tracks_get_playlist(sp_playlist* pl); 69 | void track_get_data(sp_track* track, gchar** name, gchar** artist, gchar** album, gchar** link, guint* duration, int* popularity, bool *starred); 70 | gboolean track_available(sp_track* track); 71 | void track_set_starred(sp_track** tracks, gboolean starred); 72 | 73 | sp_image* track_get_image(sp_track* track); 74 | gboolean track_get_image_data(sp_track* track, gpointer* data, gsize* len); 75 | gboolean track_get_image_file(sp_track* track, gchar** filename); 76 | 77 | sp_image* image_id_get_image(const void* img_id); 78 | 79 | /* Browsing */ 80 | sp_albumbrowse* albumbrowse_create(sp_album* album, albumbrowse_complete_cb* callback, gpointer userdata); 81 | sp_artistbrowse* artistbrowse_create(sp_artist* artist, artistbrowse_complete_cb* callback, gpointer userdata); 82 | sp_search* search_create(const gchar* query, search_complete_cb* callback, gpointer userdata); 83 | 84 | /* Events management */ 85 | gboolean session_libspotify_event(gpointer data); 86 | gboolean session_next_track_event(gpointer data); 87 | 88 | /* Callbacks */ 89 | void cb_logged_in(sp_session* session, sp_error error); 90 | void cb_logged_out(sp_session* session); 91 | void cb_metadata_updated(sp_session* session); 92 | void cb_connection_error(sp_session* session, sp_error error); 93 | void cb_message_to_user(sp_session* session, const char* message); 94 | void cb_notify_main_thread(sp_session* session); 95 | int cb_music_delivery(sp_session* session, const sp_audioformat* format, const void* frames, int num_frames); 96 | void cb_play_token_lost(sp_session* session); 97 | void cb_log_message(sp_session* session, const char* data); 98 | void cb_end_of_track(sp_session* session); 99 | void cb_streaming_error(sp_session* session, sp_error error); 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #include 27 | 28 | #include "utils.h" 29 | 30 | /* Replace all occurences of old by new in str 31 | * (from https://bugzilla.gnome.org/show_bug.cgi?id=65987) */ 32 | void g_string_replace(GString* str, const char* old, const gchar* new) { 33 | gchar *new_str, **arr; 34 | 35 | arr = g_strsplit(str->str, old, -1); 36 | if (arr != NULL && arr[0] != NULL) 37 | new_str = g_strjoinv(new, arr); 38 | else 39 | new_str = g_strdup(new); 40 | g_strfreev(arr); 41 | 42 | g_string_assign(str, new_str); 43 | } 44 | 45 | /* Add a line number with a fixed width determined by the greatest possible value */ 46 | void g_string_append_line_number(GString* str, int nb, int max_nb) { 47 | gchar fs[10]; 48 | int nb_digits = 0; 49 | while (max_nb > 0) { max_nb /= 10; nb_digits += 1; } 50 | g_snprintf(fs, sizeof(fs), "%%%dd", nb_digits); 51 | g_string_append_printf(str, fs, nb); 52 | } 53 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015 The spop contributors 3 | * 4 | * This file is part of spop. 5 | * 6 | * spop is free software: you can redistribute it and/or modify it under the 7 | * terms of the GNU General Public License as published by the Free Software 8 | * Foundation, either version 3 of the License, or (at your option) any later 9 | * version. 10 | * 11 | * spop is distributed in the hope that it will be useful, but WITHOUT ANY 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 13 | * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * spop. If not, see . 17 | * 18 | * Additional permission under GNU GPL version 3 section 7 19 | * 20 | * If you modify this Program, or any covered work, by linking or combining it 21 | * with libspotify (or a modified version of that library), containing parts 22 | * covered by the terms of the Libspotify Terms of Use, the licensors of this 23 | * Program grant you additional permission to convey the resulting work. 24 | */ 25 | 26 | #ifndef UTILS_H 27 | #define UTILS_H 28 | 29 | #include 30 | 31 | /* String manipulation */ 32 | void g_string_replace(GString* str, const char* old, const gchar* new); 33 | void g_string_append_line_number(GString* str, int nb, int max_nb); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /systemd/spopd.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Spop: a Spotify client daemon 3 | After=network.target sound.target 4 | 5 | [Service] 6 | ExecStart=/usr/bin/spopd -f 7 | 8 | [Install] 9 | WantedBy=default.target 10 | -------------------------------------------------------------------------------- /systemd/spopd.socket: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Spop Daemon Socket 3 | 4 | [Socket] 5 | ListenStream=127.0.0.1:6602 6 | ListenStream=[::1]:6602 7 | 8 | [Install] 9 | WantedBy=sockets.target 10 | --------------------------------------------------------------------------------